Repository navigation
feat(key-wallet)!: let a build exclude outpoints from coin selection - #1100
romchornyi wants to merge 5 commits into
Conversation
TransactionBuilder::exclude_outpoints names coins a build must never spend. A funding candidate that is excluded is left out of selection; an input the caller seeded with add_inputs that is excluded fails the build with BuilderError::ExcludedInput, since the caller asked for exactly that coin. Order-independent, accumulates, and records nothing on the wallet. For coins a caller holds back for now — e.g. the outputs of a send whose broadcast outcome is not known yet, which no node would accept a child of. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elease path Review follow-ups to exclude_outpoints: - ExcludedInput names the first excluded input in seeding order. It was picked from a HashSet, so with two excluded seeded inputs the reported outpoint changed from run to run. - Seeded inputs are tracked as one index range per add_inputs call and read only when an exclusion is set, instead of a per-input HashSet insert on every build. - The doc says what the exclusion does next to the existing rules: an excluded funding candidate is dropped like a reserved or locked one; an excluded seeded input is refused, unlike a reserved or non-final seeded input, which is dropped. Wallet-level helpers (build_and_sign_transaction and its funded builder) take no exclusions. - The "refused build reserves nothing" assertion could not fail (the refusal returns before selection). Replaced by a test that excludes a funding candidate, fails the sign after selection reserved the other coin, and checks nothing is left reserved. BuilderError is not #[non_exhaustive], so the ExcludedInput variant added in the previous commit breaks downstream code that matches it exhaustively. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A caller explaining a failed selection (no candidates left after an exclusion) needs the amount that was requested; the builder kept it private. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The excluded-seeded-input check sliced `inputs` by stored ranges and relied on nothing reordering it in between — a future change could make that panic. An ordered list of the seeded outpoints gives the same "first in seeding order" answer with no index bookkeeping. outputs() now says what it does not include (a special payload's credit outputs; the placeholder amount under SelectionStrategy::All), and the failed-sign release test asserts SigningFailed, so it proves selection ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… presence Read-only getters a caller needs to explain a failed selection at the build's own fee rate, and to tell a drain or a special-payload build — whose requested amount outputs() does not show — apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: dashpay/rust-dashcore/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughTransactionBuilder now tracks excluded outpoints and caller-seeded inputs. Assembly rejects an excluded seeded input, filters excluded funding candidates, and returns the existing coin-selection error if the remaining candidates are insufficient. ChangesTransaction input exclusions
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Feature Suggested reviewers: Merge Risk: ⚪ Minimal · up to This adds optional per-build outpoint exclusions to the transaction builder, with tests covering selection, ordering, and cleanup. No merge-blocking risk was identified. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The checked paths enforce exclusions before selection and reservation without adding spend authority or a persistent wallet lock. The restriction is opt-in for each build, however, and the external payment integration was not available to verify complete enforcement. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #1100 +/- ##
==========================================
+ Coverage 77.34% 77.45% +0.11%
==========================================
Files 329 320 -9
Lines 83857 81465 -2392
==========================================
- Hits 64855 63100 -1755
+ Misses 19002 18365 -637
|
|
Bots are done — your move: post |
|
/self-reviewed |
|
Ready for review — |
ZocoLini
left a comment
There was a problem hiding this comment.
I don't think key-wallet needs this. require_final_inputs() already drops every candidate that is neither confirmed nor IS-locked, from both add_funding and add_inputs, and writes nothing to the wallet. Change from a send that never reached the network never gets an IS lock, so it is never selected, and nothing gets built on top of it. The only difference is the "accepted, not locked yet" window, which by your own e2e is under 1 s. e2e is under 1 s.
A per-build outpoint list also leaves the policy outside the wallet, so any path that doesn't pass it still spends the phantom change. Suggest calling .require_final_inputs() in platform's payment builds and closing this.
A different option is to drop require final inputs and always filter out those inputs that cannot be consider final. This change only makes sense if you are open to have users waiting a little bit for their tx to be ISlocked, so it is up to the mobile and platform team, I dont have a strong opinion on this
|
Bots are done — your move: address ZocoLini requested changes, then post |
|
Thanks @ZocoLini — you're right, Checked on testnet on the same scenarios as the outpoint-exclusion version: a send that ended "status unknown" with P2P blocked, then an immediate second payment — refused, nothing built on the unconfirmed change, spendable again right after the first send's IS lock; the same with the app killed and restarted in between; and a fresh incoming payment spent ~20 s after it arrived, already IS-locked, so the extra wait isn't noticeable in practice. Same results, much less code, and no dependency on probing being enabled. Closing. |
Issue being fixed or feature implemented
A wallet that broadcasts a transaction and gets no answer from the network ("status unknown") keeps the transaction in its mempool view, so its change looks spendable. If that transaction never reached the network, every later payment built on the change is an orphan no node accepts — the wallet keeps sending coins the chain never had. dashpay/platform's wallet now tracks such unresolved sends and needs to keep their outputs out of coin selection until the network confirms them, without writing anything into wallet state (a lock that a cancelled build forgets to undo would freeze the coin).
TransactionBuilderhad no way for a caller to say "do not offer these coins" for one build.What was done?
TransactionBuilder::exclude_outpoints(iter)— a per-build set of outpoints coin selection never offers. It accumulates and works in any call order relative toadd_funding/add_inputs. It is applied inassemble_unsigned, the one place selection runs, so it coversadd_funding,add_funding_reservation_onlyand seeded inputs alike. Nothing is written to the wallet.add_inputs) is a hard error,BuilderError::ExcludedInput { outpoint }, returned before anything is reserved — the caller asked for that coin explicitly. It names the first excluded input in seeding order, so the error is deterministic.outputs()(explicit outputs only — not special-payload credit outputs, and only a placeholder underSelectionStrategy::All),fee_rate(),selection_strategy(),has_special_payload().Wallet-level helpers (
build_and_sign_transactionand its funded builder) take no exclusions; the API doc says so.Related: #1078 adds a persistent locked-outpoint set on
ManagedWalletInfofor masternode collateral. That is wallet state and outlives a build; this is a per-build exclusion the caller recomputes each time. They don't overlap.How Has This Been Tested?
cargo test -p key-wallet --lib: 689 passed, 18 ignored. New tests: an excluded candidate is never selected; an excluded seeded input fails withExcludedInputand reserves nothing; with two excluded seeded inputs the error names the first, stable over repeated builds; an excluded candidate plus a signing failure after selection leaves no reservation (assertsSigningFailed, so the release path is exercised).cargo clippyandcargo fmt --checkclean on key-wallet; key-wallet-ffi, key-wallet-manager and dash-spv type-check.[patch]: platform-wallet 1321/1321 tests pass against this branch.Breaking Changes
BuilderErrorgains the variantExcludedInput. The enum is not#[non_exhaustive], so a downstream exhaustivematchon it no longer compiles. In this workspace the only users go throughto_string().Checklist:
For repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
2e0202f/self-reviewedkey-wallet(key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs) — QuantumExplorer or ZocoLini or xdustinfaceWhen every box is checked the
PR Hygienecheck passes and this can merge.Summary by CodeRabbit