Merge #709: Implement listadresses
2660b77487d63218019413a4ca33b3a9629fbfc8 implement listadresses (pythcoiner) Pull request description: address #681 todo: - [x] implement tests - [x] update docs edit: i'm really new to rust, don't hesitate to kick my ass when i write stupid code ACKs for top commit: darosior: ACK 2660b77487d63218019413a4ca33b3a9629fbfc8 -- my requests are addressed in followup #808. Tree-SHA512: a5fdfb4516dc0379bfec1be535e752795dec75d28cbc5b9fa4fe9898fa00b1cfaa9cee3b95f4dfd68365f4585426e1b4457a8366cc4f783600704994f879526f
This commit is contained in:
commit
479efe7494
25
doc/API.md
25
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) | Number of addresses to list |
|
||||
|
||||
#### Response
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | ----------------- | ----------------------------------------------------------- |
|
||||
| `index` | integer | Derivation index |
|
||||
| `receive` | string | Receive address |
|
||||
| `change` | string | Change address |
|
||||
|
||||
|
||||
### `listcoins`
|
||||
|
||||
List all our transaction outputs, optionally filtered by status and/or outpoint.
|
||||
|
||||
@ -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},
|
||||
},
|
||||
@ -72,6 +72,8 @@ pub enum CommandError {
|
||||
/// An error that might occur in the racy rescan triggering logic.
|
||||
RescanTrigger(String),
|
||||
RecoveryNotAvailable,
|
||||
InvalidAddressCount,
|
||||
InvalidAddressIndex,
|
||||
}
|
||||
|
||||
impl fmt::Display for CommandError {
|
||||
@ -136,6 +138,8 @@ impl fmt::Display for CommandError {
|
||||
f,
|
||||
"No coin currently spendable through this timelocked recovery path."
|
||||
),
|
||||
Self::InvalidAddressCount => write!(f, "Invalid address count, should be under 2^31-1"),
|
||||
Self::InvalidAddressIndex => write!(f, "Invalid address index, should be under 2^31-1"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,6 +300,69 @@ impl DaemonControl {
|
||||
GetAddressResult::new(address)
|
||||
}
|
||||
|
||||
/// list addresses
|
||||
pub fn list_addresses(
|
||||
&self,
|
||||
start_index: Option<u32>,
|
||||
count: Option<u32>,
|
||||
) -> Result<ListAddressesResult, CommandError> {
|
||||
let mut db_conn = self.db.connection();
|
||||
let receive_index: u32 = db_conn.receive_index().into();
|
||||
let change_index: u32 = db_conn.change_index().into();
|
||||
|
||||
let start_index = start_index.unwrap_or(0);
|
||||
|
||||
if start_index > (2u32.pow(31) - 1) {
|
||||
return Err(CommandError::InvalidAddressIndex);
|
||||
}
|
||||
|
||||
let count = count.unwrap_or_else(|| receive_index.max(change_index) - start_index);
|
||||
|
||||
if count == 0 {
|
||||
let out: Vec<AddressInfo> = Vec::new();
|
||||
return Ok(ListAddressesResult::new(out));
|
||||
}
|
||||
|
||||
let index = start_index
|
||||
.checked_add(count)
|
||||
.and_then(|index| index.checked_sub(1))
|
||||
.and_then(|index| {
|
||||
if index > (2u32.pow(31) - 1) {
|
||||
None
|
||||
} else {
|
||||
Some(index)
|
||||
}
|
||||
})
|
||||
.ok_or(CommandError::InvalidAddressCount)?;
|
||||
|
||||
let addresses: Vec<AddressInfo> = (start_index..=index)
|
||||
.map(|index| {
|
||||
let child = bip32::ChildNumber::from_normal_idx(index).expect("Cannot fail here");
|
||||
|
||||
let receive = self
|
||||
.config
|
||||
.main_descriptor
|
||||
.receive_descriptor()
|
||||
.derive(child, &self.secp)
|
||||
.address(self.config.bitcoin_config.network);
|
||||
|
||||
let change = self
|
||||
.config
|
||||
.main_descriptor
|
||||
.change_descriptor()
|
||||
.derive(child, &self.secp)
|
||||
.address(self.config.bitcoin_config.network);
|
||||
|
||||
AddressInfo {
|
||||
index,
|
||||
receive,
|
||||
change,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(ListAddressesResult::new(addresses))
|
||||
}
|
||||
|
||||
/// Get a list of all known coins, optionally by status and/or outpoint.
|
||||
pub fn list_coins(
|
||||
&self,
|
||||
@ -872,6 +939,24 @@ impl GetAddressResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct AddressInfo {
|
||||
index: u32,
|
||||
receive: bitcoin::Address,
|
||||
change: bitcoin::Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ListAddressesResult {
|
||||
addresses: Vec<AddressInfo>,
|
||||
}
|
||||
|
||||
impl ListAddressesResult {
|
||||
pub fn new(addresses: Vec<AddressInfo>) -> Self {
|
||||
ListAddressesResult { addresses }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct LCSpendInfo {
|
||||
pub txid: bitcoin::Txid,
|
||||
@ -984,6 +1069,55 @@ mod tests {
|
||||
ms.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listaddresses() {
|
||||
let ms = DummyLiana::new(DummyBitcoind::new(), DummyDatabase::new());
|
||||
|
||||
let control = &ms.handle.control;
|
||||
|
||||
let list = control.list_addresses(Some(2), Some(5)).unwrap();
|
||||
|
||||
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(Some(0), None).unwrap();
|
||||
|
||||
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).unwrap();
|
||||
|
||||
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(Some(1), Some(3)).unwrap();
|
||||
|
||||
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 addr5 = control.get_new_address().address;
|
||||
let list = control.list_addresses(Some(5), None).unwrap();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_spend() {
|
||||
let dummy_op = bitcoin::OutPoint::from_str(
|
||||
|
||||
@ -136,6 +136,26 @@ fn list_coins(control: &DaemonControl, params: Option<Params>) -> Result<serde_j
|
||||
Ok(serde_json::json!(&res))
|
||||
}
|
||||
|
||||
fn list_addresses(
|
||||
control: &DaemonControl,
|
||||
params: Option<Params>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let start_index: Option<u32> = params
|
||||
.as_ref()
|
||||
.and_then(|p| p.get(0, "start_index"))
|
||||
.and_then(|i| i.as_u64())
|
||||
.and_then(|i| i.try_into().ok());
|
||||
|
||||
let count: Option<u32> = params
|
||||
.as_ref()
|
||||
.and_then(|p| p.get(1, "count"))
|
||||
.and_then(|c| c.as_u64())
|
||||
.and_then(|i| i.try_into().ok());
|
||||
|
||||
let res = &control.list_addresses(start_index, count)?;
|
||||
Ok(serde_json::json!(&res))
|
||||
}
|
||||
|
||||
fn list_confirmed(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
|
||||
let start: u32 = params
|
||||
.get(0, "start")
|
||||
@ -310,6 +330,10 @@ pub fn handle_request(control: &DaemonControl, req: Request) -> Result<Response,
|
||||
let params = req.params;
|
||||
list_coins(control, params)?
|
||||
}
|
||||
"listaddresses" => {
|
||||
let params = req.params;
|
||||
list_addresses(control, params)?
|
||||
}
|
||||
"listconfirmed" => {
|
||||
let params = req.params.ok_or_else(|| {
|
||||
Error::invalid_params(
|
||||
|
||||
@ -164,6 +164,8 @@ impl From<commands::CommandError> for Error {
|
||||
| commands::CommandError::SpendFinalization(..)
|
||||
| commands::CommandError::InsaneRescanTimestamp(..)
|
||||
| commands::CommandError::AlreadyRescanning
|
||||
| commands::CommandError::InvalidAddressCount
|
||||
| commands::CommandError::InvalidAddressIndex
|
||||
| commands::CommandError::RecoveryNotAvailable => {
|
||||
Error::new(ErrorCode::InvalidParams, e.to_string())
|
||||
}
|
||||
|
||||
@ -37,6 +37,24 @@ def test_getaddress(lianad):
|
||||
assert res["address"] != lianad.rpc.getnewaddress()["address"]
|
||||
|
||||
|
||||
def test_listadresses(lianad):
|
||||
list = lianad.rpc.listaddresses(2, 5)
|
||||
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() # start_index = 0, receive_index = 0
|
||||
_ = lianad.rpc.getnewaddress() # start_index = 0, receive_index = 1
|
||||
_ = lianad.rpc.getnewaddress() # start_index = 0, receive_index = 2
|
||||
list4 = lianad.rpc.listaddresses()
|
||||
assert len(list4["addresses"]) == len(list3["addresses"]) + 2 == 2
|
||||
list5 = lianad.rpc.listaddresses(0)
|
||||
assert list4 == list5
|
||||
|
||||
|
||||
def test_listcoins(lianad, bitcoind):
|
||||
# Initially empty
|
||||
res = lianad.rpc.listcoins()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user