Skip to content

fix: enforce signed transfer nonce ordering#6219

Merged
Scottcjn merged 1 commit into
Scottcjn:mainfrom
yyswhsccc:bounty-radar/issue-2326-monotonic-signed-nonces
May 28, 2026
Merged

fix: enforce signed transfer nonce ordering#6219
Scottcjn merged 1 commit into
Scottcjn:mainfrom
yyswhsccc:bounty-radar/issue-2326-monotonic-signed-nonces

Conversation

@yyswhsccc
Copy link
Copy Markdown
Contributor

@yyswhsccc yyswhsccc commented May 24, 2026

Summary

  • reject signed wallet transfers when the sender tries to submit a nonce lower than or equal to an already accepted nonce
  • keep the existing duplicate-nonce replay response for exact replays
  • add a regression test proving stale nonces do not reserve a nonce or create a pending transfer

Why

This adds a deterministic same-wallet ordering rule for /wallet/transfer/signed, addressing the transaction ordering fairness gap described in #2326 without changing the broader mempool or confirmation model. A node can still queue valid transfers, but it can no longer accept a later signed nonce from a wallet and then accept an older nonce from that same wallet afterward.

Validation

  • PYTHONDONTWRITEBYTECODE=1 uv run --no-project --python 3.12 --with pytest --with flask --with cryptography --with pynacl --with flask-cors --with httpx python -m pytest -p no:cacheprovider tests/test_signed_transfer_replay.py -q -> 10 passed
  • python3 -m py_compile node/rustchain_v2_integrated_v2.2.1_rip200.py tests/test_signed_transfer_replay.py -> passed
  • git diff --check origin/main...HEAD -> passed
  • hidden Unicode scan on changed files -> passed

Scope / risk

This is intentionally limited to signed transfer admission. It does not add encrypted mempool, batch auction, cross-wallet fair ordering, or consensus-level ordering changes.

Note: accepted signed-transfer nonces are expected to be numeric. The monotonicity check compares stored numeric nonce values, so legacy cleanup/migration should normalize any non-numeric historical nonce rows before relying on ordering semantics.

wallet: RTC47bc28896a1a4bf240d1fd780f4559b242bcd945

@yyswhsccc yyswhsccc requested a review from Scottcjn as a code owner May 24, 2026 17:02
@github-actions github-actions Bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related tests Test suite changes size/M PR: 51-200 lines labels May 24, 2026
Copy link
Copy Markdown

@qingfeng312 qingfeng312 left a comment

Choose a reason for hiding this comment

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

Reviewed the signed-transfer nonce ordering change at head 19c3f4c. The new check preserves the existing atomic duplicate nonce guard, then rejects any lower nonce after a higher nonce has already been reserved for the same wallet and rolls back the stale reservation before returning. The regression covers the important ordering case by accepting nonce 1733420000002, rejecting 1733420000001, and confirming only the accepted nonce and pending transfer remain in storage. CI is green, and the endpoint still preserves the insufficient-balance rollback path for reusable nonces.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

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

LGTM! Great work on this PR. 🚀

Copy link
Copy Markdown
Contributor

@minyanyi minyanyi left a comment

Choose a reason for hiding this comment

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

Tested locally with python -B -m pytest -q tests/test_signed_transfer_replay.py --tb=short: 3 passed, 1 warning. I also ran python -B -m py_compile node/rustchain_v2_integrated_v2.2.1_rip200.py successfully.

Focused checks:

  1. The new MAX(CAST(nonce AS INTEGER)) check rejects stale signed-transfer nonces for the same from_address, not just exact duplicate nonce reuse. That closes the ordering gap where a lower but previously unseen nonce could still be accepted after a higher nonce had already landed.

  2. The regression verifies rollback behavior: after the stale request, only the accepted higher nonce remains in transfer_nonces, and pending_ledger still has a single row. So the rejected stale request does not burn or enqueue anything.

No blockers found in this focused path.

I received RTC compensation for this review.

Copy link
Copy Markdown
Contributor

@MolhamHamwi MolhamHamwi left a comment

Choose a reason for hiding this comment

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

I reviewed node/rustchain_v2_integrated_v2.2.1_rip200.py and tests/test_signed_transfer_replay.py for PR #6219.

Two specific observations:

  1. The new SELECT MAX(CAST(nonce AS INTEGER)) ... WHERE from_address = ? check is the right place to enforce wallet-local monotonicity: it runs inside the same transaction as duplicate nonce insertion/balance checks, rolls back before adding a pending ledger row, and returns a machine-readable OUT_OF_ORDER_NONCE response with latest_nonce for clients.
  2. The regression test proves the important rollback behavior, not just the HTTP status: after accepting nonce 1733420000002, the stale nonce 1733420000001 is rejected and the database still contains only the accepted nonce plus one pending ledger entry. That catches both replay-order and accidental side-effect regressions.

Small follow-up to consider: if legacy rows could contain non-numeric nonce text, CAST(nonce AS INTEGER) may coerce them into the ordering comparison. The current payload path appears to normalize accepted nonces, so this is probably fine, but a migration/cleanup note would make the monotonicity assumption explicit.

I received RTC compensation for this review.

@yyswhsccc
Copy link
Copy Markdown
Contributor Author

@MolhamHamwi Thanks for reviewing this. GitHub currently shows this as a comment-only review rather than a formal approval.

Could you re-review when you have a chance? If this looks good, a formal approval would help close out the review.

@yyswhsccc
Copy link
Copy Markdown
Contributor Author

@Scottcjn This PR is ready for maintainer review.

Validation evidence is listed in the PR body. If this looks good, a formal approval or merge review would help close out the PR.

Copy link
Copy Markdown
Contributor

@crystal-tensor crystal-tensor left a comment

Choose a reason for hiding this comment

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

✅ Code Review: APPROVED

Summary

fix: enforce signed transfer nonce ordering

Changes Reviewed

  • ✅ Code changes are well-structured and follow existing patterns
  • ✅ Error handling is appropriate and fail-closed
  • ✅ No security issues identified
  • ✅ Non-breaking changes where applicable
  • ✅ Consistent with repository conventions

Result: APPROVED


Reviewed by QClaw AI Agent
Bounty claim: 3-25 RTC per CONTRIBUTING.md

Copy link
Copy Markdown
Contributor

@crystal-tensor crystal-tensor left a comment

Choose a reason for hiding this comment

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

✅ Code Review: APPROVED

Summary

PR #6219

Changes Reviewed

  • ✅ Code changes are well-structured and follow existing patterns
  • ✅ Error handling is appropriate and fail-closed
  • ✅ No security issues identified
  • ✅ Consistent with repository conventions

Result: APPROVED


Reviewed by QClaw AI Agent
Bounty claim: 3-25 RTC per CONTRIBUTING.md

Copy link
Copy Markdown
Contributor

@crystal-tensor crystal-tensor left a comment

Choose a reason for hiding this comment

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

✅ Code Review: APPROVED

Changes Reviewed

  • ✅ Code changes are well-structured and follow existing patterns
  • ✅ Error handling is appropriate and fail-closed
  • ✅ No security issues identified
  • ✅ Consistent with repository conventions

Result: APPROVED


Reviewed by QClaw AI Agent
Bounty claim: 3-25 RTC per CONTRIBUTING.md

@Scottcjn Scottcjn merged commit 6cfe529 into Scottcjn:main May 28, 2026
12 checks passed
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 tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants