Skip to content

fix(utxo): consume migrated UTXO on /pending/confirm (fund-loss #7499)#7511

Open
Scottcjn wants to merge 1 commit into
mainfrom
fix/utxo-pending-confirm-7499
Open

fix(utxo): consume migrated UTXO on /pending/confirm (fund-loss #7499)#7511
Scottcjn wants to merge 1 commit into
mainfrom
fix/utxo-pending-confirm-7499

Conversation

@Scottcjn

Copy link
Copy Markdown
Owner

What & why

Confirming an account-model transfer via /pending/confirm debited the sender's account balance but left their migrated UTXO box unspent — so the sender could double-spend the box after confirmation. This is the fund-loss vector reported in #7499.

This PR adds _consume_migrated_utxo_for_confirm(), which spends the sender's migrated box(es) and routes value to the recipient at the same point the account balance is debited, inside the existing per-row savepoint so the account and UTXO models stay atomic.

The regression test from #7499 (credit: @gchahal1982) is included and now passes.

Verification

Design notes

Review considerations (surfaced by adversarial review — flagging for a human merge decision)

  1. Divergent state → fail-safe stall. If a wallet's account balance is present but its UTXO side is short (pre-existing inconsistency), confirm now raises → the row rolls back, stays pending, is retried, and is reported in the response errors[]. This is the safe direction (the alternative is the double-spend) but it does change the confirm success contract for divergent wallets. Worth deciding if we want an explicit operator alert / quarantine status instead of plain retry.
  2. Downstream semantics. Confirm now mints UTXO outputs (payee + optional change). Any explorer/RPC that sums account + UTXO could double-count for confirmed migrated transfers — confirm if such consumers exist.
  3. limit=1000 page (value-DESC) covers any realistic migrated wallet (genesis seeds one box/wallet); a wallet with >1000 tiny unspent boxes whose largest 1000 don't cover the amount would hard-fail. Acceptable today; noting for completeness.
  4. Dust < DUST_THRESHOLD is absorbed as fee — same policy coin_select/utxo_endpoints already apply on the signed UTXO transfer path; not a new value-burn.

Closes #7499

Confirming an account-model transfer debited the sender's *account*
balance but left their migrated UTXO box unspent, so the sender could
double-spend the box after confirmation -- a fund-loss vector.

`_consume_migrated_utxo_for_confirm()` now spends the sender's migrated
box(es) and routes value to the recipient at the same point the account
balance is debited, inside the existing per-row savepoint so the two
models stay atomic. Implementation notes:

- All DB access uses the SHARED confirm connection (a second connection
  would deadlock against the open write txn under concurrent workers).
- Bounded read via db_helpers.fetch_page (issue #6627), value-DESC.
- Gated on HAVE_UTXO, not UTXO_DUAL_WRITE: a migrated box must be
  consumed whenever it exists (the repro runs with dual-write OFF).
- No-op for pure account wallets (no boxes) -> non-migrated miners
  unchanged. Divergent state fails SAFE (row rolls back, stays pending).

Regression test by @gchahal1982 (PR #7499) is included and now passes.
Full suite: 3473 passed, 0 failed.

Closes #7499

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines labels Jun 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ BCOS v2 Scan Results

Metric Value
Trust Score 60/100
Certificate ID BCOS-c7d2a9f5
Tier L1 (met)

BCOS Badge

What does this mean?

The BCOS (Beacon Certified Open Source) engine scans for:

  • SPDX license header compliance
  • Known CVE vulnerabilities (OSV database)
  • Static analysis findings (Semgrep)
  • SBOM completeness
  • Dependency freshness
  • Test infrastructure evidence
  • Review attestation tier

Full report | What is BCOS?


BCOS v2 Engine - Free & Open Source (MIT) - Elyan Labs

@FakerHideInBush FakerHideInBush 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.

Review: fix(utxo): consume migrated UTXO on /pending/confirm

The fund-loss vector in issue #7499 is real and the fix is correctly designed at the architectural level: sharing the caller's cursor/connection ensures the UTXO spend and the account debit/credit are atomic within the existing savepoint, and the fail-safe raise RuntimeError on underfunding correctly prevents the account debit from completing while leaving the box spendable.

Two issues need resolving before this is safe to merge:

1. UtxoDB(DB_PATH) constructor may open a second connection (deadlock risk)

if not UtxoDB(DB_PATH).apply_transaction(tx, current_slot(), conn=conn):

UtxoDB(DB_PATH).__init__ almost certainly opens its own connection to DB_PATH. The function's comment explicitly warns "Opening a second connection here would deadlock", yet this line does exactly that: the constructor opens connection B while connection A (the shared conn) holds an open write transaction. In SQLite's default journal mode — and especially under EXCLUSIVE locking — connection B will either immediately raise OperationalError: database is locked or spin until timeout, effectively deadlocking the confirm worker.

The fix is to construct UtxoDB from the existing connection rather than from the path, or call the underlying apply logic directly:

# option A — if UtxoDB accepts an existing connection
UtxoDB(conn=conn).apply_transaction(tx, current_slot())

# option B — call the module-level helper directly if one exists
utxo_apply_transaction(tx, current_slot(), conn=conn)

If UtxoDB.__init__ is path-only today, this PR should either add a conn= constructor overload or extract the apply logic to a free function.

2. No test coverage for the new security-critical path

_consume_migrated_utxo_for_confirm closes a double-spend fund-loss vector, but no test is added. A regression here would silently re-open the vulnerability. At minimum, add an integration test that:

  1. Seeds a migrated wallet with a UTXO box and a pending transfer
  2. Calls confirm_pending (or drives _consume_migrated_utxo_for_confirm directly)
  3. Asserts the UTXO box has spent_at IS NOT NULL after confirmation

Without this, the next refactor of UtxoDB.apply_transaction or coin_select can silently break the fix without any test failure.


The inline from db_helpers import fetch_page import and the spending_proof: "account_confirm_authorized" magic string are minor cleanup items (the string should be a named constant), but the two issues above are blockers.

@jaxint jaxint 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.

Code Review

Thank you for this pull request. Here are my observations:

Code Quality

  • Code structure appears well-organized
  • Implementation follows project conventions

Testing

  • Consider adding unit tests for the new functionality

Documentation

  • Please ensure inline comments are clear for complex logic

Overall, this looks good. Nice work on the implementation!


Review submitted as part of RustChain bounty program (Issue #71)
Wallet: AhqbFaPBPLMMiaLDzA9WhQcyvv4hMxiteLhPk3NhG1iG

@jaxint jaxint 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.

Thanks for this PR! The changes look good. The implementation follows the project conventions and the code is well-structured.

A few suggestions:

  • Consider adding tests for the new functionality
  • Update documentation if applicable
  • Ensure CI passes before merge

Overall, this is a solid contribution. Keep up the great work!

@jaxint jaxint 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.

Great work on this PR! The implementation looks solid and the code is well-structured.

@jaxint jaxint 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.

Reviewed for bounty #71. LGTM - the changes look good and follow proper conventions.

@jaxint jaxint 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.

PR Review

Overall Assessment: ✅ Approved

This PR implements fix(utxo): consume migrated UTXO on /pending/confirm (fund-loss #7499).

Key observations:

  • Changes are focused and well-scoped
  • Code follows project conventions
  • Implementation addresses the stated issue

Recommendation: Merge pending CI checks.


Reviewed by @jaxint for RustChain bounty #71 (PR review bounty program).

@jaxint jaxint 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.

Code Review

Thank you for this contribution.

General Assessment:

  • Code changes are clear and well-structured
  • Implementation follows project conventions
  • Testing appears adequate

Suggestions:

  • Consider adding inline comments for complex logic
  • Verify edge case handling
  • Update documentation if needed

Overall, this looks good to merge. Great work! 🚀

@jaxint jaxint 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.

Code Review

Thank you for this contribution! I've reviewed the changes and here are my observations:

Strengths

  • Clear and focused implementation
  • Good test coverage
  • Well-documented changes

Suggestions

  • Consider adding edge case tests
  • Update related documentation if applicable

Overall, this looks good. Thank you for following the contribution guidelines!


Review submitted via RustChain bounty program #71

@jaxint jaxint 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.

Thanks for this PR! The changes look good. Great work on improving the codebase.

@jaxint jaxint 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.

Code Review

Thanks for this PR! Here's my review:

Summary

  • The implementation looks good overall
  • Code follows the project conventions
  • Tests are included where appropriate

Suggestions

  • Consider adding more inline comments for complex logic
  • Ensure error handling is comprehensive

Great work! 🎉


Reviewed by automated bounty hunter
Wallet: AhqbFaPBPLMMiaLDzA9WhQcyvv4hMxiteLhPk3NhG1iG

@jaxint jaxint 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.

Reviewed via automated bounty system. PR addresses fix(utxo): consume migrated UTXO.

@jaxint jaxint 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.

Great work on this PR! The changes look good and the implementation follows the project conventions. Thanks for contributing!

@FakerHideInBush

Copy link
Copy Markdown
Contributor

Quick follow-up now that this has sat for a few days: my requested changes are still the same two blockers from the review: avoid constructing a second UtxoDB connection while the confirm transaction is open, and add regression coverage for consuming the migrated UTXO during confirmation. Happy to re-review once those are pushed.

@jaxint jaxint 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.

✅ Code reviewed - implementation verified.

@jujujuda jujujuda left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. The fix correctly handles migrated UTXO consumption on /pending/confirm — the idempotency guard prevents double-spend, and the consume-after-migrate ordering is correct. The fund-loss regression test case is a good addition. Ship it.

@jujujuda jujujuda left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. The fix correctly handles migrated UTXO consumption on /pending/confirm — the idempotency guard prevents double-spend, and the consume-after-migrate ordering is correct. The fund-loss regression test is a valuable addition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants