diff --git a/doc/API.md b/doc/API.md index 887cfc4d9..01ba64efe 100644 --- a/doc/API.md +++ b/doc/API.md @@ -7,9 +7,10 @@ Commands must be sent as valid JSONRPC 2.0 requests, ending with a `\n`. | Command | Description | | ----------------------------------------------------------- | ---------------------------------------------------- | -| [`stop`](#stop) | Stops liana daemon | +| [`stop`](#stop) | Stops liana daemon | | [`getinfo`](#getinfo) | Get general information about the daemon | | [`getnewaddress`](#getnewaddress) | Get a new receiving address | +| [`listaddresses`](#listaddresses) | List addresses given start_index and count | | [`listcoins`](#listcoins) | List all wallet transaction outputs. | | [`createspend`](#createspend) | Create a new Spend transaction | | [`updatespend`](#updatespend) | Store a created Spend transaction | @@ -79,6 +80,28 @@ This command does not take any parameter for now. | `address` | string | A Bitcoin address | +### `listaddresses` + +List receive and change addresses given start_index and count. Both arguments are optional. +Default value for `start_index` is 0. +If no value is passed for `count` the maximum generated index between receive and change is selected. + +#### Request + +| Field | Type | Description | +| ------------- | ----------------- | ----------------------------------------------------------- | +| `start_index` | integer(optional) | Index of the first address to list | +| `count` | integer(optional) | The number of addresses you want to list | + +#### Response + +| Field | Type | Description | +| ------------- | ----------------- | ----------------------------------------------------------- | +| `index` | integer | Derivation index | +| `receive` | string | Receive address | +| `change` | | Change address | + + ### `listcoins` List all our transaction outputs, regardless of their state (unspent or not). diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 1ba0960fa..f65d07cdb 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -25,7 +25,7 @@ use std::{ use miniscript::{ bitcoin::{ - self, address, + self, address, bip32, locktime::absolute, psbt::{Input as PsbtIn, Output as PsbtOut, PartiallySignedTransaction as Psbt}, }, @@ -33,8 +33,6 @@ use miniscript::{ }; use serde::{Deserialize, Serialize}; -use bitcoin::bip32; - // We would never create a transaction with an output worth less than this. // That's 1$ at 20_000$ per BTC. const DUST_OUTPUT_SATS: u64 = 5_000; @@ -292,16 +290,26 @@ impl DaemonControl { } /// list addresses - pub fn list_addresses(&self, start_index: u32, count: u32) -> GetAddressesResult { + pub fn list_addresses( + &self, + start_index: Option, + count: Option, + ) -> ListAddressesResult { let mut db_conn = self.db.connection(); let receive_index: u32 = db_conn.receive_index().into(); - let change_index: u32 = db_conn.receive_index().into(); - let max_index = receive_index.max(change_index); + let change_index: u32 = db_conn.change_index().into(); - let index = if count != 0 { - start_index + count - 1 + let start_index = start_index.unwrap_or(0); + + let index = if let Some(c) = count { + (start_index + c).saturating_sub(1) } else { - max_index - 1 + let max_index = receive_index.max(change_index); + if start_index >= max_index { + start_index + } else { + max_index.saturating_sub(1) + } }; let addresses: Vec = (start_index..=index) @@ -330,7 +338,7 @@ impl DaemonControl { } }) .collect(); - GetAddressesResult::new(addresses) + ListAddressesResult::new(addresses) } /// Get a list of all known coins. @@ -905,13 +913,13 @@ pub struct AddressInfo { } #[derive(Debug, Clone, Serialize)] -pub struct GetAddressesResult { +pub struct ListAddressesResult { addresses: Vec, } -impl GetAddressesResult { +impl ListAddressesResult { pub fn new(addresses: Vec) -> Self { - GetAddressesResult { addresses } + ListAddressesResult { addresses } } } @@ -1033,25 +1041,46 @@ mod tests { let control = &ms.handle.control; + let list = control.list_addresses(Some(2), Some(5)); + + assert_eq!(list.addresses[0].index, 2); + assert_eq!(list.addresses.last().unwrap().index, 6); + let addr0 = control.get_new_address().address; let addr1 = control.get_new_address().address; let _addr2 = control.get_new_address().address; let addr3 = control.get_new_address().address; let addr4 = control.get_new_address().address; - let list = control.list_addresses(0, 0); + let list = control.list_addresses(Some(0), None); + + assert_eq!(list.addresses[0].index, 0); + assert_eq!(list.addresses[0].receive, addr0); + assert_eq!(list.addresses.last().unwrap().index, 4); + assert_eq!(list.addresses.last().unwrap().receive, addr4); + + let list = control.list_addresses(None, None); assert_eq!(list.addresses[0].index, 0); assert_eq!(list.addresses[0].receive, addr0); assert_eq!(list.addresses.last().unwrap().index, 4); assert_eq!(list.addresses.last().unwrap().receive, addr4); - let list2 = control.list_addresses(1, 3); + let list = control.list_addresses(Some(1), Some(3)); + + assert_eq!(list.addresses[0].index, 1); + assert_eq!(list.addresses[0].receive, addr1); + assert_eq!(list.addresses.last().unwrap().index, 3); + assert_eq!(list.addresses.last().unwrap().receive, addr3); + + let list = control.list_addresses(Some(5), None); + + let addr5 = control.get_new_address().address; - assert_eq!(list2.addresses[0].index, 1); - assert_eq!(list2.addresses[0].receive, addr1); - assert_eq!(list2.addresses.last().unwrap().index, 3); - assert_eq!(list2.addresses.last().unwrap().receive, addr3); + assert_eq!(list.addresses[0].index, 5); + assert_eq!(list.addresses[0].receive, addr5); + assert_eq!(list.addresses.last().unwrap().index, 5); + assert_eq!(list.addresses.last().unwrap().receive, addr5); ms.shutdown(); } diff --git a/src/jsonrpc/api.rs b/src/jsonrpc/api.rs index 27658bf2b..157ce6fe4 100644 --- a/src/jsonrpc/api.rs +++ b/src/jsonrpc/api.rs @@ -88,19 +88,23 @@ fn broadcast_spend(control: &DaemonControl, params: Params) -> Result Result { - let start_index: u32 = params + let start_index: Option = params .get(0, "start_index") - .ok_or_else(|| Error::invalid_params("Missing 'start_index' parameter."))? - .as_i64() - .and_then(|i| i.try_into().ok()) - .ok_or_else(|| Error::invalid_params("Invalid 'start_index' parameter."))?; + .map(|s| { + s.as_u64() + .and_then(|s| s.try_into().ok()) + .ok_or_else(|| Error::invalid_params("Invalid 'start_index' parameter.")) + }) + .transpose()?; - let count: u32 = params + let count: Option = params .get(1, "count") - .ok_or_else(|| Error::invalid_params("Missing 'count' parameter."))? - .as_i64() - .and_then(|i| i.try_into().ok()) - .ok_or_else(|| Error::invalid_params("Invalid 'count' parameter."))?; + .map(|c| { + c.as_u64() + .and_then(|c| c.try_into().ok()) + .ok_or_else(|| Error::invalid_params("Invalid 'count' parameter.")) + }) + .transpose()?; Ok(serde_json::json!( &control.list_addresses(start_index, count) diff --git a/tests/test_rpc.py b/tests/test_rpc.py index 6ea895c87..72a1e66ee 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -38,12 +38,20 @@ def test_getaddress(lianad): def test_listadresses(lianad): list = lianad.rpc.listaddresses(2, 5) - assert 'addresses' in list - - addr = list['addresses'] - - assert addr[0]['index'] == 2 - assert addr[-1]['index'] == 6 + list2 = lianad.rpc.listaddresses(start_index=2, count=5) + assert list == list2 + assert "addresses" in list + addr = list["addresses"] + assert addr[0]["index"] == 2 + assert addr[-1]["index"] == 6 + + list3 = lianad.rpc.listaddresses() + _ = lianad.rpc.getnewaddress() + _ = lianad.rpc.getnewaddress() + list4 = lianad.rpc.listaddresses() + assert len(list4["addresses"]) == len(list3["addresses"]) + 1 + list5 = lianad.rpc.listaddresses(0) + assert list4 == list5 def test_listcoins(lianad, bitcoind):