fix(lite): unconfirmedBalance uses full-node pending semantics; drop pending-spent t-UTXOs from spendable
Lite-parity audit mediums (display / spendable-gating only — never over-reports spendable; no send call): - unconfirmedBalance was just the backend's mempool-incoming figure, but the shared UI (balance_tab "pending" chip, the fast-poll hint) reads it as the full-node "total minus spendable" portion. Set it to the larger of the two (the not-yet-spendable shielded part vs the mempool-incoming), so neither is hidden and a note counted in both isn't double-reported. - transparentBalance/spendableTransparentBalance included t-UTXOs already spent by a pending tx (the backend tbalance sums every utxo with spent==None). Subtract unconfirmed-spent t-UTXOs from the SPENDABLE figure only (clamped; gross transparent display unchanged) so the Send picker can't offer funds a pending tx already consumed. Falls back to no-subtraction when the notes call failed that cycle (as before). Test: testLiteUnconfirmedAndTransparentSpendable. Multi-recipient history rows held (TransactionInfo is single-output, same as the full node). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11981,6 +11981,48 @@ void testLiteParserAmountRangeChecks()
|
||||
EXPECT_TRUE(!note.ok);
|
||||
}
|
||||
|
||||
// Lite-parity mediums: unconfirmedBalance uses the full-node pending semantics (total - spendable, or the
|
||||
// backend mempool-incoming, whichever is larger), and a t-UTXO spent by a pending tx is excluded from the
|
||||
// SPENDABLE transparent total (never over-reports spendable).
|
||||
void testLiteUnconfirmedAndTransparentSpendable()
|
||||
{
|
||||
using namespace dragonx::wallet;
|
||||
{
|
||||
LiteWalletAppRefreshModel m;
|
||||
m.hasBalance = true;
|
||||
m.balance.shieldedZatoshis = 500000000; m.balance.spendableShieldedZatoshis = 300000000;
|
||||
m.balance.transparentZatoshis = 100000000; m.balance.totalZatoshis = 600000000;
|
||||
m.balance.unconfirmedZatoshis = 50000000;
|
||||
dragonx::WalletState s;
|
||||
applyLiteRefreshModelToWalletState(m, s);
|
||||
EXPECT_NEAR(s.unconfirmedBalance, 2.0, 1e-9);
|
||||
EXPECT_NEAR(s.totalBalance - s.spendableTotalBalance, 2.0, 1e-9);
|
||||
m.balance.unconfirmedZatoshis = 900000000;
|
||||
applyLiteRefreshModelToWalletState(m, s);
|
||||
EXPECT_NEAR(s.unconfirmedBalance, 9.0, 1e-9);
|
||||
}
|
||||
{
|
||||
LiteWalletAppRefreshModel m;
|
||||
m.hasBalance = true;
|
||||
m.balance.transparentZatoshis = 300000000; m.balance.totalZatoshis = 300000000;
|
||||
m.hasSpendableOutputs = true;
|
||||
LiteWalletAppSpendableOutputModel a; a.kind = LiteSpendableOutputKind::Utxo; a.address = "R1"; a.valueZatoshis = 100000000; a.unconfirmedSpent = true;
|
||||
LiteWalletAppSpendableOutputModel b = a; b.unconfirmedSpent = false; b.valueZatoshis = 200000000;
|
||||
m.spendableOutputs = {a, b};
|
||||
dragonx::WalletState s;
|
||||
applyLiteRefreshModelToWalletState(m, s);
|
||||
EXPECT_NEAR(s.transparentBalance, 3.0, 1e-9);
|
||||
EXPECT_NEAR(s.spendableTransparentBalance, 2.0, 1e-9);
|
||||
EXPECT_NEAR(s.spendableTotalBalance, 2.0, 1e-9);
|
||||
m.hasSpendableOutputs = false;
|
||||
applyLiteRefreshModelToWalletState(m, s);
|
||||
EXPECT_NEAR(s.spendableTransparentBalance, 3.0, 1e-9);
|
||||
m.hasSpendableOutputs = true; m.spendableOutputs[0].valueZatoshis = 900000000;
|
||||
applyLiteRefreshModelToWalletState(m, s);
|
||||
EXPECT_NEAR(s.spendableTransparentBalance, 0.0, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
// A tolerated partial refresh where the notes/utxo command failed (hasAddresses but
|
||||
// !hasSpendableOutputs) must NOT zero every per-address balance — it keeps the last-known
|
||||
// values so a correct total isn't accompanied by a misleading all-zero breakdown.
|
||||
@@ -19730,6 +19772,7 @@ int main()
|
||||
testLiteWalletControllerAsyncAppliesPassphrase();
|
||||
testLiteNewPassphraseRules();
|
||||
testLiteChainNameMigration();
|
||||
testLiteUnconfirmedAndTransparentSpendable();
|
||||
testLiteRefreshModelAppliesToWalletState();
|
||||
testLiteTransactionsAreNewestFirst();
|
||||
testSameTransactionList();
|
||||
|
||||
Reference in New Issue
Block a user