From 19a4892871752c120f5bc045547ddf205b4a5f47 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Fri, 22 Nov 2024 07:56:42 -0600 Subject: [PATCH 1/5] wallet: store total balance in Balance struct --- src/wallet/receive.cpp | 1 + src/wallet/receive.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index c164266f80b..ba761f09101 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -314,6 +314,7 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) } ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE); ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY); + ret.m_total = ret.m_mine_trusted + ret.m_mine_untrusted_pending + ret.m_mine_immature + ret.m_watchonly_trusted + ret.m_watchonly_untrusted_pending + ret.m_watchonly_immature; } } return ret; diff --git a/src/wallet/receive.h b/src/wallet/receive.h index d50644b4cf9..027f40a5f7a 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -55,6 +55,7 @@ struct Balance { CAmount m_watchonly_trusted{0}; CAmount m_watchonly_untrusted_pending{0}; CAmount m_watchonly_immature{0}; + CAmount m_total{0}; }; Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true); From 1b03a82cffff9bed2557279f27862b2a010d6bd5 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Fri, 22 Nov 2024 07:59:28 -0600 Subject: [PATCH 2/5] rpc, test: add "total" field to RPC getbalances --- src/wallet/rpc/coins.cpp | 4 +++- test/functional/wallet_balance.py | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index f1430a3c601..91f5b8b3ee7 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -447,6 +447,7 @@ RPCHelpMan getbalances() {RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"}, }}, RESULT_LAST_PROCESSED_BLOCK, + {RPCResult::Type::STR_AMOUNT, "total", "total of all balances returned by this RPC"}, } }, RPCExamples{ @@ -466,6 +467,7 @@ RPCHelpMan getbalances() const auto bal = GetBalance(wallet); UniValue balances{UniValue::VOBJ}; + const Balance full_bal{GetBalance(wallet, /*min_depth=*/0, /*avoid_reuse=*/false)}; { UniValue balances_mine{UniValue::VOBJ}; balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted)); @@ -474,7 +476,6 @@ RPCHelpMan getbalances() if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get // the total balance, and then subtract bal to get the reused address balance. - const auto full_bal = GetBalance(wallet, 0, false); balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending)); } balances.pushKV("mine", std::move(balances_mine)); @@ -489,6 +490,7 @@ RPCHelpMan getbalances() } AppendLastProcessedBlock(balances, wallet); + balances.pushKV("total", ValueFromAmount(full_bal.m_total)); return balances; }, }; diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 9da53402a45..27f36b731c7 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -176,10 +176,12 @@ class WalletTest(BitcoinTestFramework): 'untrusted_pending': Decimal('60.0')}, 'watchonly': {'immature': Decimal('5000'), 'trusted': Decimal('50.0'), - 'untrusted_pending': Decimal('0E-8')}} + 'untrusted_pending': Decimal('0E-8')}, + 'total': Decimal('69.99' if self.options.descriptors else '5119.99')} expected_balances_1 = {'mine': {'immature': Decimal('0E-8'), 'trusted': Decimal('0E-8'), # node 1's send had an unsafe input - 'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent + 'untrusted_pending': Decimal('30.0') - fee_node_1}, # Doesn't include output of node 0's send since it was spent + 'total': Decimal('30.0') - fee_node_1} if self.options.descriptors: del expected_balances_0["watchonly"] balances_0 = self.nodes[0].getbalances() From 6cfb73e0e3d645b9b50c5919c9b67091f41c32f9 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Fri, 22 Nov 2024 08:23:54 -0600 Subject: [PATCH 3/5] cli, test: update -getinfo to use rpc getbalances#total --- src/bitcoin-cli.cpp | 6 +++--- test/functional/interface_bitcoin_cli.py | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index dd7b21e5671..e5ef6428d32 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -374,7 +374,7 @@ public: result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]); } if (!batch[ID_BALANCES]["result"].isNull()) { - result.pushKV("balance", batch[ID_BALANCES]["result"]["mine"]["trusted"]); + result.pushKV("balance", batch[ID_BALANCES]["result"]["total"]); } result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]); result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]); @@ -1007,7 +1007,7 @@ static void ParseError(const UniValue& error, std::string& strPrint, int& nRet) /** * GetWalletBalances calls listwallets; if more than one wallet is loaded, it then - * fetches mine.trusted balances for each loaded wallet and pushes them to `result`. + * fetches the total balance for each loaded wallet and pushes it to `result`. * * @param result Reference to UniValue object the wallet names and balances are pushed to. */ @@ -1023,7 +1023,7 @@ static void GetWalletBalances(UniValue& result) for (const UniValue& wallet : wallets.getValues()) { const std::string& wallet_name = wallet.get_str(); const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name); - const UniValue& balance = getbalances.find_value("result")["mine"]["trusted"]; + const UniValue& balance = getbalances.find_value("result")["total"]; balances.pushKV(wallet_name, balance); } result.pushKV("balances", std::move(balances)); diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 3fe6570dd16..968b9b70395 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -21,10 +21,9 @@ from test_framework.util import ( import time # The block reward of coinbaseoutput.nValue (50) BTC/block matures after -# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect -# node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 50 BTC/block. +# COINBASE_MATURITY (100) blocks. BLOCKS = COINBASE_MATURITY + 1 -BALANCE = (BLOCKS - 100) * 50 +BALANCE = BLOCKS * 50 JSON_PARSING_ERROR = 'error: Error parsing JSON: foo' BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero' @@ -219,7 +218,7 @@ class TestBitcoinCli(BitcoinTestFramework): # Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets. wallets = [self.default_wallet_name, 'Encrypted', 'secret'] - amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)] + amounts = [BALANCE, 9, 31] self.nodes[0].createwallet(wallet_name=wallets[1]) self.nodes[0].createwallet(wallet_name=wallets[2]) w1 = self.nodes[0].get_wallet_rpc(wallets[0]) @@ -231,9 +230,11 @@ class TestBitcoinCli(BitcoinTestFramework): w2.encryptwallet(password) w1.sendtoaddress(w2.getnewaddress(), amounts[1]) w1.sendtoaddress(w3.getnewaddress(), amounts[2]) + amounts[0] -= (amounts[1] + amounts[2]) # Mine a block to confirm; adds a block reward (50 BTC) to the default wallet. self.generate(self.nodes[0], 1) + amounts[0] += 50 self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance") for i in range(len(wallets)): From e00979167a969f0e65dc2ea5ac66489fc0925f5e Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Fri, 22 Nov 2024 08:45:31 -0600 Subject: [PATCH 4/5] test: coverage for getbalances#total field in wallet_avoidreuse --- test/functional/wallet_avoidreuse.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py index bba79d0a258..383f5aaaeab 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -59,9 +59,12 @@ def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None def assert_balances(node, mine, margin=0.001): '''Make assertions about a node's getbalances output''' - got = node.getbalances()["mine"] + balances, total = node.getbalances(), 0 for k,v in mine.items(): - assert_approx(got[k], v, margin) + assert_approx(balances["mine"][k], v, margin) + total += v + assert_approx(balances["total"], total, margin) + class AvoidReuseTest(BitcoinTestFramework): def add_options(self, parser): From 5d0c8362940bec425967484c8ecb59f51150fe57 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sat, 23 Nov 2024 08:45:40 -0600 Subject: [PATCH 5/5] doc: release notes for rpc getbalances#total and -getinfo balances update --- doc/release-notes-31353.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 doc/release-notes-31353.md diff --git a/doc/release-notes-31353.md b/doc/release-notes-31353.md new file mode 100644 index 00000000000..84a0aeb24fd --- /dev/null +++ b/doc/release-notes-31353.md @@ -0,0 +1,9 @@ +Updated RPCs +------------ + +- RPC getbalances has a new `total` field that provides the sum of all wallet + balances returned by the RPC. (#31353) + +- CLI -getinfo now displays wallet balances from RPC getbalances `total` instead + of `mine.trusted` in order to include watchonly, reused, untrusted pending, and + immature coinbase outputs in the balance shown. (#31353)