diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1022,6 +1022,74 @@ return reply; } +static UniValue dumpcoins(const Config &config, const JSONRPCRequest &request) { + std::shared_ptr const pwallet = + GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet.get(), request.fHelp)) { + return NullUniValue; + } + + RPCHelpMan{ + "dumpcoins", + "dump all the UTXO tracked by the wallet.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ_DYN, + "", + "", + {{ + RPCResult::Type::ARR, + "address", + "The list of UTXO corresponding to this address.", + {{ + RPCResult::Type::OBJ, + "", + "", + { + {RPCResult::Type::STR_HEX, "txid", + "The transaction id"}, + {RPCResult::Type::NUM, "vout", "The output number"}, + {RPCResult::Type::NUM, "depth", "The output's depth"}, + {RPCResult::Type::STR_AMOUNT, "value", + "The output's amount"}, + }, + }}, + }}, + }, + RPCExamples{HelpExampleCli("dumpcoins", "") + + HelpExampleRpc("dumpcoins", "")}, + } + .Check(request); + + CWallet &wallet = *pwallet; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + wallet.BlockUntilSyncedToCurrentChain(); + + LOCK(wallet.cs_wallet); + + EnsureWalletIsUnlocked(&wallet); + + UniValue result(UniValue::VOBJ); + for (const auto &p : wallet.ListCoins()) { + UniValue coins(UniValue::VARR); + for (const auto &o : p.second) { + UniValue utxo(UniValue::VOBJ); + utxo.pushKV("txid", o.tx->GetId().ToString()); + utxo.pushKV("vout", o.i); + utxo.pushKV("depth", o.nDepth); + utxo.pushKV("value", ValueFromAmount(o.tx->tx->vout[o.i].nValue)); + + coins.push_back(std::move(utxo)); + } + + result.pushKV(EncodeDestination(p.first, config), coins); + } + + return result; +} + struct ImportData { // Input data //! Provided redeemScript; will be moved to `import_scripts` if relevant. @@ -2252,6 +2320,7 @@ { "wallet", "abortrescan", abortrescan, {} }, { "wallet", "dumpprivkey", dumpprivkey, {"address"} }, { "wallet", "dumpwallet", dumpwallet, {"filename"} }, + { "wallet", "dumpcoins", dumpcoins, {} }, { "wallet", "importdescriptors", importdescriptors, {"requests"} }, { "wallet", "importmulti", importmulti, {"requests","options"} }, { "wallet", "importprivkey", importprivkey, {"privkey","label","rescan"} }, diff --git a/test/functional/abc_wallet_dumpcoins.py b/test/functional/abc_wallet_dumpcoins.py new file mode 100755 --- /dev/null +++ b/test/functional/abc_wallet_dumpcoins.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test the dumpcoins RPCs call. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +from decimal import Decimal + + +class DumpCoinsTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + assert_equal(node.dumpcoins(), {}) + + address0 = node.getnewaddress() + address1 = node.getnewaddress() + + coinbases = [] + + def generate_and_get_txid(address, expected_coins): + blockhash = node.generatetoaddress(1, address)[0] + assert_equal(node.dumpcoins(), expected_coins) + + # Get the coinbase txid + coinbases.append(node.getblock(blockhash)["tx"][0]) + + generate_and_get_txid(address0, {}) + + for _ in range(99): + generate_and_get_txid(address1, {}) + + # Coinbases reach maturity and start to show up. + generate_and_get_txid(address1, { + address0: [{ + "txid": coinbases[0], + "vout": 0, + "depth": 101, + "value": Decimal('50.00000000'), + }], + }) + + # And now on address1 + generate_and_get_txid(address1, { + address0: [{ + "txid": coinbases[0], + "vout": 0, + "depth": 102, + "value": Decimal('50.00000000'), + }], + address1: [{ + "txid": coinbases[1], + "vout": 0, + "depth": 101, + "value": Decimal('50.00000000'), + }], + }) + + +if __name__ == '__main__': + DumpCoinsTest().main()