Skip to content

Commit d8a6662

Browse files
committed
Merge #17283: rpc: improve getaddressinfo test coverage, help, code docs
33f5fc3 test: add rpc getaddressinfo labels test coverage (Jon Atack) 0f3539a test: add listlabels test in wallet_labels.py (Jon Atack) 1388de8 rpc: add getaddressinfo code documentation (Jon Atack) 2ee0cb3 rpc: update getaddressinfo RPCExamples to bech32 (Jon Atack) 8d1ed0c rpc: clarify label vs labels in getaddressinfo RPCHelpman (Jon Atack) 5a0ed85 rpc: improve getaddressinfo RPCHelpman content (Jon Atack) 70cda34 rpc: improve getaddressinfo RPCHelpman formatting (Jon Atack) Pull request description: This PR is a continuation of the work in bitcoin/bitcoin#12892. Main motivations: - There is currently no test coverage for the getaddressinfo `labels` response. Coverage here is a prerequisite before deprecating the `label` response or adding multiple labels per address. - `bitcoin-cli help getaddressinfo` returns a few content errors, difficult-to-read formatting, and no explanation why it returns both `label` and `labels` and how they relate, which can be confusing for application developers. Changes by order of commits: - [x] improve/fix getaddressinfo RPCHelpman layout formatting - [x] improve/fix getaddressinfo RPCHelpman content - [x] clarify the `label` and `labels` fields in getaddressinfo RPCHelpman - [x] update getaddressinfo RPCExamples addresses to bech32 - [x] add getaddressinfo code docs - [x] add a `listlabels` test assertion in wallet_labels.py - [x] add missing getaddressinfo `labels` test coverage and improve the existing `label` tests Here are gists of the CLI help output: [`bitcoin-cli help getaddressinfo` before this PR](https://gist.github.com/jonatack/022af5221a85c069780359a22643c810) [`bitcoin-cli help getaddressinfo` after this PR](https://gist.github.com/jonatack/4ee5f6abc62a3d99269570206a5f90ba) It seems we ought to begin a deprecation process for the getaddressinfo `label` field? If yes, I have a follow-up ready. _--> EDIT: Deprecation follow-ups #17578 and #17585 now build on this PR._ ACKs for top commit: fjahr: Re-ACK 33f5fc3 jnewbery: ACK 33f5fc3. Tree-SHA512: a001aa863090ec2566a31059477945b1c303ebeb430b33472f8b150e420fa5742fc33bca9d95571746395b607f43f6078dd5b53e238ac1f3fc648b51c8f79a07
2 parents 4fb82e9 + 33f5fc3 commit d8a6662

File tree

7 files changed

+118
-60
lines changed

7 files changed

+118
-60
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
951951
}
952952

953953
RPCHelpMan{"addmultisigaddress",
954-
"\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
954+
"\nAdd an nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
955955
"Each key is a Bitcoin address or hex-encoded public key.\n"
956956
"This functionality is only intended for use with non-watchonly addresses.\n"
957957
"See `importaddress` for watchonly p2sh address support.\n"
@@ -3709,52 +3709,57 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
37093709
}
37103710

37113711
RPCHelpMan{"getaddressinfo",
3712-
"\nReturn information about the given bitcoin address. Some information requires the address\n"
3713-
"to be in the wallet.\n",
3712+
"\nReturn information about the given bitcoin address.\n"
3713+
"Some of the information will only be present if the address is in the active wallet.\n",
37143714
{
3715-
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to get the information of."},
3715+
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for which to get information."},
37163716
},
37173717
RPCResult{
37183718
"{\n"
3719-
" \"address\" : \"address\", (string) The bitcoin address validated\n"
3720-
" \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n"
3721-
" \"ismine\" : true|false, (boolean) If the address is yours or not\n"
3722-
" \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
3723-
" \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n"
3724-
" \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n"
3725-
" \"isscript\" : true|false, (boolean) If the key is a script\n"
3726-
" \"ischange\" : true|false, (boolean) If the address was used for change output\n"
3727-
" \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
3728-
" \"witness_version\" : version (numeric, optional) The version number of the witness program\n"
3729-
" \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n"
3730-
" \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n"
3731-
" \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n"
3732-
" \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n"
3719+
" \"address\" : \"address\", (string) The bitcoin address validated.\n"
3720+
" \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address.\n"
3721+
" \"ismine\" : true|false, (boolean) If the address is yours.\n"
3722+
" \"iswatchonly\" : true|false, (boolean) If the address is watchonly.\n"
3723+
" \"solvable\" : true|false, (boolean) If we know how to spend coins sent to this address, ignoring the possible lack of private keys.\n"
3724+
" \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable).\n"
3725+
" \"isscript\" : true|false, (boolean) If the key is a script.\n"
3726+
" \"ischange\" : true|false, (boolean) If the address was used for change output.\n"
3727+
" \"iswitness\" : true|false, (boolean) If the address is a witness address.\n"
3728+
" \"witness_version\" : version (numeric, optional) The version number of the witness program.\n"
3729+
" \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program.\n"
3730+
" \"script\" : \"type\" (string, optional) The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
3731+
" types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
3732+
" witness_v0_scripthash, witness_unknown.\n"
3733+
" \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address.\n"
3734+
" \"pubkeys\" (array, optional) Array of pubkeys associated with the known redeemscript (only if script is multisig).\n"
37333735
" [\n"
3734-
" \"pubkey\"\n"
3736+
" \"pubkey\" (string)\n"
37353737
" ,...\n"
37363738
" ]\n"
3737-
" \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n"
3738-
" \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n"
3739-
" \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n"
3740-
" \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n"
3741-
" \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n"
3742-
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
3743-
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
3744-
" \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n"
3745-
" \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n"
3746-
" \"labels\" (object) Array of labels associated with the address.\n"
3739+
" \"sigsrequired\" : xxxxx (numeric, optional) The number of signatures required to spend multisig output (only if script is multisig).\n"
3740+
" \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH).\n"
3741+
" \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. Includes all\n"
3742+
" getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath,\n"
3743+
" hdseedid) and relation to the wallet (ismine, iswatchonly).\n"
3744+
" \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed.\n"
3745+
" \"label\" : \"label\" (string) The label associated with the address. Defaults to \"\". Equivalent to the name field in the labels array.\n"
3746+
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available, expressed in seconds since Epoch Time (Jan 1 1970 GMT).\n"
3747+
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath, if the key is HD and available.\n"
3748+
" \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed.\n"
3749+
" \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingerprint of the master key.\n"
3750+
" \"labels\" (object) An array of labels associated with the address. Currently limited to one label but returned\n"
3751+
" as an array to keep the API stable if multiple labels are enabled in the future.\n"
37473752
" [\n"
37483753
" { (json object of label data)\n"
3749-
" \"name\": \"labelname\" (string) The label\n"
3750-
" \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
3754+
" \"name\": \"label name\" (string) The label name. Defaults to \"\". Equivalent to the label field above.\n"
3755+
" \"purpose\": \"purpose\" (string) The purpose of the associated address (send or receive).\n"
37513756
" },...\n"
37523757
" ]\n"
37533758
"}\n"
37543759
},
37553760
RPCExamples{
3756-
HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
3757-
+ HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
3761+
HelpExampleCli("getaddressinfo", "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"") +
3762+
HelpExampleRpc("getaddressinfo", "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"")
37583763
},
37593764
}.Check(request);
37603765

@@ -3773,23 +3778,39 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
37733778

37743779
CScript scriptPubKey = GetScriptForDestination(dest);
37753780
ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
3781+
37763782
const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey);
37773783

37783784
isminetype mine = pwallet->IsMine(dest);
37793785
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
3786+
37803787
bool solvable = provider && IsSolvable(*provider, scriptPubKey);
37813788
ret.pushKV("solvable", solvable);
3789+
37823790
if (solvable) {
37833791
ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString());
37843792
}
3793+
37853794
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
3795+
3796+
// Return DescribeWalletAddress fields.
3797+
// Always returned: isscript, ischange, iswitness.
3798+
// Optional: witness_version, witness_program, script, hex, pubkeys (array),
3799+
// sigsrequired, pubkey, embedded, iscompressed.
37863800
UniValue detail = DescribeWalletAddress(pwallet, dest);
37873801
ret.pushKVs(detail);
3802+
3803+
// Return label field if existing. Currently only one label can be
3804+
// associated with an address, so the label should be equivalent to the
3805+
// value of the name key/value pair in the labels hash array below.
37883806
if (pwallet->mapAddressBook.count(dest)) {
37893807
ret.pushKV("label", pwallet->mapAddressBook[dest].name);
37903808
}
3809+
37913810
ret.pushKV("ischange", pwallet->IsChange(scriptPubKey));
37923811

3812+
// Fetch KeyMetadata, if present, for the timestamp, hdkeypath, hdseedid,
3813+
// and hdmasterfingerprint fields.
37933814
ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey);
37943815
if (spk_man) {
37953816
if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) {
@@ -3802,9 +3823,11 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
38023823
}
38033824
}
38043825

3805-
// Currently only one label can be associated with an address, return an array
3806-
// so the API remains stable if we allow multiple labels to be associated with
3807-
// an address.
3826+
// Return a labels array containing a hash of key/value pairs for the label
3827+
// name and address purpose. The name value is equivalent to the label field
3828+
// above. Currently only one label can be associated with an address, but we
3829+
// return an array so the API remains stable if we allow multiple labels to
3830+
// be associated with an address in the future.
38083831
UniValue labels(UniValue::VARR);
38093832
std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest);
38103833
if (mi != pwallet->mapAddressBook.end()) {

test/functional/test_framework/wallet_util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ def get_multisig(node):
8888
p2sh_p2wsh_script=CScript([OP_HASH160, witness_script, OP_EQUAL]).hex(),
8989
p2sh_p2wsh_addr=script_to_p2sh_p2wsh(script_code))
9090

91+
def labels_value(name="", purpose="receive"):
92+
"""Generate a getaddressinfo labels array from a name and purpose.
93+
Often used as the value of a labels kwarg for calling test_address below."""
94+
return [{"name": name, "purpose": purpose}]
95+
9196
def test_address(node, address, **kwargs):
9297
"""Get address info for `address` and test whether the returned values are as expected."""
9398
addr_info = node.getaddressinfo(address)

test/functional/wallet_basic.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
connect_nodes,
1616
wait_until,
1717
)
18+
from test_framework.wallet_util import (
19+
labels_value,
20+
test_address,
21+
)
1822

1923

2024
class WalletTest(BitcoinTestFramework):
@@ -390,7 +394,7 @@ def run_test(self):
390394
for label in [u'рыба', u'𝅘𝅥𝅯']:
391395
addr = self.nodes[0].getnewaddress()
392396
self.nodes[0].setlabel(addr, label)
393-
assert_equal(self.nodes[0].getaddressinfo(addr)['label'], label)
397+
test_address(self.nodes[0], addr, label=label, labels=labels_value(name=label))
394398
assert label in self.nodes[0].listlabels()
395399
self.nodes[0].rpc.ensure_ascii = True # restore to default
396400

test/functional/wallet_import_with_label.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
"""
1212

1313
from test_framework.test_framework import BitcoinTestFramework
14-
from test_framework.wallet_util import test_address
14+
from test_framework.wallet_util import (
15+
labels_value,
16+
test_address,
17+
)
1518

1619

1720
class ImportWithLabel(BitcoinTestFramework):
@@ -36,7 +39,8 @@ def run_test(self):
3639
address,
3740
iswatchonly=True,
3841
ismine=False,
39-
label=label)
42+
label=label,
43+
labels=labels_value(name=label))
4044

4145
self.log.info(
4246
"Import the watch-only address's private key without a "
@@ -47,7 +51,8 @@ def run_test(self):
4751

4852
test_address(self.nodes[1],
4953
address,
50-
label=label)
54+
label=label,
55+
labels=labels_value(name=label))
5156

5257
self.log.info(
5358
"Test importaddress without label and importprivkey with label."
@@ -59,7 +64,8 @@ def run_test(self):
5964
address2,
6065
iswatchonly=True,
6166
ismine=False,
62-
label="")
67+
label="",
68+
labels=labels_value())
6369

6470
self.log.info(
6571
"Import the watch-only address's private key with a "
@@ -71,7 +77,8 @@ def run_test(self):
7177

7278
test_address(self.nodes[1],
7379
address2,
74-
label=label2)
80+
label=label2,
81+
labels=labels_value(name=label2))
7582

7683
self.log.info("Test importaddress with label and importprivkey with label.")
7784
self.log.info("Import a watch-only address with a label.")
@@ -82,7 +89,8 @@ def run_test(self):
8289
address3,
8390
iswatchonly=True,
8491
ismine=False,
85-
label=label3_addr)
92+
label=label3_addr,
93+
labels=labels_value(name=label3_addr))
8694

8795
self.log.info(
8896
"Import the watch-only address's private key with a "
@@ -94,7 +102,8 @@ def run_test(self):
94102

95103
test_address(self.nodes[1],
96104
address3,
97-
label=label3_priv)
105+
label=label3_priv,
106+
labels=labels_value(name=label3_priv))
98107

99108
self.log.info(
100109
"Test importprivkey won't label new dests with the same "
@@ -109,6 +118,7 @@ def run_test(self):
109118
iswatchonly=True,
110119
ismine=False,
111120
label=label4_addr,
121+
labels=labels_value(name=label4_addr),
112122
embedded=None)
113123

114124
self.log.info(
@@ -123,10 +133,13 @@ def run_test(self):
123133

124134
test_address(self.nodes[1],
125135
embedded_addr,
126-
label="")
136+
label="",
137+
labels=labels_value())
138+
127139
test_address(self.nodes[1],
128140
address4,
129-
label=label4_addr)
141+
label=label4_addr,
142+
labels=labels_value(name=label4_addr))
130143

131144
self.stop_nodes()
132145

0 commit comments

Comments
 (0)