Skip to content

feat(key-wallet): owner-tagged reservations to close the broadcast-release TOCTOU (platform#4185) - #916

Open
bfoss765 wants to merge 6 commits into
dashpay:devfrom
bfoss765:feat/owner-tagged-reservations
Open

feat(key-wallet): owner-tagged reservations to close the broadcast-release TOCTOU (platform#4185)#916
bfoss765 wants to merge 6 commits into
dashpay:devfrom
bfoss765:feat/owner-tagged-reservations

Conversation

@bfoss765

@bfoss765 bfoss765 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Why this PR exists

This adds an owner-tagged reservation capability to key-wallet to close a real time-of-check/time-of-use double-spend window that the platform layer (dashpay/platform#4185) cannot fix on its own.

The bug it fixes: ReservationSet currently maps OutPoint → reserved-at-height with no per-reservation owner, and release removes by outpoint unconditionally. Platform's deferred-broadcast / asset-lock path reserves a transaction's inputs, .awaits the network broadcast, and on a Rejected result releases those inputs by outpoint. If key-wallet's TTL sweep reclaims the reservation during that await and a different concurrent build re-reserves the same outpoint (same wallet generation, so any Arc::ptr_eq generation guard still passes), the rejected build's cleanup frees the other build's reservation — opening a double-spend window. Platform cannot detect this, because the sweep happens invisibly inside key-wallet; the "release only if it's still mine" check must be atomic under the ReservationSet mutex. That's what this PR provides.

What changed

New public type ReservationToken (re-exported from crate root + managed_account): opaque Copy newtype over a private u64, with no public constructor — a token can only be obtained from reserve(), so it is forge-proof and cheap to hold across an await. Height cannot serve as the identity (it collides across same-height reserves).

ReservationSet (still pub(crate)):

  • reserve(&[OutPoint], height) -> ReservationToken — now returns a fresh per-call token stamping all outpoints; re-reserving transfers ownership to the new token.
  • release_if_owner(&[OutPoint], ReservationToken) (new) — atomically removes only outpoints whose stored owner == the token; a no-op for swept / re-reserved / unreserved outpoints.
  • release(...) (kept, unconditional) — doc now scopes it to the known-spent path only.
  • Storage: HashMap<OutPoint, Reservation { reserved_at_height, owner }> + a next_token counter; TTL sweep preserved.

Public seam so consumers can adopt the fix (all additive):

  • TransactionBuilder::build_unsigned_reserved() / build_signed_reserved() → return the extra Option<ReservationToken>.
  • ManagedCoreFundsAccount::release_reservation_if_owner(&Transaction, ReservationToken) — the owner-guarded release a Rejected path should call instead of the unconditional release_reservation.
  • AssetLockResult.reservation_token: Option<ReservationToken> — carries the token out of the asset-lock build.

The sign-failure path in build_signed (which also awaits) was switched to release_if_owner. The processed-spend release stays unconditional (the coin is genuinely spent).

Back-compat

  • build_unsigned / build_signed and release_reservation keep their existing signatures (they thin-delegate); the ~20 in-repo callers are untouched.
  • ReservationSet::reserve's changed return is pub(crate), zero external impact; all in-repo sites updated.
  • key-wallet-manager, dash-spv, key-wallet-ffi don't reference reservations; they still compile.

Validation

cargo build -p key-wallet -p key-wallet-manager -p dash-spv -p key-wallet-ffi clean; cargo test -p key-wallet --lib = 552 passed, 0 failed (13/13 reservation tests incl. the TOCTOU regression: reserve X→token A, simulate sweep + re-reserve X→token B, assert release_if_owner([X], A) does NOT free X); clippy + doc clean.

Scope note

The asset-lock flow (the one platform#4185 cites) is wired end-to-end. The general manager send wrappers (build_and_sign_transaction*) still return (Transaction, u64) and drop the token — platform's #4185 adoption can call build_signed_reserved directly; happy to thread the token through those wrappers too if preferred.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added ownership tracking for funding-input reservations using optional tokens.
    • Asset-lock results and reservation-aware transaction builds now expose tokens for guarded cleanup.
    • Added masternode lookup by voting key, returning matching registration transaction hashes in ascending order.
  • Bug Fixes

    • Prevented stale or failed transaction builds from releasing reservations owned by concurrent builds.
    • Improved safety during reservation expiry and re-reservation races by releasing only still-owned reservations.

…se the broadcast-release TOCTOU (platform#4185)

ReservationSet previously mapped OutPoint -> reserved-at-height with no
per-reservation owner, and released unconditionally by outpoint. The platform
broadcast path (dashpay/platform#4185) reserves an asset-lock/deferred send's
inputs, awaits the broadcast, and on a Rejected result releases them by
outpoint. If key-wallet's TTL sweep reclaims that reservation mid-await and a
different concurrent build re-reserves the same outpoint (same wallet
generation, so any Arc::ptr_eq generation guard still passes), the rejected
build's cleanup frees the OTHER build's reservation -> a double-spend window.
The platform layer cannot detect this because the sweep is invisible inside
key-wallet; the "release only if still mine" check must be atomic under the
ReservationSet mutex.

Stamp every reservation with a ReservationToken (a Copy newtype over a
monotonic per-set counter, unique even across same-height reserves). reserve()
returns the token it stamped; release_if_owner(outpoints, token) removes only
outpoints still owned by that token, atomically under the mutex, and is a no-op
for any swept-and-re-reserved outpoint. The unconditional release() is kept for
the processed-spend path, where the coin is genuinely spent and must leave the
set regardless of owner.

Wiring:
- assemble_unsigned returns the stamped token; build_signed's sign-failure
  path now releases owner-guarded (it too awaits, so it shares the hazard).
- Additive token-returning builds: build_unsigned_reserved /
  build_signed_reserved. build_unsigned / build_signed keep their signatures.
- Public seam for platform: ManagedCoreFundsAccount::release_reservation_if_owner
  and AssetLockResult::reservation_token; ReservationToken re-exported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f74078f4-cf81-4e87-b6bf-8866bc01e89a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Reservation ownership tokens are introduced for UTXO reservations, propagated through reserved transaction and asset-lock builders, and exposed through managed account and crate APIs. A masternode voting-key lookup helper and tests are also added.

Changes

Reservation ownership flow

Layer / File(s) Summary
Reservation ownership model
key-wallet/src/managed_account/reservation.rs
Reservations now store per-build ReservationToken values, support owner-guarded release, and test token uniqueness, ownership checks, and sweep/re-reserve behavior.
Reservation-aware transaction building
key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs
Reserved unsigned and signed build paths return optional reservation tokens and use owner-guarded cleanup after signing failures.
Asset-lock and account integration
key-wallet/src/managed_account/..., key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs, key-wallet/src/lib.rs
Asset-lock results expose reservation tokens, managed accounts add guarded release, and ReservationToken is re-exported publicly.

Masternode voting-key lookup

Layer / File(s) Summary
Voting-key lookup and coverage
dash/src/sml/masternode_list/masternode_helpers.rs
MasternodeList now returns registration transaction hashes for matching voting keys. Tests cover multiple matches, unique matches, ordering, and empty results.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ManagedWalletInfo
  participant TransactionBuilder
  participant ReservationSet
  participant Signer
  ManagedWalletInfo->>TransactionBuilder: build_signed_reserved
  TransactionBuilder->>ReservationSet: reserve selected outpoints
  ReservationSet-->>TransactionBuilder: ReservationToken
  TransactionBuilder->>Signer: sign transaction
  Signer-->>TransactionBuilder: signed transaction or failure
  TransactionBuilder->>ReservationSet: release_if_owner on failure
  TransactionBuilder-->>ManagedWalletInfo: transaction, fee, token
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the owner-tagged reservation change and its purpose of closing the broadcast-release TOCTOU issue.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs (1)

219-252: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reservation tokens are captured but never used to guard cleanup on downstream (non-signing) failures. Both asset-lock builders take a reservation via build_signed_reserved and get back reservation_token, but a failure in the code that runs after the successful signed build (credit-key derivation, or the signer round-trip/bookkeeping) returns Err without releasing it — stranding the already-signed transaction's reserved inputs until the 24-block TTL sweep, since the caller never sees the token to release it itself.

  • key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs#L219-L252: in build_asset_lock, clone the funding account's ReservationSet before the credit-key derivation loop and call release_if_owner on the transaction's outpoints when resolve_funding_account/next_private_key fails, before returning Err.
  • key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs#L293-L357: in build_asset_lock_with_signer, apply the same guarded-release pattern around the Phase 1–3 bookkeeping loop (resolve_funding_account, peek_next_path, signer.public_key(&path).await, mark_first_pool_index_used).
🤖 Prompt for 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.

In `@key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs` around lines
219 - 252, Add guarded reservation cleanup in
key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs:219-252 within
build_asset_lock by cloning the funding account’s ReservationSet before
credit-key derivation and calling release_if_owner on the signed transaction’s
outpoints whenever resolve_funding_account or next_private_key fails. Apply the
same pattern in
key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs:293-357 within
build_asset_lock_with_signer for failures during the Phase 1–3 bookkeeping
operations, including resolve_funding_account, peek_next_path,
signer.public_key, and mark_first_pool_index_used; preserve successful returns
and only release reservations owned by this build.
🤖 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.

Outside diff comments:
In `@key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs`:
- Around line 219-252: Add guarded reservation cleanup in
key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs:219-252 within
build_asset_lock by cloning the funding account’s ReservationSet before
credit-key derivation and calling release_if_owner on the signed transaction’s
outpoints whenever resolve_funding_account or next_private_key fails. Apply the
same pattern in
key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs:293-357 within
build_asset_lock_with_signer for failures during the Phase 1–3 bookkeeping
operations, including resolve_funding_account, peek_next_path,
signer.public_key, and mark_first_pool_index_used; preserve successful returns
and only release reservations owned by this build.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 392d56f0-13b2-454e-aea8-f0de66b4c248

📥 Commits

Reviewing files that changed from the base of the PR and between 70d4bf8 and e99959c.

📒 Files selected for processing (6)
  • key-wallet/src/lib.rs
  • key-wallet/src/managed_account/managed_core_funds_account.rs
  • key-wallet/src/managed_account/mod.rs
  • key-wallet/src/managed_account/reservation.rs
  • key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs
  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.60870% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.79%. Comparing base (9cbe4e7) to head (2a56bb0).

Files with missing lines Patch % Lines
...c/wallet/managed_wallet_info/asset_lock_builder.rs 79.68% 13 Missing ⚠️
.../src/managed_account/managed_core_funds_account.rs 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #916      +/-   ##
==========================================
+ Coverage   74.76%   74.79%   +0.02%     
==========================================
  Files         328      328              
  Lines       76593    76765     +172     
==========================================
+ Hits        57267    57415     +148     
- Misses      19326    19350      +24     
Flag Coverage Δ
core 77.35% <100.00%> (+0.05%) ⬆️
ffi 49.32% <ø> (-0.41%) ⬇️
rpc 20.00% <ø> (ø)
spv 91.12% <ø> (+0.17%) ⬆️
wallet 75.75% <89.94%> (+0.06%) ⬆️
Files with missing lines Coverage Δ
dash/src/sml/masternode_list/masternode_helpers.rs 75.00% <100.00%> (+49.19%) ⬆️
key-wallet/src/managed_account/reservation.rs 100.00% <100.00%> (ø)
.../wallet/managed_wallet_info/transaction_builder.rs 87.39% <100.00%> (+0.34%) ⬆️
.../src/managed_account/managed_core_funds_account.rs 78.99% <0.00%> (-0.70%) ⬇️
...c/wallet/managed_wallet_info/asset_lock_builder.rs 89.61% <79.68%> (-1.11%) ⬇️

... and 23 files with indirect coverage changes

bfoss765 added a commit to bfoss765/platform that referenced this pull request Jul 23, 2026
Point all rust-dashcore crates at bfoss765/rust-dashcore
e99959ced0062159d629930f488374e29f63c42b (PR dashpay/rust-dashcore#916),
which is v4.1-dev's rust-dashcore tip 70d4bf8 plus the additive
owner-tagged reservation API: key_wallet::ReservationToken,
ReservationSet::reserve/release_if_owner,
TransactionBuilder::build_{unsigned,signed}_reserved,
ManagedCoreFundsAccount::release_reservation_if_owner, and
AssetLockResult.reservation_token. Additive over 70d4bf8, so it stays
compatible with the rest of the v4.1-dev workspace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tream failures

Addresses the CodeRabbit Major (PR dashpay#916, asset_lock_builder.rs :219-252 /
:293-357). Both asset-lock builders took a reservation via
build_signed_reserved and captured its reservation_token, but a failure in
the code that runs AFTER the successful signed build — credit-key derivation
in build_asset_lock, and the Phase 1-3 signer/bookkeeping loop in
build_asset_lock_with_signer — returned Err without releasing the reservation.
The already-signed transaction's inputs then stranded until the 24-block TTL
sweep, since the caller never received the token to release them itself.

Clone the funding account's ReservationSet (a shared Arc view) before each
loop re-borrows self.accounts, and on any failure release only THIS build's
reservation with the owner-guarded release_if_owner(&reserved, token) — never
the unconditional release, which could free a concurrent build's inputs that
the TTL sweep + re-reserve handed over during this build (the very TOCTOU of
dashpay/platform#4185 this PR closes). The success path is unchanged: a
successful build consumes the reservation as designed, and the signer-failure
release inside build_signed_reserved returns via `?` before this new code
runs, so there is no double-release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 24, 2026
@github-actions github-actions Bot added ready-for-review CodeRabbit has approved this PR merge-conflict The PR conflicts with the target branch. labels Jul 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

Adds a by-voting-key lookup (returns matching pro_reg_tx_hashes) so SDK
consumers can resolve which masternodes an imported voting key controls
without the legacy dashj masternode list — needed for contested-username
voting after the dashj engine is held post-cutover. Data was already
present on every entry (key_id_voting, pro_reg_tx_hash); this is the
missing index/helper plus a unit test covering multi-match, single-match
and no-match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
dash/src/sml/masternode_list/masternode_helpers.rs (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Split the voting-key lookup from the reservation-token changes.

The supplied stack context shows no dependency between these cohorts. Split this public MasternodeList feature into a focused PR. After the split, use an allowed feat: title prefix for this new API. The supplied context does not include the actual PR title, so verify its prefix separately.

As per path instructions, “If a PR mixes unrelated concerns … suggest splitting it into separate focused PRs.”

🤖 Prompt for 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.

In `@dash/src/sml/masternode_list/masternode_helpers.rs` at line 6, Split the
voting-key lookup changes from the reservation-token changes into separate
focused PRs, keeping the public MasternodeList API work with the voting-key
feature. For the resulting voting-key PR, verify and update the PR title to use
the allowed feat: prefix.

Source: Path instructions

🤖 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 `@dash/src/sml/masternode_list/masternode_helpers.rs`:
- Around line 116-121: Document in masternodes_by_voting_key that matching
masternodes are returned in ascending ProTxHash order, based on the underlying
sorted BTreeMap. Update the test to assert the returned order directly rather
than sorting matched before checking the expected seed values.

---

Nitpick comments:
In `@dash/src/sml/masternode_list/masternode_helpers.rs`:
- Line 6: Split the voting-key lookup changes from the reservation-token changes
into separate focused PRs, keeping the public MasternodeList API work with the
voting-key feature. For the resulting voting-key PR, verify and update the PR
title to use the allowed feat: prefix.
🪄 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 Plus

Run ID: edd2b510-be53-47ae-bb49-57425fed6b0e

📥 Commits

Reviewing files that changed from the base of the PR and between b3c216a and 4d927c1.

📒 Files selected for processing (1)
  • dash/src/sml/masternode_list/masternode_helpers.rs

Comment thread dash/src/sml/masternode_list/masternode_helpers.rs Outdated
bfoss765 added a commit to bfoss765/platform that referenced this pull request Aug 1, 2026
All eight workspace rust-dashcore dependencies were redirected at
bfoss765/rust-dashcore solely to pick up
`MasternodeList::masternodes_by_voting_key`. That revision also carried ~528
lines of unrelated owner-tagged-reservation and asset-lock behaviour, and the
upstream PR carrying the helper (dashpay/rust-dashcore#916) is
CHANGES_REQUESTED, conflicting against dev, and has been asked to split — so
the pin had no bounded lifetime.

Revert the pin to the Dash-owned revision v4.2-dev already tracks
(70d4bf8e36057c58e02d56769a6e9760f701dd06) and implement the filter locally in
`spv/runtime.rs` as a private free function over the engine's current-tip
MasternodeList. It reads only long-standing public SML fields (`key_id_voting`,
`pro_reg_tx_hash`, both present on that revision), and folds in the
`ProTxHash -> [u8; 32]` internal-byte-order conversion the caller previously
did itself, so `masternodes_by_voting_key_blocking` gets shorter rather than
longer. Four unit tests cover multi-match, single-match, no-match and the
empty-list case.

Swapping back to the upstream helper once dashpay#916 lands is tracked by dashpay#4262, cited
in a doc comment at the duplication site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bfoss765

bfoss765 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up on a downstream cleanup that this PR unblocks, for whoever merges it.

The last commit here — 4d927c15 (feat(sml): masternodes_by_voting_key lookup on MasternodeList) — is consumed by dashpay/platform#4258, which surfaces a by-voting-key masternode lookup to Kotlin so contested-username voting keeps working once the legacy dashj masternode list is retired.

Because this PR is still open, platform#4258 could not pin to a revision carrying the helper without redirecting all eight of its workspace rust-dashcore dependencies at a personal fork — which would also have dragged in the owner-tagged-reservation and asset-lock changes from the other three commits here. That pin was rejected in review on #4258, and since this PR is CHANGES_REQUESTED, conflicting against dev, and has been asked to split, there was no bounded wait to hold out for.

So platform#4258 now stays on the Dash-owned rust-dashcore revision and carries a small local copy of the filter (14 lines over MasternodeList.masternodes, using only the long-standing public key_id_voting / pro_reg_tx_hash fields).

When masternodes_by_voting_key lands upstream — from this PR or from whatever split it ends up in — platform can delete that duplication and call the real helper. Tracked downstream by dashpay/platform#4262.

bfoss765 and others added 2 commits August 1, 2026 15:19
Resolves the conflict flagged on PR dashpay#916 against the current `dev`
(9cbe4e7). Only one file conflicted:
`key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs`, in two
places, both the same shape.

`dev`'s dashpay#915 (CoinJoin funding account + drain mode) refactored both
asset-lock builders from a single chained `TransactionBuilder::new()...`
expression into a two-step `let mut builder = ...` so it can
conditionally apply `set_selection_strategy(SelectionStrategy::All)` for
drain mode. This branch had changed the same statement's destructuring to
pick up the new third element, the `Option<ReservationToken>`.

Resolution takes `dev`'s two-step builder form and moves this branch's
three-element destructuring down onto the `let (transaction, fee, ...) =
builder` line that consumes it, i.e. both changes are kept:

    let (transaction, fee, reservation_token) = builder
        .set_funding(...)
        .require_final_inputs()
        .build_signed_reserved(...)
        .await?;

No semantic conflict underneath: `dev` did not touch
`managed_account/reservation.rs`, where the owner-tagged reservation
logic actually lives, and the owner-guarded release blocks added by
b3c216a still sit after the build in both builders, unchanged.
`managed_core_funds_account.rs` and `transaction_builder.rs` auto-merged.

Validated after the merge:
  cargo build -p key-wallet -p key-wallet-manager -p dash-spv \
              -p key-wallet-ffi -p dashcore   -> clean
  cargo test -p key-wallet --lib              -> 603 passed, 0 failed
    (incl. 23 reservation tests, TOCTOU regression among them)
  cargo test -p dashcore --lib                -> 577 passed, 0 failed
  cargo test -p key-wallet-manager --lib      ->  49 passed, 0 failed
  cargo test -p dash-spv --lib                -> 494 passed, 0 failed
  cargo clippy -p key-wallet -p dashcore --all-targets -- -D warnings
                                              -> exit 0, 0 warnings

Note for the platform#4185 re-pin: this branch's own public API
(`ReservationToken`, `build_unsigned_reserved`/`build_signed_reserved`,
`ManagedCoreFundsAccount::release_reservation_if_owner`,
`AssetLockResult::reservation_token`) is unchanged by this merge. However
`dev`'s dashpay#915 independently changed `build_asset_lock` /
`build_asset_lock_with_signer` to take `AssetLockFundingAccount` instead
of a bare `account_index`, plus a new `drain: bool`. That is a `dev`-side
break that #4185 will hit on re-pin regardless of this PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing_key

Addresses the one unresolved CodeRabbit review thread on PR dashpay#916
(masternode_helpers.rs:116-121): the function returned matches in
`BTreeMap` order but the public docs never promised an order, and the
test hid that by sorting the result before asserting on it.

Document the ordering as a guaranteed part of the API rather than an
implementation accident, and say why it holds: `masternodes` is a
`BTreeMap<ProTxHash, _>` (std guarantees ascending-key iteration) and
`ProTxHash` derives `Ord` over its 32 internal bytes. Also note that
because `ProTxHash` is `#[hash_newtype(forward)]`, the internal byte
order is the same order its hex `Display` reads in — so the sequence is
sorted the way it prints, which is the non-obvious part for a codebase
that reverses byte order on several other hash types.

Test changes:
  - `masternodes_by_voting_key_filters_and_collects` no longer sorts the
    result. It asserts the whole `Vec` at once, so it now pins ordering
    as well as contents.
  - New `masternodes_by_voting_key_returns_ascending_pro_tx_hash_order`
    seeds the list in DESCENDING proTxHash order, so a result that merely
    echoed insertion order would come back reversed and fail. It also
    asserts strict ascent through the public `Ord` impl, which is the
    comparison callers would actually use.

The ordering claim is verified by the new test, not assumed.

Also fixes a `cargo fmt` violation in this file's `list_from` test helper
that was already present on the branch (`cargo fmt --all -- --check` was
failing before this commit and passes after).

  cargo fmt --all -- --check   -> clean
  cargo test -p dashcore --lib -> 577 passed, 0 failed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bfoss765

bfoss765 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto dev — conflicts resolved, PR is now mergeable.

Addressed the outstanding review items:

  • masternodes_by_voting_key ordering: documented ascending ProTxHash order as a guaranteed API property (BTreeMap<ProTxHash, _> + derived Ord; ProTxHash is #[hash_newtype(forward)], so internal byte order equals hex-Display order — worth stating explicitly in a codebase where ScoreHash reverses). Dropped the sort from the existing test so it pins order as well as contents, and added a test that seeds the list in descending order so an insertion-order implementation would fail.
  • The earlier reservation-stranding finding on the asset-lock builders was already resolved in b3c216ac (clones the ReservationSet before each bookkeeping loop, release_if_owner on downstream failure in both builders).
  • Fixed a pre-existing cargo fmt violation in the list_from helper.

cargo test: key-wallet 603, dashcore 577, key-wallet-manager 49, dash-spv 494 — all passing. cargo fmt --all -- --check and cargo clippy --all-targets -- -D warnings both clean.

Note on priority: platform#4185 pins all eight crates to this branch, so it cannot merge until this lands upstream.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@bfoss765 I will review the rebased changes, including the ordering contract and reservation cleanup paths.


✏️ Learnings added
Learnt from: bfoss765
URL: https://github.com/dashpay/rust-dashcore/pull/916

Timestamp: 2026-08-01T19:23:39.543Z
Learning: In `dash/src/sml/masternode_list/masternode_helpers.rs`, `MasternodeList::masternodes_by_voting_key` guarantees results in ascending `ProTxHash` order. The order follows the `BTreeMap<ProTxHash, _>` key order, and `ProTxHash` uses forward byte order that matches its hexadecimal `Display` order.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot added ready-for-review CodeRabbit has approved this PR and removed merge-conflict The PR conflicts with the target branch. ready-for-review CodeRabbit has approved this PR labels Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant