You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
key-wallet: spend routed away from the owning account by tx-type narrowing leaves a stale UTXO #898
WalletTransactionChecker::check_transaction narrows which accounts are checked by the transaction's classified type (TransactionRouter::get_relevant_account_types). A transaction that spends an account's coin but classifies to a type excluding that account (example: an AssetLock-classified transaction spending a CoinJoin-account coin) is never matched against the owning account, so update_utxos never runs there and the spent coin stays in the UTXO set, inflating the balance.
This is a pre-existing gap on dev, independent of block-delivery order. It only self-corrects if the funding is ever redelivered after the spend (the #649 observed-spent guard then blocks re-insertion), which cannot be relied on.
PR #851 briefly carried an un-gated remove_spent_from_accounts pass that scanned all funding accounts per block transaction. It was dropped from the final PR to keep it scoped to #649. A proper fix should decide where this belongs:
an un-gated spend reconciliation pass (the dropped approach, reinstated deliberately with its own tests and cost analysis), or
widening get_relevant_account_types so any account type owning a spent input is always checked, or
an outpoint-indexed lookup (wallet-level OutPoint -> account index) making the reconciliation O(inputs) instead of O(inputs x accounts).
Closing: filed against a stale reading of the tree. The reported bug is already fixed, and the residual gaps are not reachable.
The headline case is fixed.#867 (93b7477c8) widened the AssetLock arm of get_relevant_account_types to the full fund-bearing set plus the identity types, driven by the same class of production reports (dashpay/platform#4073, #4074, dashpay/dash-wallet#1507). It is pinned by test_asset_lock_spending_coinjoin_utxo_is_debited in wallet_checker.rs, which funds a CoinJoin UTXO, spends it via an AssetLock transaction with BIP44 change, asserts the classification really is AssetLock, and asserts the CoinJoin input is debited. So the exact scenario this issue describes is covered.
The remaining arms cannot produce a stale UTXO. Auditing every classification against fund_bearing_account_types() = {StandardBIP44, StandardBIP32, CoinJoin, DashpayReceivingFunds, DashpayExternalAccount}:
AssetUnlock is consensus-forbidden from carrying any input at all: Dash Core rejects such a transaction outright at src/evo/assetlocktx.cpp:150 (if (!tx.vin.empty()) return state.Invalid(..., "bad-assetunlocktx-have-input")). Its funds come from the credit pool, so there is no input to route and no coin to strand. Coinbase and Ignored likewise have no real prevouts.
That leaves Provider* omitting the two DashPay account types. The mechanism is live (DashPay funds accounts are real, their coins count toward the balance and are offered as spendable, and TransactionBuilder::set_funding accepts any ManagedCoreFundsAccount), but it needs a masternode operator to deliberately fund a ProRegTx or ProUpServTx from a DIP15 contact-receiving account — money a contact sent them — while ignoring their standard accounts. Nothing in the tree does this and it is not a natural user action. Theoretically reachable, practically not.
What came out of this that is worth keeping is a different bug with the opposite sign, on the credit side rather than the debit side: Coinbase and AssetUnlock consult only StandardBIP44/BIP32, so an output paying a CoinJoin or DashPay address is never discovered, undercounting the balance. Filed separately.
Also worth noting for whoever touches this next: "fund-bearing account" is currently spelled out three times by hand — fund_bearing_account_types(), ManagedAccountCollection::all_funding_accounts(), and update_utxos's match arm. Drift between those lists is exactly what this issue thought it had found.
Activity
xdustinface commentedon Jul 16, 2026
Closing: filed against a stale reading of the tree. The reported bug is already fixed, and the residual gaps are not reachable.
The headline case is fixed. #867 (
93b7477c8) widened theAssetLockarm ofget_relevant_account_typesto the full fund-bearing set plus the identity types, driven by the same class of production reports (dashpay/platform#4073, #4074, dashpay/dash-wallet#1507). It is pinned bytest_asset_lock_spending_coinjoin_utxo_is_debitedinwallet_checker.rs, which funds a CoinJoin UTXO, spends it via an AssetLock transaction with BIP44 change, asserts the classification really isAssetLock, and asserts the CoinJoin input is debited. So the exact scenario this issue describes is covered.The remaining arms cannot produce a stale UTXO. Auditing every classification against
fund_bearing_account_types()= {StandardBIP44, StandardBIP32, CoinJoin, DashpayReceivingFunds, DashpayExternalAccount}:Standard,CoinJoinAssetLockProviderRegistration/ProviderUpdateRegistrar/ProviderUpdateService/ProviderUpdateRevocationAssetUnlockCoinbaseIgnoredAssetUnlockis consensus-forbidden from carrying any input at all: Dash Core rejects such a transaction outright atsrc/evo/assetlocktx.cpp:150(if (!tx.vin.empty()) return state.Invalid(..., "bad-assetunlocktx-have-input")). Its funds come from the credit pool, so there is no input to route and no coin to strand. Coinbase andIgnoredlikewise have no real prevouts.That leaves
Provider*omitting the two DashPay account types. The mechanism is live (DashPay funds accounts are real, their coins count toward the balance and are offered as spendable, andTransactionBuilder::set_fundingaccepts anyManagedCoreFundsAccount), but it needs a masternode operator to deliberately fund a ProRegTx or ProUpServTx from a DIP15 contact-receiving account — money a contact sent them — while ignoring their standard accounts. Nothing in the tree does this and it is not a natural user action. Theoretically reachable, practically not.What came out of this that is worth keeping is a different bug with the opposite sign, on the credit side rather than the debit side:
CoinbaseandAssetUnlockconsult only StandardBIP44/BIP32, so an output paying a CoinJoin or DashPay address is never discovered, undercounting the balance. Filed separately.Also worth noting for whoever touches this next: "fund-bearing account" is currently spelled out three times by hand —
fund_bearing_account_types(),ManagedAccountCollection::all_funding_accounts(), andupdate_utxos's match arm. Drift between those lists is exactly what this issue thought it had found.