From 999931cf8f167c7547f1015cdf05437a460c27f0 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Tue, 9 Apr 2019 09:10:08 -0400 Subject: [PATCH] rpc: Add getbalances RPC --- src/wallet/rpcwallet.cpp | 75 ++++++++++++++++++++++++++++--- test/functional/wallet_balance.py | 15 ++++++- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 02da2a7114..18da83dfe8 100644 --- 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. @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -27,10 +26,11 @@ #include #include #include -#include #include +#include #include #include +#include #include #include #include @@ -2373,6 +2373,68 @@ static UniValue settxfee(const JSONRPCRequest& request) return true; } +static UniValue getbalances(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 JSONRPCRequest& request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -2389,9 +2451,9 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) "{\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" + " \"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" @@ -4075,6 +4137,7 @@ static const CRPCCommand commands[] = { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, + { "wallet", "getbalances", &getbalances, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} }, { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, { "wallet", "importmulti", &importmulti, {"requests","options"} }, diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index c02d7422b9..4d1f1ccdc1 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -59,14 +59,24 @@ class WalletTest(BitcoinTestFramework): 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) @@ -107,8 +117,11 @@ class WalletTest(BitcoinTestFramework): assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) # getunconfirmedbalance assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend + assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('60')) assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) + assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('0')) assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) test_balances(fee_node_1=Decimal('0.01'))