Skip to content

fix(key-wallet): let CoinSelector spend mempool UTXOs #748

Merged
xdustinface merged 3 commits into
v0.42-devfrom
fix/coin-selector-spend-mempool
May 9, 2026
Merged

fix(key-wallet): let CoinSelector spend mempool UTXOs #748
xdustinface merged 3 commits into
v0.42-devfrom
fix/coin-selector-spend-mempool

Conversation

@ZocoLini
Copy link
Copy Markdown
Collaborator

@ZocoLini ZocoLini commented May 9, 2026

WalletCoreBalance::spendable() already counts trusted self-send change and untrusted incoming mempool UTXOs, but CoinSelector filtered them out, building a tx right after a send returned NoUtxosAvailable despite the UI showing funds.
Defer candidate filtering to Utxo::is_spendable (the wallet already decides what's spendable upstream). Adds two dashd integration tests covering both mempool flavors: trusted change and untrusted incoming.

Summary by CodeRabbit

Release Notes

  • Tests

    • Expanded test coverage for transaction mempool handling, including scenarios for spending change outputs from pending transactions and handling unconfirmed incoming funds.
  • Refactor

    • Simplified coin selection logic to streamline how the wallet filters and selects UTXOs during transaction building.

ZocoLini and others added 2 commits May 8, 2026 13:15
…g UTXOs

Adds two regression tests in `tests/dashd_sync/tests_transaction_builder.rs`
that drive `ManagedWalletInfo::build_and_sign_transaction` end-to-end against
a real dashd regtest node, broadcasting via the SPV client.

Both tests use a fresh-mnemonic wallet (EMPTY/SECONDARY) so the SPV side has
no preexisting confirmed UTXOs from the test chain that would mask the bug.

- test_spend_unconfirmed_change_balance: confirms the wallet reports trusted
  mempool change as spendable, then fails to actually spend it through coin
  selection (the filter ignores `is_trusted`).
- test_spend_unconfirmed_incoming_balance: confirms the wallet reports
  unconfirmed incoming as spendable, then fails to spend it.

Both tests are red against the current coin selector and document the user-
reported symptom ("balance shows up but cannot be spent").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dable

`CoinSelector` carried its own confirmation policy
(`min_confirmations`, `include_unconfirmed`) and filtered candidates
with `(include_unconfirmed || is_confirmed || is_instantlocked)` plus
a `current_height - utxo.height >= min_confirmations` depth check.
Two consequences:

  1. `is_trusted` was missing from the OR — mempool change from a tx
     the wallet itself authored was rejected, even though the balance
     UI shows it as spendable. Users hit this as
     `CoinSelection(NoUtxosAvailable)` immediately after sending.
  2. The depth check used `>= 1`, which silently rejected a UTXO mined
     into the wallet's tip block (depth 0, but 1 confirmation in the
     usual sense), and gated all unconfirmed incoming through the same
     path.

The `Utxo` itself already has `is_spendable(current_height)` — which
checks not-locked and coinbase maturity — and the wallet decides what
to put in the UTXO map (with `is_confirmed`, `is_instantlocked`,
`is_trusted` flags reflecting its policy). The coin selector having a
*second*, divergent policy on top is what produced the bug.

Drop the duplicated machinery: remove `min_confirmations`,
`include_unconfirmed`, and the per-call helpers. Both filter sites
now defer to `Utxo::is_spendable`, so coin selection accepts every
UTXO the wallet tracks as spendable — confirmed, IS-locked, trusted
self-send change, and incoming mempool alike.

Tightens `tests_transaction_builder.rs` accordingly: the workaround
that mined an extra "bump" tx so `last_processed_height` advanced past
the funding UTXO is no longer needed.

All 482 key-wallet unit tests and 26 dashd_sync integration tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Review Change Stack

Warning

Rate limit exceeded

@ZocoLini has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 20 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ad1eac50-1574-42b3-b952-d95ddc863929

📥 Commits

Reviewing files that changed from the base of the PR and between 80e95ce and a6cb4d4.

📒 Files selected for processing (2)
  • dash-spv/tests/dashd_sync/tests_transaction.rs
  • key-wallet/src/wallet/managed_wallet_info/coin_selection.rs
📝 Walkthrough

Walkthrough

This PR simplifies CoinSelector's spendability filtering by removing confirmation count and unconfirmed transaction configuration options, then adds comprehensive test coverage for mempool transaction spending scenarios. The CoinSelector now relies solely on Utxo::is_spendable(current_height), and new tests verify that transactions can spend both unconfirmed change outputs and unconfirmed incoming transactions.

Changes

Mempool and Unconfirmed Transaction Handling

Layer / File(s) Summary
CoinSelector API Simplification
key-wallet/src/wallet/managed_wallet_info/coin_selection.rs
Removed min_confirmations and include_unconfirmed fields and builder methods from CoinSelector. Filtering in select_coins_with_size and Random strategy now relies only on Utxo::is_spendable(current_height).
Test Infrastructure and Helpers
dash-spv/tests/dashd_sync/tests_transaction.rs
Expanded imports for transaction building and async primitives. Added MEMPOOL_TIMEOUT constant, reserve_first_address (derives unused BIP44 external address), and build_and_sign (constructs and signs transactions using TransactionBuilder).
Mempool Transaction Tests
dash-spv/tests/dashd_sync/tests_transaction.rs
Added test_spend_change_balance to verify spending unconfirmed change UTXOs from a prior transaction. Added test_spend_incoming_balance to verify spending unconfirmed incoming transactions. Both tests wait for mempool detection and verify transaction dependency chains.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A selector hops with lighter stride,
Unconfirmed coins no longer hide,
Two tests dance through mempool streams,
Spending change and incoming dreams!
What simplicity, what clarity,
The rabbit's coin now flows free!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: removing CoinSelector's confirmation filters to allow spending mempool UTXOs, which aligns directly with the core objective of fixing duplicate coin-selection logic that rejected spendable mempool UTXOs.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/coin-selector-spend-mempool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@key-wallet/src/wallet/managed_wallet_info/coin_selection.rs`:
- Around line 86-91: The doc comment on CoinSelector still references removed
methods (`with_min_confirmations`, `exclude_unconfirmed`/`include_unconfirmed`)
causing broken intra-doc links; update the comment on CoinSelector to remove
those link references and instead state that spendability is determined solely
by Utxo::is_spendable(current_height) (which checks lock status and coinbase
maturity), and that callers who need stricter policies must pre-filter the UTXO
iterator before calling select_coins; mention CoinSelector and select_coins by
name so readers can locate the relevant API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f8abd56-6f53-4a17-87c6-376496243a9b

📥 Commits

Reviewing files that changed from the base of the PR and between 34f574d and 80e95ce.

📒 Files selected for processing (2)
  • dash-spv/tests/dashd_sync/tests_transaction.rs
  • key-wallet/src/wallet/managed_wallet_info/coin_selection.rs

Comment thread key-wallet/src/wallet/managed_wallet_info/coin_selection.rs Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 71.91%. Comparing base (6e48993) to head (a6cb4d4).
⚠️ Report is 2 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
...t/src/wallet/managed_wallet_info/coin_selection.rs 66.66% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #748      +/-   ##
=============================================
+ Coverage      71.73%   71.91%   +0.17%     
=============================================
  Files            321      320       -1     
  Lines          70013    69451     -562     
=============================================
- Hits           50227    49948     -279     
+ Misses         19786    19503     -283     
Flag Coverage Δ
core 76.30% <ø> (ø)
ffi 46.50% <ø> (-0.89%) ⬇️
rpc 20.00% <ø> (ø)
spv 87.91% <ø> (+0.53%) ⬆️
wallet 71.22% <66.66%> (+0.46%) ⬆️
Files with missing lines Coverage Δ
...t/src/wallet/managed_wallet_info/coin_selection.rs 81.03% <66.66%> (+2.29%) ⬆️

... and 22 files with indirect coverage changes

@ZocoLini ZocoLini force-pushed the fix/coin-selector-spend-mempool branch from 80e95ce to a6cb4d4 Compare May 9, 2026 01:46
@xdustinface xdustinface merged commit 5313086 into v0.42-dev May 9, 2026
37 checks passed
@xdustinface xdustinface deleted the fix/coin-selector-spend-mempool branch May 9, 2026 02:40
xdustinface added a commit that referenced this pull request May 9, 2026
#751)

`#740` moved `create_test_wallet` from `dashd_sync/setup.rs` to `dash_spv::test_utils` and changed `setup.rs` to a private re-import. `#748` was authored against an earlier base where `setup.rs` still defined a local `pub(super) fn create_test_wallet`, so the new tests in `tests_transaction.rs` imported it via `super::setup`. Each PR was green against its own base, but their merged state fails to compile because the `super::setup` import is now private.

Switch the import to `dash_spv::test_utils`, matching `tests_basic.rs`, `tests_mempool.rs`, `tests_multi_wallet.rs`, and `tests_restart.rs`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants