diff options
author | Ivan Metlushko <metlushko@gmail.com> | 2020-10-09 14:24:20 +0700 |
---|---|---|
committer | Ivan Metlushko <metlushko@gmail.com> | 2021-01-27 21:22:13 +0100 |
commit | 647b81b70938dc4dbcf32399c56f78be395c721a (patch) | |
tree | 3098f9a6da9bef19aa45e6865f7bc7ba5b20214e | |
parent | f1f26b8d5baec4a45a3a9ba0440cd4eff7af8407 (diff) |
wallet, rpc: add listdescriptors command
-rw-r--r-- | src/wallet/rpcdump.cpp | 69 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 2 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rwxr-xr-x | test/functional/wallet_listdescriptors.py | 67 |
4 files changed, 139 insertions, 0 deletions
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6b46868d10..8505ddc309 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1740,3 +1740,72 @@ RPCHelpMan importdescriptors() }, }; } + +RPCHelpMan listdescriptors() +{ + return RPCHelpMan{ + "listdescriptors", + "\nList descriptors imported into a descriptor-enabled wallet.", + {}, + RPCResult{ + RPCResult::Type::ARR, "", "Response is an array of descriptor objects", + { + {RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "desc", "Descriptor string representation"}, + {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, + {RPCResult::Type::BOOL, "active", "Activeness flag"}, + {RPCResult::Type::BOOL, "internal", true, "Whether this is internal or external descriptor; defined only for active descriptors"}, + {RPCResult::Type::ARR_FIXED, "range", true, "Defined only for ranged descriptors", { + {RPCResult::Type::NUM, "", "Range start inclusive"}, + {RPCResult::Type::NUM, "", "Range end inclusive"}, + }}, + {RPCResult::Type::NUM, "next", true, "The next index to generate addresses from; defined only for ranged descriptors"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + if (!wallet) return NullUniValue; + + if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { + throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets"); + } + + LOCK(wallet->cs_wallet); + + UniValue response(UniValue::VARR); + const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans(); + for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) { + const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man); + if (!desc_spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type."); + } + UniValue spk(UniValue::VOBJ); + LOCK(desc_spk_man->cs_desc_man); + const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); + spk.pushKV("desc", wallet_descriptor.descriptor->ToString()); + spk.pushKV("timestamp", wallet_descriptor.creation_time); + const bool active = active_spk_mans.count(desc_spk_man) != 0; + spk.pushKV("active", active); + const auto& type = wallet_descriptor.descriptor->GetOutputType(); + if (active && type != nullopt) { + spk.pushKV("internal", wallet->GetScriptPubKeyMan(*type, true) == desc_spk_man); + } + if (wallet_descriptor.descriptor->IsRange()) { + UniValue range(UniValue::VARR); + range.push_back(wallet_descriptor.range_start); + range.push_back(wallet_descriptor.range_end - 1); + spk.pushKV("range", range); + spk.pushKV("next", wallet_descriptor.next_index); + } + response.push_back(spk); + } + + return response; +}, + }; +} diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ae4e8f2898..92bae9dc54 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4537,6 +4537,7 @@ RPCHelpMan importprunedfunds(); RPCHelpMan removeprunedfunds(); RPCHelpMan importmulti(); RPCHelpMan importdescriptors(); +RPCHelpMan listdescriptors(); Span<const CRPCCommand> GetWalletRPCCommands() { @@ -4575,6 +4576,7 @@ static const CRPCCommand commands[] = { "wallet", "importwallet", &importwallet, {"filename"} }, { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} }, { "wallet", "listaddressgroupings", &listaddressgroupings, {} }, + { "wallet", "listdescriptors", &listdescriptors, {} }, { "wallet", "listlabels", &listlabels, {"purpose"} }, { "wallet", "listlockunspent", &listlockunspent, {} }, { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 261c1f0a1b..f62a0f267a 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -237,6 +237,7 @@ BASE_SCRIPTS = [ 'rpc_named_arguments.py', 'wallet_listsinceblock.py', 'wallet_listsinceblock.py --descriptors', + 'wallet_listdescriptors.py --descriptors', 'p2p_leak.py', 'wallet_encryption.py', 'wallet_encryption.py --descriptors', diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py new file mode 100755 index 0000000000..9f8c341bc7 --- /dev/null +++ b/test/functional/wallet_listdescriptors.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the listdescriptors RPC.""" + +from test_framework.descriptors import ( + descsum_create +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) + + +class ListDescriptorsTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + # do not create any wallet by default + def init_wallet(self, i): + return + + def run_test(self): + node = self.nodes[0] + assert_raises_rpc_error(-18, 'No wallet is loaded.', node.listdescriptors) + + self.log.info('Test that the command is not available for legacy wallets.') + node.createwallet(wallet_name='w1', descriptors=False) + assert_raises_rpc_error(-4, 'listdescriptors is not available for non-descriptor wallets', node.listdescriptors) + + self.log.info('Test the command for empty descriptors wallet.') + node.createwallet(wallet_name='w2', blank=True, descriptors=True) + assert_equal(0, len(node.get_wallet_rpc('w2').listdescriptors())) + + self.log.info('Test the command for a default descriptors wallet.') + node.createwallet(wallet_name='w3', descriptors=True) + result = node.get_wallet_rpc('w3').listdescriptors() + assert_equal(6, len(result)) + assert_equal(6, len([d for d in result if d['active']])) + assert_equal(3, len([d for d in result if d['internal']])) + for item in result: + assert item['desc'] != '' + assert item['next'] == 0 + assert item['range'] == [0, 0] + assert item['timestamp'] is not None + + self.log.info('Test non-active non-range combo descriptor') + node.createwallet(wallet_name='w4', blank=True, descriptors=True) + wallet = node.get_wallet_rpc('w4') + wallet.importdescriptors([{ + 'desc': descsum_create('combo(' + node.get_deterministic_priv_key().key + ')'), + 'timestamp': 1296688602, + }]) + expected = [{'active': False, + 'desc': 'combo(0227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3f)#np574htj', + 'timestamp': 1296688602}] + assert_equal(expected, wallet.listdescriptors()) + + +if __name__ == '__main__': + ListDescriptorsTest().main() |