feat: add rpc command to list revealed addresses
This commit is contained in:
parent
4945dba7c0
commit
3d6a22ea05
35
doc/API.md
35
doc/API.md
@ -12,6 +12,7 @@ Commands must be sent as valid JSONRPC 2.0 requests, ending with a `\n`.
|
||||
| [`updatederivationindexes`](#updatederivationindexes) | Update last generated addresses derivation indexes |
|
||||
| [`getnewaddress`](#getnewaddress) | Get a new receiving address |
|
||||
| [`listaddresses`](#listaddresses) | List addresses given start_index and count |
|
||||
| [`listrevealedaddresses`](#listrevealedaddresses) | List revealed addresses (both used and unused) |
|
||||
| [`listcoins`](#listcoins) | List all wallet transaction outputs. |
|
||||
| [`createspend`](#createspend) | Create a new Spend transaction |
|
||||
| [`updatespend`](#updatespend) | Store a created Spend transaction |
|
||||
@ -140,6 +141,40 @@ If no value is passed for `count` the maximum generated index between receive an
|
||||
| `change` | string | Change address |
|
||||
|
||||
|
||||
### `listrevealedaddresses`
|
||||
|
||||
List revealed receive or change addresses, optionally filtering for those that are unused by any of the current coins in the wallet.
|
||||
|
||||
Addresses are returned in order of descending derivation index.
|
||||
|
||||
If `start_index` is omitted or `null`, then addresses will be returned starting from the last revealed address.
|
||||
Otherwise, addresses will be returned starting from the specified derivation index.
|
||||
|
||||
#### Request
|
||||
|
||||
| Field | Type | Description |
|
||||
| --------------- | ----------------- | --------------------------------------------------------------------------------------- |
|
||||
| `is_change` | bool | Whether to return change or otherwise receive addresses. |
|
||||
| `exclude_used` | bool | Whether to exclude those addresses that have been used by a current coin in the wallet. |
|
||||
| `limit` | integer | The maximum number of addresses to list. |
|
||||
| `start_index` | integer(optional) | For pagination, pass the `continue_from` value from the previous response. |
|
||||
|
||||
#### Response
|
||||
|
||||
The response contains two fields:
|
||||
- `addresses`: an array of revealed addresses, with the structure given below.
|
||||
- `continue_from`: used for pagination of results. If not `null`, this indicates that there may be additional addresses that can be listed and
|
||||
this value can be passed to the next request as `start_index` to continue with the next page of results.
|
||||
|
||||
Each element in the `addresses` array has the following fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | ---------------- | ---------------------------------------------------------------------------- |
|
||||
| `index` | integer | Derivation index. |
|
||||
| `address` | string | Address. |
|
||||
| `used_count` | integer | The number of current coins in the wallet that are using this address. |
|
||||
| `label` | string or null | Address label, if any. |
|
||||
|
||||
### `listcoins`
|
||||
|
||||
List all our transaction outputs, optionally filtered by status and/or outpoint.
|
||||
|
||||
@ -199,6 +199,50 @@ fn list_addresses(
|
||||
Ok(serde_json::json!(&res))
|
||||
}
|
||||
|
||||
fn list_revealed_addresses(
|
||||
control: &DaemonControl,
|
||||
params: Params,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let is_change = params
|
||||
.get(0, "is_change")
|
||||
.ok_or_else(|| Error::invalid_params("Missing 'is_change' parameter."))?
|
||||
.as_bool()
|
||||
.ok_or_else(|| Error::invalid_params("Invalid 'is_change' parameter."))?;
|
||||
let exclude_used = params
|
||||
.get(1, "exclude_used")
|
||||
.ok_or_else(|| Error::invalid_params("Missing 'exclude_used' parameter."))?
|
||||
.as_bool()
|
||||
.ok_or_else(|| Error::invalid_params("Invalid 'exclude_used' parameter."))?;
|
||||
let limit = params
|
||||
.get(2, "limit")
|
||||
.ok_or_else(|| Error::invalid_params("Missing 'limit' parameter."))?
|
||||
.as_u64()
|
||||
.and_then(|l| l.try_into().ok())
|
||||
.ok_or_else(|| Error::invalid_params("Invalid 'limit' parameter."))?;
|
||||
// A missing value and `null` are both mapped to `None`.
|
||||
let start_index = if let Some(ind) = params.get(3, "start_index") {
|
||||
if ind.as_null().is_some() {
|
||||
None
|
||||
} else {
|
||||
let ind_u32: u32 = ind
|
||||
.as_u64()
|
||||
.and_then(|ind_u64| ind_u64.try_into().ok())
|
||||
.ok_or_else(|| Error::invalid_params("Invalid 'start_index' parameter."))?;
|
||||
Some(ind_u32)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let res = &control.list_revealed_addresses(
|
||||
is_change,
|
||||
exclude_used,
|
||||
limit,
|
||||
start_index.map(|ind| ind.into()),
|
||||
)?;
|
||||
Ok(serde_json::json!(&res))
|
||||
}
|
||||
|
||||
fn update_deriv_indexes(
|
||||
control: &DaemonControl,
|
||||
params: Params,
|
||||
@ -493,6 +537,14 @@ pub fn handle_request(control: &mut DaemonControl, req: Request) -> Result<Respo
|
||||
let params = req.params;
|
||||
list_addresses(control, params)?
|
||||
}
|
||||
"listrevealedaddresses" => {
|
||||
let params = req.params.ok_or_else(|| {
|
||||
Error::invalid_params(
|
||||
"The 'listrevealedaddresses' command requires 3 parameters: 'is_change', 'exclude_used' and 'limit'",
|
||||
)
|
||||
})?;
|
||||
list_revealed_addresses(control, params)?
|
||||
}
|
||||
"listconfirmed" => {
|
||||
let params = req.params.ok_or_else(|| {
|
||||
Error::invalid_params(
|
||||
|
||||
@ -201,6 +201,234 @@ def test_listaddresses(lianad):
|
||||
lianad.rpc.listaddresses(0, "blb")
|
||||
|
||||
|
||||
def test_listrevealedaddresses(lianad, bitcoind):
|
||||
|
||||
# Get addresses for reference:
|
||||
addresses = lianad.rpc.listaddresses(0, 10)["addresses"]
|
||||
|
||||
# We start with index 0 already "revealed":
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 10)
|
||||
assert list_rec["continue_from"] is None # there are no more addresses to list
|
||||
assert len(list_rec["addresses"]) == 1
|
||||
assert list_rec["addresses"][0]["index"] == 0
|
||||
assert list_rec["addresses"][0]["address"] == addresses[0]["receive"]
|
||||
assert list_rec["addresses"][0]["used_count"] == 0
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
|
||||
# Generate some addresses.
|
||||
addr_1 = lianad.rpc.getnewaddress()["address"]
|
||||
addr_2 = lianad.rpc.getnewaddress()["address"]
|
||||
addr_3 = lianad.rpc.getnewaddress()["address"]
|
||||
addr_4 = lianad.rpc.getnewaddress()["address"]
|
||||
addr_5 = lianad.rpc.getnewaddress()["address"]
|
||||
addr_6 = lianad.rpc.getnewaddress()["address"]
|
||||
addr_7 = lianad.rpc.getnewaddress()["address"]
|
||||
|
||||
# Last revealed receive index is 7.
|
||||
assert lianad.rpc.getinfo()["receive_index"] == 7
|
||||
|
||||
# Set some labels
|
||||
lianad.rpc.updatelabels(
|
||||
{addr_1: "my test label 1", addr_5: "my test label 5"},
|
||||
)
|
||||
|
||||
# Passing None or omitting start_index parameter is the same:
|
||||
assert lianad.rpc.listrevealedaddresses(
|
||||
False, False, 10
|
||||
) == lianad.rpc.listrevealedaddresses(False, False, 10, None)
|
||||
|
||||
# If we continue_from a value above our last revealed index, we'll start from the last index.
|
||||
assert lianad.rpc.listrevealedaddresses(
|
||||
False, False, 10
|
||||
) == lianad.rpc.listrevealedaddresses(False, False, 10, 100)
|
||||
|
||||
# Similarly if we start from a hardened index:
|
||||
assert lianad.rpc.listrevealedaddresses(
|
||||
False, False, 10
|
||||
) == lianad.rpc.listrevealedaddresses(False, False, 10, 4_294_967_295)
|
||||
|
||||
# Get 3 addresses starting at last revealed index:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 3)
|
||||
assert list_rec["continue_from"] == 4
|
||||
assert len(list_rec["addresses"]) == 3
|
||||
assert list_rec["addresses"][0]["index"] == 7
|
||||
assert list_rec["addresses"][0]["address"] == addr_7
|
||||
assert list_rec["addresses"][0]["used_count"] == 0
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
assert list_rec["addresses"][1]["index"] == 6
|
||||
assert list_rec["addresses"][1]["address"] == addr_6
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
assert list_rec["addresses"][2]["index"] == 5
|
||||
assert list_rec["addresses"][2]["address"] == addr_5
|
||||
assert list_rec["addresses"][2]["used_count"] == 0
|
||||
assert list_rec["addresses"][2]["label"] == "my test label 5"
|
||||
|
||||
# Get next 3 using continue_from returned above as start_index:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 3, 4)
|
||||
assert list_rec["continue_from"] == 1
|
||||
assert len(list_rec["addresses"]) == 3
|
||||
assert list_rec["addresses"][0]["index"] == 4
|
||||
assert list_rec["addresses"][0]["address"] == addr_4
|
||||
assert list_rec["addresses"][0]["used_count"] == 0
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
assert list_rec["addresses"][1]["index"] == 3
|
||||
assert list_rec["addresses"][1]["address"] == addr_3
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
assert list_rec["addresses"][2]["index"] == 2
|
||||
assert list_rec["addresses"][2]["address"] == addr_2
|
||||
assert list_rec["addresses"][2]["used_count"] == 0
|
||||
assert list_rec["addresses"][2]["label"] is None
|
||||
|
||||
# Get final page of results consisting of 2 addresses:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 3, 1)
|
||||
assert list_rec["continue_from"] is None # final page
|
||||
assert len(list_rec["addresses"]) == 2 # num addresses remaining is below limit
|
||||
assert list_rec["addresses"][0]["index"] == 1
|
||||
assert list_rec["addresses"][0]["address"] == addr_1
|
||||
assert list_rec["addresses"][0]["used_count"] == 0
|
||||
assert list_rec["addresses"][0]["label"] == "my test label 1"
|
||||
assert list_rec["addresses"][1]["index"] == 0
|
||||
assert list_rec["addresses"][1]["address"] == addresses[0]["receive"]
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
|
||||
# Receive funds at a couple of addresses.
|
||||
destinations = {
|
||||
addr_2: 0.003,
|
||||
addr_4: 0.004,
|
||||
addr_7: 0.005,
|
||||
}
|
||||
txid = bitcoind.rpc.sendmany("", destinations)
|
||||
bitcoind.generate_block(1, wait_for_mempool=txid)
|
||||
wait_for(lambda: len(lianad.rpc.listcoins(["confirmed"])["coins"]) == 3)
|
||||
|
||||
# The addresses are shown as used.
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 3)
|
||||
assert list_rec["continue_from"] == 4
|
||||
assert len(list_rec["addresses"]) == 3
|
||||
assert list_rec["addresses"][0]["index"] == 7
|
||||
assert list_rec["addresses"][0]["address"] == addr_7
|
||||
assert list_rec["addresses"][0]["used_count"] == 1
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
assert list_rec["addresses"][1]["index"] == 6
|
||||
assert list_rec["addresses"][1]["address"] == addr_6
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
assert list_rec["addresses"][2]["index"] == 5
|
||||
assert list_rec["addresses"][2]["address"] == addr_5
|
||||
assert list_rec["addresses"][2]["used_count"] == 0
|
||||
assert list_rec["addresses"][2]["label"] == "my test label 5"
|
||||
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 3, 4)
|
||||
assert list_rec["continue_from"] == 1
|
||||
assert len(list_rec["addresses"]) == 3
|
||||
assert list_rec["addresses"][0]["index"] == 4
|
||||
assert list_rec["addresses"][0]["address"] == addr_4
|
||||
assert list_rec["addresses"][0]["used_count"] == 1
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
assert list_rec["addresses"][1]["index"] == 3
|
||||
assert list_rec["addresses"][1]["address"] == addr_3
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
assert list_rec["addresses"][2]["index"] == 2
|
||||
assert list_rec["addresses"][2]["address"] == addr_2
|
||||
assert list_rec["addresses"][2]["used_count"] == 1
|
||||
assert list_rec["addresses"][2]["label"] is None
|
||||
|
||||
# We can exclude used addresses:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, True, 3)
|
||||
assert list_rec["continue_from"] == 2
|
||||
assert len(list_rec["addresses"]) == 3
|
||||
assert list_rec["addresses"][0]["index"] == 6
|
||||
assert list_rec["addresses"][0]["address"] == addr_6
|
||||
assert list_rec["addresses"][0]["used_count"] == 0
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
assert list_rec["addresses"][1]["index"] == 5
|
||||
assert list_rec["addresses"][1]["address"] == addr_5
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] == "my test label 5"
|
||||
assert list_rec["addresses"][2]["index"] == 3 # index 4 was skipped
|
||||
assert list_rec["addresses"][2]["address"] == addr_3
|
||||
assert list_rec["addresses"][2]["used_count"] == 0
|
||||
assert list_rec["addresses"][2]["label"] is None
|
||||
|
||||
# We can exclude used also if we continue from the value in the response above:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, True, 3, 2)
|
||||
assert list_rec["continue_from"] is None
|
||||
assert len(list_rec["addresses"]) == 2
|
||||
assert list_rec["addresses"][0]["index"] == 1 # index 2 was skipped
|
||||
assert list_rec["addresses"][0]["address"] == addr_1
|
||||
assert list_rec["addresses"][0]["used_count"] == 0
|
||||
assert list_rec["addresses"][0]["label"] == "my test label 1"
|
||||
assert list_rec["addresses"][1]["index"] == 0
|
||||
assert list_rec["addresses"][1]["address"] == addresses[0]["receive"]
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
|
||||
# Receive funds at some of the same addresses again.
|
||||
destinations = {
|
||||
addr_2: 0.0031,
|
||||
addr_4: 0.0041,
|
||||
}
|
||||
txid = bitcoind.rpc.sendmany("", destinations)
|
||||
bitcoind.generate_block(1, wait_for_mempool=txid)
|
||||
wait_for(lambda: len(lianad.rpc.listcoins(["confirmed"])["coins"]) == 5)
|
||||
|
||||
# One more coin to addr_2.
|
||||
destinations = {
|
||||
addr_2: 0.0032,
|
||||
}
|
||||
txid = bitcoind.rpc.sendmany("", destinations)
|
||||
bitcoind.generate_block(1, wait_for_mempool=txid)
|
||||
wait_for(lambda: len(lianad.rpc.listcoins(["confirmed"])["coins"]) == 6)
|
||||
|
||||
# The counts have updated:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 3, 4)
|
||||
assert list_rec["continue_from"] == 1
|
||||
assert len(list_rec["addresses"]) == 3
|
||||
assert list_rec["addresses"][0]["index"] == 4
|
||||
assert list_rec["addresses"][0]["address"] == addr_4
|
||||
assert list_rec["addresses"][0]["used_count"] == 2
|
||||
assert list_rec["addresses"][0]["label"] is None
|
||||
assert list_rec["addresses"][1]["index"] == 3
|
||||
assert list_rec["addresses"][1]["address"] == addr_3
|
||||
assert list_rec["addresses"][1]["used_count"] == 0
|
||||
assert list_rec["addresses"][1]["label"] is None
|
||||
assert list_rec["addresses"][2]["index"] == 2
|
||||
assert list_rec["addresses"][2]["address"] == addr_2
|
||||
assert list_rec["addresses"][2]["used_count"] == 3
|
||||
assert list_rec["addresses"][2]["label"] is None
|
||||
|
||||
# If we request limit 0, we get empty list:
|
||||
list_rec = lianad.rpc.listrevealedaddresses(False, False, 0)
|
||||
assert list_rec["continue_from"] == 7 # same as starting index
|
||||
assert len(list_rec["addresses"]) == 0
|
||||
|
||||
# The poller currently sets the change index to match the receive index.
|
||||
# See https://github.com/wizardsardine/liana/issues/1333.
|
||||
assert lianad.rpc.getinfo()["receive_index"] == 7
|
||||
assert lianad.rpc.getinfo()["change_index"] == 7
|
||||
|
||||
# We can get change addresses:
|
||||
list_cha = lianad.rpc.listrevealedaddresses(True, False, 3)
|
||||
assert list_cha["continue_from"] == 4
|
||||
assert len(list_cha["addresses"]) == 3
|
||||
assert list_cha["addresses"][0]["index"] == 7
|
||||
assert list_cha["addresses"][0]["address"] == addresses[7]["change"]
|
||||
assert list_cha["addresses"][0]["used_count"] == 0
|
||||
assert list_cha["addresses"][0]["label"] is None
|
||||
assert list_cha["addresses"][1]["index"] == 6
|
||||
assert list_cha["addresses"][1]["address"] == addresses[6]["change"]
|
||||
assert list_cha["addresses"][1]["used_count"] == 0
|
||||
assert list_cha["addresses"][1]["label"] is None
|
||||
assert list_cha["addresses"][2]["index"] == 5
|
||||
assert list_cha["addresses"][2]["address"] == addresses[5]["change"]
|
||||
assert list_cha["addresses"][2]["used_count"] == 0
|
||||
assert list_cha["addresses"][2]["label"] is None
|
||||
|
||||
|
||||
def test_listcoins(lianad, bitcoind):
|
||||
# Initially empty
|
||||
res = lianad.rpc.listcoins()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user