This commit is contained in:
Jon Atack 2025-01-08 20:44:51 +01:00 committed by GitHub
commit 360def914b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 31 additions and 12 deletions

View file

@ -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)

View file

@ -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));

View file

@ -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;

View file

@ -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);

View file

@ -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;
},
};

View file

@ -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'
@ -222,7 +221,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])
@ -234,9 +233,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)):

View file

@ -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):

View file

@ -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()