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); @@ -2898,39 +2976,31 @@ {}, RPCResult{ "{\n" - " \"walletname\": xxxxx, (string) the wallet " - "name\n" + " \"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" - " \"txcount\": xxxxxxx, (numeric) the total " - "number of transactions in the wallet\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 " - "(seconds since Unix epoch) of the oldest pre-generated key in " - "the key pool\n" - " \"keypoolsize\": xxxx, (numeric) how many new " - "keys are pre-generated (only counts external keys)\n" - " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new " - "keys are pre-generated for internal use (used for change " - "outputs, only appears if the wallet is using this feature, " - "otherwise external keys are used)\n" - " \"unlocked_until\": ttt, (numeric) the timestamp " - "in seconds since epoch (midnight Jan 1 1970 GMT) that the " - "wallet is unlocked for transfers, or 0 if the wallet is " - "locked\n" - " \"paytxfee\": x.xxxx, (numeric) the " - "transaction fee configuration, set in " + + "(seconds since Unix epoch) of the oldest pre-generated key in the " + "key pool\n" + " \"keypoolsize\": xxxx, (numeric) how many new keys " + "are pre-generated (only counts external keys)\n" + " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys " + "are pre-generated for internal use (used for change outputs, only " + "appears if the wallet is using this feature, otherwise external " + "keys are used)\n" + " \"unlocked_until\": ttt, (numeric) the timestamp in " + "seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is " + "unlocked for transfers, or 0 if the wallet is locked\n" + " \"paytxfee\": x.xxxx, (numeric) the transaction " + "fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"\" (string, optional) the " @@ -4763,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 @@ -67,14 +67,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) @@ -138,10 +149,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'))