Skip to content

Commit 5209273

Browse files
committed
wallet: Add separate balance info for non-mempool wallet txs
1 parent abe7cbf commit 5209273

File tree

8 files changed

+42
-13
lines changed

8 files changed

+42
-13
lines changed

src/interfaces/wallet.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,12 @@ struct WalletBalances
369369
CAmount balance = 0;
370370
CAmount unconfirmed_balance = 0;
371371
CAmount immature_balance = 0;
372+
CAmount nonmempool_balance = 0;
372373

373374
bool balanceChanged(const WalletBalances& prev) const
374375
{
375376
return balance != prev.balance || unconfirmed_balance != prev.unconfirmed_balance ||
376-
immature_balance != prev.immature_balance;
377+
immature_balance != prev.immature_balance || nonmempool_balance != prev.nonmempool_balance;
377378
}
378379
};
379380

src/wallet/interfaces.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ class WalletImpl : public Wallet
388388
result.balance = bal.m_mine_trusted;
389389
result.unconfirmed_balance = bal.m_mine_untrusted_pending;
390390
result.immature_balance = bal.m_mine_immature;
391+
result.nonmempool_balance = bal.m_mine_nonmempool;
391392
return result;
392393
}
393394
bool tryGetBalances(WalletBalances& balances, uint256& block_hash) override

src/wallet/receive.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,26 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse)
260260
CAmount credit_mine = txo.GetTxOut().nValue;
261261

262262
// Set the amounts in the return object
263-
if (wallet.IsTxImmatureCoinBase(wtx) && wtx.isConfirmed()) {
264-
ret.m_mine_immature += credit_mine;
265-
} else if (is_trusted && tx_depth >= min_depth) {
266-
ret.m_mine_trusted += credit_mine;
267-
} else if (!is_trusted && wtx.InMempool()) {
268-
ret.m_mine_untrusted_pending += credit_mine;
263+
if (wallet.IsTxImmatureCoinBase(wtx)) {
264+
if (wtx.isConfirmed()) {
265+
ret.m_mine_immature += credit_mine;
266+
}
267+
// immature coinbase txs that aren't confirmed are irrelevant
268+
} else if (is_trusted) {
269+
if (tx_depth >= min_depth) {
270+
ret.m_mine_trusted += credit_mine;
271+
} else {
272+
ret.m_mine_untrusted_pending += credit_mine;
273+
}
274+
} else { // !is_trusted
275+
if (wtx.InMempool()) {
276+
ret.m_mine_untrusted_pending += credit_mine;
277+
} else if (wtx.isAbandoned() || wtx.isBlockConflicted() || wtx.isMempoolConflicted()) {
278+
// ignore abandoned, conflicted txs
279+
// same condition as for IsSpent()
280+
} else {
281+
ret.m_mine_nonmempool += credit_mine;
282+
}
269283
}
270284
}
271285
}

src/wallet/receive.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct Balance {
4747
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
4848
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
4949
CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain
50+
CAmount m_mine_nonmempool{0}; //!< Regular txs that are not in the mempool
5051
};
5152
Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true);
5253

src/wallet/rpc/coins.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ RPCHelpMan getbalances()
413413
{RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"},
414414
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"},
415415
{RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
416+
{RPCResult::Type::STR_AMOUNT, "nonmempool", "balance from transactions not in the mempool"},
416417
{RPCResult::Type::STR_AMOUNT, "used", /*optional=*/true, "(only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating)"},
417418
}},
418419
RESULT_LAST_PROCESSED_BLOCK,
@@ -440,6 +441,7 @@ RPCHelpMan getbalances()
440441
balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted));
441442
balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending));
442443
balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature));
444+
balances_mine.pushKV("nonmempool", ValueFromAmount(bal.m_mine_nonmempool));
443445
if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
444446
// If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get
445447
// the total balance, and then subtract bal to get the reused address balance.

test/functional/wallet_balance.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,12 @@ def test_balances(*, fee_node_1=0):
145145
# getbalances
146146
expected_balances_0 = {'mine': {'immature': Decimal('0E-8'),
147147
'trusted': Decimal('9.99'), # change from node 0's send
148-
'untrusted_pending': Decimal('60.0')}}
148+
'untrusted_pending': Decimal('60.0'),
149+
'nonmempool': Decimal('0.0')}}
149150
expected_balances_1 = {'mine': {'immature': Decimal('0E-8'),
150151
'trusted': Decimal('0E-8'), # node 1's send had an unsafe input
151-
'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
152+
'untrusted_pending': Decimal('30.0') - fee_node_1, # Doesn't include output of node 0's send since it was spent
153+
'nonmempool': Decimal('0.0')}}
152154
balances_0 = self.nodes[0].getbalances()
153155
balances_1 = self.nodes[1].getbalances()
154156
# remove lastprocessedblock keys (they will be tested later)

test/functional/wallet_migration.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import shutil
99
import struct
1010
import time
11+
from decimal import Decimal
1112

1213
from test_framework.address import (
1314
key_to_p2pkh,
@@ -522,6 +523,7 @@ def test_pk_coinbases(self):
522523
self.generatetodescriptor(self.master_node, 1, desc)
523524

524525
bals = wallet.getbalances()
526+
bals["mine"]["nonmempool"] = Decimal('0.0')
525527

526528
_, wallet = self.migrate_and_get_rpc("pkcb")
527529

@@ -537,6 +539,7 @@ def test_encrypted(self):
537539
txid = default.sendtoaddress(addr, 1)
538540
self.generate(self.master_node, 1)
539541
bals = wallet.getbalances()
542+
bals["mine"]["nonmempool"] = Decimal('0.0')
540543

541544
# Use self.migrate_and_get_rpc to test this error to get everything copied over to the master node
542545
assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", self.migrate_and_get_rpc, "encrypted")
@@ -577,6 +580,7 @@ def test_unloaded_by_path(self):
577580
txid = default.sendtoaddress(addr, 1)
578581
self.generate(self.master_node, 1)
579582
bals = wallet.getbalances()
583+
bals["mine"]["nonmempool"] = Decimal('0.0')
580584

581585
wallet.unloadwallet()
582586

@@ -616,6 +620,7 @@ def test_wallet_with_relative_path(self):
616620
txid = default.sendtoaddress(addr, 1)
617621
self.generate(self.master_node, 1)
618622
bals = wallet.getbalances()
623+
bals["mine"]["nonmempool"] = Decimal('0.0')
619624

620625
migrate_res, wallet = self.migrate_and_get_rpc(relative_name)
621626

@@ -636,6 +641,7 @@ def test_wallet_with_relative_path(self):
636641
self.old_node.restorewallet("relative_restored", migrate_res['backup_path'])
637642
wallet = self.old_node.get_wallet_rpc("relative_restored")
638643
assert wallet.gettransaction(txid)
644+
del bals["mine"]["nonmempool"]
639645
assert_equal(bals, wallet.getbalances())
640646

641647
info = wallet.getwalletinfo()
@@ -652,6 +658,7 @@ def test_wallet_with_path(self, wallet_path):
652658
txid = default.sendtoaddress(addr, 1)
653659
self.generate(self.master_node, 1)
654660
bals = wallet.getbalances()
661+
bals["mine"]["nonmempool"] = Decimal('0.0')
655662

656663
_, wallet = self.migrate_and_get_rpc(wallet_path)
657664

@@ -1423,14 +1430,14 @@ def test_miniscript(self):
14231430
_, wallet = self.migrate_and_get_rpc("miniscript")
14241431

14251432
# The miniscript with all keys should be in the migrated wallet
1426-
assert_equal(wallet.getbalances()["mine"], {"trusted": 0.75, "untrusted_pending": 0, "immature": 0})
1433+
assert_equal(wallet.getbalances()["mine"], {"trusted": 0.75, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
14271434
assert_equal(wallet.getaddressinfo(all_keys_addr)["ismine"], True)
14281435
assert_equal(wallet.getaddressinfo(some_keys_addr)["ismine"], False)
14291436

14301437
# The miniscript with some keys should be in the watchonly wallet
14311438
assert "miniscript_watchonly" in self.master_node.listwallets()
14321439
watchonly = self.master_node.get_wallet_rpc("miniscript_watchonly")
1433-
assert_equal(watchonly.getbalances()["mine"], {"trusted": 1, "untrusted_pending": 0, "immature": 0})
1440+
assert_equal(watchonly.getbalances()["mine"], {"trusted": 1, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
14341441
assert_equal(watchonly.getaddressinfo(some_keys_addr)["ismine"], True)
14351442
assert_equal(watchonly.getaddressinfo(all_keys_addr)["ismine"], False)
14361443

@@ -1479,15 +1486,15 @@ def test_taproot(self):
14791486
res, wallet = self.migrate_and_get_rpc("taproot")
14801487

14811488
# The rawtr should be migrated
1482-
assert_equal(wallet.getbalances()["mine"], {"trusted": 0.5, "untrusted_pending": 0, "immature": 0})
1489+
assert_equal(wallet.getbalances()["mine"], {"trusted": 0.5, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
14831490
assert_equal(wallet.getaddressinfo(rawtr_addr)["ismine"], True)
14841491
assert_equal(wallet.getaddressinfo(tr_addr)["ismine"], False)
14851492
assert_equal(wallet.getaddressinfo(tr_script_addr)["ismine"], False)
14861493

14871494
# The tr() with some keys should be in the watchonly wallet
14881495
assert "taproot_watchonly" in self.master_node.listwallets()
14891496
watchonly = self.master_node.get_wallet_rpc("taproot_watchonly")
1490-
assert_equal(watchonly.getbalances()["mine"], {"trusted": 5, "untrusted_pending": 0, "immature": 0})
1497+
assert_equal(watchonly.getbalances()["mine"], {"trusted": 5, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
14911498
assert_equal(watchonly.getaddressinfo(rawtr_addr)["ismine"], False)
14921499
assert_equal(watchonly.getaddressinfo(tr_addr)["ismine"], True)
14931500
assert_equal(watchonly.getaddressinfo(tr_script_addr)["ismine"], True)

test/functional/wallet_orphanedreward.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def run_test(self):
4646
"trusted": 10,
4747
"untrusted_pending": 0,
4848
"immature": 0,
49+
"nonmempool": 0,
4950
})
5051
# And the unconfirmed tx to be abandoned
5152
assert_equal(self.nodes[1].gettransaction(txid)["details"][0]["abandoned"], True)

0 commit comments

Comments
 (0)