diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2883,6 +2884,83 @@ return true; } +static UniValue getbalances(const Config &config, + const JSONRPCRequest &request) { + std::shared_ptr const rpc_wallet = + GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(rpc_wallet.get(), request.fHelp)) { + return NullUniValue; + } + CWallet &wallet = *rpc_wallet; + + const RPCHelpMan help{ + "getbalances", + "Returns an object with all balances in " + CURRENCY_UNIT + ".\n", + {}, + RPCResult{ + "{\n" + " \"mine\": { (object) balances from " + "outputs that the wallet can sign\n" + " \"trusted\": xxx (numeric) trusted balance " + "(outputs created by the wallet or confirmed outputs)\n" + " \"untrusted_pending\": xxx (numeric) untrusted " + "pending balance (outputs created by others that are in the " + "mempool)\n" + " \"immature\": xxx (numeric) balance from " + "immature coinbase outputs\n" + " },\n" + " \"watchonly\": { (object) watchonly " + "balances (not present if wallet does not watch anything)\n" + " \"trusted\": xxx (numeric) trusted balance " + "(outputs created by the wallet or confirmed outputs)\n" + " \"untrusted_pending\": xxx (numeric) untrusted " + "pending balance (outputs created by others that are in the " + "mempool)\n" + " \"immature\": xxx (numeric) balance from " + "immature coinbase outputs\n" + " },\n" + "}\n"}, + RPCExamples{HelpExampleCli("getbalances", "") + + HelpExampleRpc("getbalances", "")}, + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } + + // 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(); + + auto locked_chain = wallet.chain().lock(); + LOCK(wallet.cs_wallet); + + UniValue obj(UniValue::VOBJ); + + const auto bal = wallet.GetBalance(); + UniValue balances{UniValue::VOBJ}; + { + UniValue balances_mine{UniValue::VOBJ}; + balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted)); + balances_mine.pushKV("untrusted_pending", + ValueFromAmount(bal.m_mine_untrusted_pending)); + balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature)); + balances.pushKV("mine", balances_mine); + } + if (wallet.HaveWatchOnly()) { + UniValue balances_watchonly{UniValue::VOBJ}; + balances_watchonly.pushKV("trusted", + ValueFromAmount(bal.m_watchonly_trusted)); + balances_watchonly.pushKV( + "untrusted_pending", + ValueFromAmount(bal.m_watchonly_untrusted_pending)); + balances_watchonly.pushKV("immature", + ValueFromAmount(bal.m_watchonly_immature)); + balances.pushKV("watchonly", balances_watchonly); + } + return balances; +} + static UniValue getwalletinfo(const Config &config, const JSONRPCRequest &request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -2901,18 +2979,12 @@ " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet " "version\n" - " \"balance\": xxxxxxx, (numeric) the total " - "confirmed balance of the wallet in " + - CURRENCY_UNIT + - "\n" - " \"unconfirmed_balance\": xxx, (numeric) the total " - "unconfirmed balance of the wallet in " + - CURRENCY_UNIT + - "\n" - " \"immature_balance\": xxxxxx, (numeric) the total immature " - "balance of the wallet in " + - CURRENCY_UNIT + - "\n" + " \"balance\": xxxxxxx, (numeric) Identical to " + "getbalances().mine.trusted\n" + " \"unconfirmed_balance\": xxx, (numeric) Identical to " + "getbalances().mine.untrusted_pending\n" + " \"immature_balance\": xxxxxx, (numeric) Identical to " + "getbalances().mine.immature\n" " \"txcount\": xxxxxxx, (numeric) the total number " "of transactions in the wallet\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp " @@ -4761,6 +4833,7 @@ { "wallet", "getreceivedbylabel", getreceivedbylabel, {"label","minconf"} }, { "wallet", "gettransaction", gettransaction, {"txid","include_watchonly"} }, { "wallet", "getunconfirmedbalance", getunconfirmedbalance, {} }, + { "wallet", "getbalances", getbalances, {} }, { "wallet", "getwalletinfo", getwalletinfo, {} }, { "wallet", "keypoolrefill", keypoolrefill, {"newsize"} }, { "wallet", "listaddressgroupings", listaddressgroupings, {} }, diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -66,14 +66,25 @@ assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0) - self.log.info("Mining blocks ...") + self.log.info("Check that only node 0 is watching an address") + assert 'watchonly' in self.nodes[0].getbalances() + assert 'watchonly' not in self.nodes[1].getbalances() + self.log.info("Mining blocks ...") self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(1) self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) self.sync_all() + assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50) + assert_equal(self.nodes[0].getwalletinfo()['balance'], 50) + assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50) + + assert_equal(self.nodes[0].getbalances()[ + 'watchonly']['immature'], 5000) + assert 'watchonly' not in self.nodes[1].getbalances() + assert_equal(self.nodes[0].getbalance(), 50) assert_equal(self.nodes[1].getbalance(), 50) @@ -137,10 +148,15 @@ # getunconfirmedbalance # output of node 1's spend assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) + assert_equal(self.nodes[0].getbalances()['mine'] + ['untrusted_pending'], Decimal('60')) assert_equal(self.nodes[0].getwalletinfo()[ "unconfirmed_balance"], Decimal('60')) + # Doesn't include output of node 0's send since it was spent assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) + assert_equal(self.nodes[1].getbalances()['mine'] + ['untrusted_pending'], Decimal('0')) assert_equal(self.nodes[1].getwalletinfo()[ "unconfirmed_balance"], Decimal('0'))