Add descriptors to listunspent and getaddressinfo + tests
This commit is contained in:
parent
9b2a25b13f
commit
16203d5df7
2 changed files with 69 additions and 0 deletions
|
@ -20,6 +20,7 @@
|
|||
#include <rpc/rawtransaction.h>
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/util.h>
|
||||
#include <script/descriptor.h>
|
||||
#include <script/sign.h>
|
||||
#include <shutdown.h>
|
||||
#include <timedata.h>
|
||||
|
@ -2623,6 +2624,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
|||
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n"
|
||||
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
|
||||
" \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
|
||||
" \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n"
|
||||
" \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n"
|
||||
" from outside keys and unconfirmed replacement transactions are considered unsafe\n"
|
||||
" and are not eligible for spending by fundrawtransaction and sendtoaddress.\n"
|
||||
|
@ -2740,6 +2742,10 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
|||
entry.pushKV("confirmations", out.nDepth);
|
||||
entry.pushKV("spendable", out.fSpendable);
|
||||
entry.pushKV("solvable", out.fSolvable);
|
||||
if (out.fSolvable) {
|
||||
auto descriptor = InferDescriptor(scriptPubKey, *pwallet);
|
||||
entry.pushKV("desc", descriptor->ToString());
|
||||
}
|
||||
entry.pushKV("safe", out.fSafe);
|
||||
results.push_back(entry);
|
||||
}
|
||||
|
@ -3456,6 +3462,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
|||
" \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n"
|
||||
" \"ismine\" : true|false, (boolean) If the address is yours or not\n"
|
||||
" \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
|
||||
" \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n"
|
||||
" \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n"
|
||||
" \"isscript\" : true|false, (boolean) If the key is a script\n"
|
||||
" \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
|
||||
" \"witness_version\" : version (numeric, optional) The version number of the witness program\n"
|
||||
|
@ -3508,6 +3516,11 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
|||
|
||||
isminetype mine = IsMine(*pwallet, dest);
|
||||
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
|
||||
bool solvable = IsSolvable(*pwallet, scriptPubKey);
|
||||
ret.pushKV("solvable", solvable);
|
||||
if (solvable) {
|
||||
ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString());
|
||||
}
|
||||
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
|
||||
UniValue detail = DescribeWalletAddress(pwallet, dest);
|
||||
ret.pushKVs(detail);
|
||||
|
|
|
@ -99,6 +99,8 @@ class AddressTypeTest(BitcoinTestFramework):
|
|||
"""Run sanity checks on an address."""
|
||||
info = self.nodes[node].getaddressinfo(address)
|
||||
assert(self.nodes[node].validateaddress(address)['isvalid'])
|
||||
assert_equal(info.get('solvable'), True)
|
||||
|
||||
if not multisig and typ == 'legacy':
|
||||
# P2PKH
|
||||
assert(not info['isscript'])
|
||||
|
@ -146,6 +148,47 @@ class AddressTypeTest(BitcoinTestFramework):
|
|||
# Unknown type
|
||||
assert(False)
|
||||
|
||||
def test_desc(self, node, address, multisig, typ, utxo):
|
||||
"""Run sanity checks on a descriptor reported by getaddressinfo."""
|
||||
info = self.nodes[node].getaddressinfo(address)
|
||||
assert('desc' in info)
|
||||
assert_equal(info['desc'], utxo['desc'])
|
||||
assert(self.nodes[node].validateaddress(address)['isvalid'])
|
||||
|
||||
# Use a ridiculously roundabout way to find the key origin info through
|
||||
# the PSBT logic. However, this does test consistency between the PSBT reported
|
||||
# fingerprints/paths and the descriptor logic.
|
||||
psbt = self.nodes[node].createpsbt([{'txid':utxo['txid'], 'vout':utxo['vout']}],[{address:0.00010000}])
|
||||
psbt = self.nodes[node].walletprocesspsbt(psbt, False, "ALL", True)
|
||||
decode = self.nodes[node].decodepsbt(psbt['psbt'])
|
||||
key_descs = {}
|
||||
for deriv in decode['inputs'][0]['bip32_derivs']:
|
||||
assert_equal(len(deriv['master_fingerprint']), 8)
|
||||
assert_equal(deriv['path'][0], 'm')
|
||||
key_descs[deriv['pubkey']] = '[' + deriv['master_fingerprint'] + deriv['path'][1:] + ']' + deriv['pubkey']
|
||||
|
||||
if not multisig and typ == 'legacy':
|
||||
# P2PKH
|
||||
assert_equal(info['desc'], "pkh(%s)" % key_descs[info['pubkey']])
|
||||
elif not multisig and typ == 'p2sh-segwit':
|
||||
# P2SH-P2WPKH
|
||||
assert_equal(info['desc'], "sh(wpkh(%s))" % key_descs[info['pubkey']])
|
||||
elif not multisig and typ == 'bech32':
|
||||
# P2WPKH
|
||||
assert_equal(info['desc'], "wpkh(%s)" % key_descs[info['pubkey']])
|
||||
elif typ == 'legacy':
|
||||
# P2SH-multisig
|
||||
assert_equal(info['desc'], "sh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]]))
|
||||
elif typ == 'p2sh-segwit':
|
||||
# P2SH-P2WSH-multisig
|
||||
assert_equal(info['desc'], "sh(wsh(multi(2,%s,%s)))" % (key_descs[info['embedded']['pubkeys'][0]], key_descs[info['embedded']['pubkeys'][1]]))
|
||||
elif typ == 'bech32':
|
||||
# P2WSH-multisig
|
||||
assert_equal(info['desc'], "wsh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]]))
|
||||
else:
|
||||
# Unknown type
|
||||
assert(False)
|
||||
|
||||
def test_change_output_type(self, node_sender, destinations, expected_type):
|
||||
txid = self.nodes[node_sender].sendmany(dummy="", amounts=dict.fromkeys(destinations, 0.001))
|
||||
raw_tx = self.nodes[node_sender].getrawtransaction(txid)
|
||||
|
@ -198,6 +241,7 @@ class AddressTypeTest(BitcoinTestFramework):
|
|||
self.log.debug("Old balances are {}".format(old_balances))
|
||||
to_send = (old_balances[from_node] / 101).quantize(Decimal("0.00000001"))
|
||||
sends = {}
|
||||
addresses = {}
|
||||
|
||||
self.log.debug("Prepare sends")
|
||||
for n, to_node in enumerate(range(from_node, from_node + 4)):
|
||||
|
@ -228,6 +272,7 @@ class AddressTypeTest(BitcoinTestFramework):
|
|||
|
||||
# Output entry
|
||||
sends[address] = to_send * 10 * (1 + n)
|
||||
addresses[to_node] = (address, typ)
|
||||
|
||||
self.log.debug("Sending: {}".format(sends))
|
||||
self.nodes[from_node].sendmany("", sends)
|
||||
|
@ -244,6 +289,17 @@ class AddressTypeTest(BitcoinTestFramework):
|
|||
self.nodes[5].generate(1)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
# Verify that the receiving wallet contains a UTXO with the expected address, and expected descriptor
|
||||
for n, to_node in enumerate(range(from_node, from_node + 4)):
|
||||
to_node %= 4
|
||||
found = False
|
||||
for utxo in self.nodes[to_node].listunspent():
|
||||
if utxo['address'] == addresses[to_node][0]:
|
||||
found = True
|
||||
self.test_desc(to_node, addresses[to_node][0], multisig, addresses[to_node][1], utxo)
|
||||
break
|
||||
assert found
|
||||
|
||||
new_balances = self.get_balances()
|
||||
self.log.debug("Check new balances: {}".format(new_balances))
|
||||
# We don't know what fee was set, so we can only check bounds on the balance of the sending node
|
||||
|
|
Loading…
Reference in a new issue