Skip to content

Commit 647b81b

Browse files
committed
wallet, rpc: add listdescriptors command
1 parent f1f26b8 commit 647b81b

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

src/wallet/rpcdump.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,3 +1740,72 @@ RPCHelpMan importdescriptors()
17401740
},
17411741
};
17421742
}
1743+
1744+
RPCHelpMan listdescriptors()
1745+
{
1746+
return RPCHelpMan{
1747+
"listdescriptors",
1748+
"\nList descriptors imported into a descriptor-enabled wallet.",
1749+
{},
1750+
RPCResult{
1751+
RPCResult::Type::ARR, "", "Response is an array of descriptor objects",
1752+
{
1753+
{RPCResult::Type::OBJ, "", "", {
1754+
{RPCResult::Type::STR, "desc", "Descriptor string representation"},
1755+
{RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
1756+
{RPCResult::Type::BOOL, "active", "Activeness flag"},
1757+
{RPCResult::Type::BOOL, "internal", true, "Whether this is internal or external descriptor; defined only for active descriptors"},
1758+
{RPCResult::Type::ARR_FIXED, "range", true, "Defined only for ranged descriptors", {
1759+
{RPCResult::Type::NUM, "", "Range start inclusive"},
1760+
{RPCResult::Type::NUM, "", "Range end inclusive"},
1761+
}},
1762+
{RPCResult::Type::NUM, "next", true, "The next index to generate addresses from; defined only for ranged descriptors"},
1763+
}},
1764+
}
1765+
},
1766+
RPCExamples{
1767+
HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
1768+
},
1769+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1770+
{
1771+
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
1772+
if (!wallet) return NullUniValue;
1773+
1774+
if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
1775+
throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
1776+
}
1777+
1778+
LOCK(wallet->cs_wallet);
1779+
1780+
UniValue response(UniValue::VARR);
1781+
const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();
1782+
for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
1783+
const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
1784+
if (!desc_spk_man) {
1785+
throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
1786+
}
1787+
UniValue spk(UniValue::VOBJ);
1788+
LOCK(desc_spk_man->cs_desc_man);
1789+
const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
1790+
spk.pushKV("desc", wallet_descriptor.descriptor->ToString());
1791+
spk.pushKV("timestamp", wallet_descriptor.creation_time);
1792+
const bool active = active_spk_mans.count(desc_spk_man) != 0;
1793+
spk.pushKV("active", active);
1794+
const auto& type = wallet_descriptor.descriptor->GetOutputType();
1795+
if (active && type != nullopt) {
1796+
spk.pushKV("internal", wallet->GetScriptPubKeyMan(*type, true) == desc_spk_man);
1797+
}
1798+
if (wallet_descriptor.descriptor->IsRange()) {
1799+
UniValue range(UniValue::VARR);
1800+
range.push_back(wallet_descriptor.range_start);
1801+
range.push_back(wallet_descriptor.range_end - 1);
1802+
spk.pushKV("range", range);
1803+
spk.pushKV("next", wallet_descriptor.next_index);
1804+
}
1805+
response.push_back(spk);
1806+
}
1807+
1808+
return response;
1809+
},
1810+
};
1811+
}

src/wallet/rpcwallet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4537,6 +4537,7 @@ RPCHelpMan importprunedfunds();
45374537
RPCHelpMan removeprunedfunds();
45384538
RPCHelpMan importmulti();
45394539
RPCHelpMan importdescriptors();
4540+
RPCHelpMan listdescriptors();
45404541

45414542
Span<const CRPCCommand> GetWalletRPCCommands()
45424543
{
@@ -4575,6 +4576,7 @@ static const CRPCCommand commands[] =
45754576
{ "wallet", "importwallet", &importwallet, {"filename"} },
45764577
{ "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} },
45774578
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} },
4579+
{ "wallet", "listdescriptors", &listdescriptors, {} },
45784580
{ "wallet", "listlabels", &listlabels, {"purpose"} },
45794581
{ "wallet", "listlockunspent", &listlockunspent, {} },
45804582
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@
237237
'rpc_named_arguments.py',
238238
'wallet_listsinceblock.py',
239239
'wallet_listsinceblock.py --descriptors',
240+
'wallet_listdescriptors.py --descriptors',
240241
'p2p_leak.py',
241242
'wallet_encryption.py',
242243
'wallet_encryption.py --descriptors',
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2014-2021 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test the listdescriptors RPC."""
6+
7+
from test_framework.descriptors import (
8+
descsum_create
9+
)
10+
from test_framework.test_framework import BitcoinTestFramework
11+
from test_framework.util import (
12+
assert_equal,
13+
assert_raises_rpc_error,
14+
)
15+
16+
17+
class ListDescriptorsTest(BitcoinTestFramework):
18+
def set_test_params(self):
19+
self.num_nodes = 1
20+
21+
def skip_test_if_missing_module(self):
22+
self.skip_if_no_wallet()
23+
self.skip_if_no_sqlite()
24+
25+
# do not create any wallet by default
26+
def init_wallet(self, i):
27+
return
28+
29+
def run_test(self):
30+
node = self.nodes[0]
31+
assert_raises_rpc_error(-18, 'No wallet is loaded.', node.listdescriptors)
32+
33+
self.log.info('Test that the command is not available for legacy wallets.')
34+
node.createwallet(wallet_name='w1', descriptors=False)
35+
assert_raises_rpc_error(-4, 'listdescriptors is not available for non-descriptor wallets', node.listdescriptors)
36+
37+
self.log.info('Test the command for empty descriptors wallet.')
38+
node.createwallet(wallet_name='w2', blank=True, descriptors=True)
39+
assert_equal(0, len(node.get_wallet_rpc('w2').listdescriptors()))
40+
41+
self.log.info('Test the command for a default descriptors wallet.')
42+
node.createwallet(wallet_name='w3', descriptors=True)
43+
result = node.get_wallet_rpc('w3').listdescriptors()
44+
assert_equal(6, len(result))
45+
assert_equal(6, len([d for d in result if d['active']]))
46+
assert_equal(3, len([d for d in result if d['internal']]))
47+
for item in result:
48+
assert item['desc'] != ''
49+
assert item['next'] == 0
50+
assert item['range'] == [0, 0]
51+
assert item['timestamp'] is not None
52+
53+
self.log.info('Test non-active non-range combo descriptor')
54+
node.createwallet(wallet_name='w4', blank=True, descriptors=True)
55+
wallet = node.get_wallet_rpc('w4')
56+
wallet.importdescriptors([{
57+
'desc': descsum_create('combo(' + node.get_deterministic_priv_key().key + ')'),
58+
'timestamp': 1296688602,
59+
}])
60+
expected = [{'active': False,
61+
'desc': 'combo(0227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3f)#np574htj',
62+
'timestamp': 1296688602}]
63+
assert_equal(expected, wallet.listdescriptors())
64+
65+
66+
if __name__ == '__main__':
67+
ListDescriptorsTest().main()

0 commit comments

Comments
 (0)