Skip to content

fix: reject invalid-denomination DSQUEUEs on mixing clients by sharing the server validation - #7955

Open
PastaPastaPasta wants to merge 3 commits into
dashpay:developfrom
PastaPastaPasta:refactor/coinjoin-dsqueue-validation
Open

PastaPastaPasta wants to merge 3 commits into
dashpay:developfrom
PastaPastaPasta:refactor/coinjoin-dsqueue-validation

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Mixing clients accept a signed DSQUEUE with an invalid denomination into their queue. Masternodes have rejected such queues (misbehaving 10) since #6865, but that check was added only to the server's DSQUEUE handler. The wallet-side handler keeps its own copy of the same validation, and the two copies have drifted apart. The bogus queue matches no session, but it takes up a queue slot, and it counts toward the masternode's mixing-rate threshold.

What was done?

  • refactor: moved the DSQUEUE checks that both roles share into CoinJoin::ValidateDSQueue (src/coinjoin/coinjoin.{h,cpp}):

    • denomination
    • masternode lookup by protx
    • time bounds
    • collateral lookup
    • operator signature

    It returns the signing masternode and the score to penalize the peer with. CCoinJoinServer::ProcessDSQUEUE now uses it. The server's checks, order and penalties are unchanged.

  • fix: CJWalletManagerImpl::ProcessDSQueue uses the same function, so clients now reject invalid denominations with the same penalty as the server (10).

The duplicate check is passed in as a callback. Each role keeps its own queue storage and locking, and duplicates are still dropped before the BLS signature is verified. Each caller applies the returned penalty its own way (PeerMisbehaving on the server, MisbehavingError on the client).

Remaining differences between the two handlers, kept on purpose and not changed here:

  • Mixing-rate threshold: the server applies it, and AllowMixing, only to non-ready queues, and never stores or relays ready queues. The client first tries to submit to its session for a ready queue. If no session is waiting on that masternode, it applies the threshold and stores the ready queue like any other. So on clients, ready queues count toward the threshold. This is long-standing behavior. It may deserve its own look, but this PR doesn't change it.
  • Duplicate tracking: the server uses TryCheckDuplicate under TRY_LOCK and drops the message silently on lock contention. The client takes a blocking lock, checks HasQueue(hash) then HasQueueFromMasternode, and holds cs_ProcessDSQueue until it has stored the queue.
  • After validation: the server relays the queue. The client marks queues it already joined as tried.

Side effects of the client change:

masternodeOutpoint is not serialized, so in practice every DSQUEUE is resolved through m_protxHash. The collateral lookup and protx fill-in after it can't fail for network input. I kept them so the server code moves over line for line.

How Has This Been Tested?

  • New unit test coinjoin_queue_tests/client_rejects_invalid_queues:
    • It registers a masternode on a DIP3 regtest chain and feeds signed DSQUEUEs through CJWalletManager::processMessage.
    • It checks the returned penalty and whether the queue was stored for: an invalid denomination, a missing protx, an unknown protx and a wrong signer.
    • A valid queue from the same masternode is the accepted control.
    • Without the client change, the invalid-denomination row fails: the queue is stored and no penalty is returned.
  • Each commit builds on its own, and coinjoin_queue_tests passes on both.
  • Also run on Linux x86_64 with --enable-debug:
    • unit tests: coinjoin_queue_tests, coinjoin_inouts_tests, coinjoin_dstxmanager_tests, coinjoin_tests
    • functional tests: rpc_coinjoin.py, p2p_dstx.py
    • lints: lint-circular-dependencies.py, lint-includes.py, lint-whitespace.py

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

🤖 Generated with Claude Code

PastaPastaPasta and others added 2 commits October 29, 2026 08:23
CCoinJoinServer::ProcessDSQUEUE and CJWalletManagerImpl::ProcessDSQueue
each carried their own copy of the DSQUEUE checks, and the copies have
drifted apart. Move the checks that do not depend on the receiving role
into CoinJoin::ValidateDSQueue: denomination, masternode lookup, time
bounds and the operator signature. The function returns the signing
masternode and the score to penalize the sending peer with.

The duplicate check is a callback, so each role keeps its own queue
storage and locking, and duplicates are still dropped before the BLS
signature is verified. The mixing-rate threshold stays in the callers,
because the server and the client apply it to different queues.

No behavior change: the server runs the same checks in the same order
with the same penalties.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The server started rejecting DSQUEUE messages with an invalid
denomination (misbehaving 10) in dashpay#6865, but the wallet-side handler
never got that check. A client accepted a signed DSQUEUE with a bogus
denomination into its queue, where it matched no session and only took
up a slot.

Validate client DSQUEUEs with CoinJoin::ValidateDSQueue, the checks the
server already uses, so the client rejects such queues with the same
penalty. The client keeps its own duplicate tracking under
cs_ProcessDSQueue, the ready-queue submission and the mixing-rate
threshold.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-29T08:25:19.799103Z 0e19b87 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@PastaPastaPasta PastaPastaPasta changed the title fix(coinjoin): reject invalid-denomination DSQUEUEs on mixing clients by sharing the server's validation fix: reject invalid-denomination DSQUEUEs on mixing clients by sharing the server validation Oct 29, 2026
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 72a86ec6-6250-4075-b645-945dfaa6175a
Commits

Reviewing files that changed from the base of the PR and between 0e19b87 and 0039926.

Files selected for processing: 1
  • src/test/coinjoin_queue_tests.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Walkthrough

The change adds CoinJoin::ValidateDSQueue and its validation result type. The server and wallet queue handlers use the shared validator with separate duplicate-check callbacks. Wallet-enabled tests cover invalid denominations, missing or unknown masternode identifiers, invalid signatures, and acceptance of a valid queue.

Priority: Normal

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

Sequence Diagram(s)

sequenceDiagram
  participant ProcessDSQUEUE
  participant CoinJoinValidateDSQueue as CoinJoin::ValidateDSQueue
  participant DuplicateCheck as Duplicate-check callback
  ProcessDSQUEUE->>CoinJoinValidateDSQueue: Pass queue, masternode list, and callback
  CoinJoinValidateDSQueue->>DuplicateCheck: Check whether queue is duplicate
  DuplicateCheck-->>CoinJoinValidateDSQueue: Return duplicate result
  CoinJoinValidateDSQueue-->>ProcessDSQUEUE: Return validation result
Loading
Merge Risk: Minimal · up to 00399

The change is ready to merge with no confirmed actionable risk remaining.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title names the main change: invalid-denomination DSQUEUEs are now rejected on mixing clients by sharing the server's validation. The raw summary confirms the client wallet handler now calls the s…
Description check Passed The description explains the problem (mixing clients accept DSQUEUEs with invalid denominations), the refactor into CoinJoin::ValidateDSQueue, the server and client call sites, intentional remaining d…
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.
Full details: Title check

Explanation

The title names the main change: invalid-denomination DSQUEUEs are now rejected on mixing clients by sharing the server's validation. The raw summary confirms the client wallet handler now calls the shared CoinJoin::ValidateDSQueue, and the objectives describe the same penalty-10 behavior. The title is specific and matches the changeset.

Full details: Description check

Explanation

The description explains the problem (mixing clients accept DSQUEUEs with invalid denominations), the refactor into CoinJoin::ValidateDSQueue, the server and client call sites, intentional remaining differences, and the new test coinjoin_queue_tests/client_rejects_invalid_queues. This matches the changeset described in the summaries.

Full details: Docstring Coverage

Explanation

Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 7 functions across 5 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@thepastaclaw

thepastaclaw commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 0039926) · triage: normal

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 1 + Phase 2

The DSQUEUE validation refactor preserves the server’s validation order, penalties, duplicate handling, and client rejection behavior. Two non-blocking issues remain: the server loses peer attribution in the invalid-denomination log, and the regression test does not cover the documented ready-queue case. AMD64 builds and ClangFormat passed; ARM64 builds are still in progress, while the failed PR-title check is unrelated to the code changes.

🟡 1 suggestion(s) | 💬 1 nitpick(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: dash-core-commit-history); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a contained peer-to-peer validation behavior change with shared logic refactoring and focused tests, but it does not alter consensus, funds, cryptographic algorithms, or network deserialization.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — dash-core-commit-history (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/coinjoin/coinjoin.cpp`:
- [NITPICK] src/coinjoin/coinjoin.cpp:775-777: Shared log drops peer id for invalid-denomination DSQUEUEs
  Before extraction, the server logged the invalid denomination together with the relaying peer id. The shared helper now logs only the denomination, so server diagnostics no longer identify which peer sent the malformed queue. This does not change the misbehavior score, but it removes useful attribution when investigating or correlating misbehavior. Preserve the peer id for the server call, either by passing it into the helper or by adding server-side logging at the call site.

In `src/test/coinjoin_queue_tests.cpp`:
- [SUGGESTION] src/test/coinjoin_queue_tests.cpp:370: No ready-queue regression case for invalid denominations
  The PR documents that an invalid-denomination ready queue must be rejected before the client attempts to submit it to a session, but the regression test only constructs non-ready queues because MakeQueue hardcodes fReady=false. Add a ready invalid-denomination case to verify the same penalty and storage rejection on the path whose behavior is explicitly called out by the PR.

Comment thread src/coinjoin/coinjoin.cpp Resolved
Comment thread src/test/coinjoin_queue_tests.cpp Resolved
@thepastaclaw thepastaclaw added the pastaclaw:commented thepastaclaw's latest review was comment-only label Oct 29, 2026
@thepastaclaw thepastaclaw removed the pastaclaw:commented thepastaclaw's latest review was comment-only label Oct 29, 2026

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review — Final validation — Phase 1 + Phase 2

The shared DSQUEUE validator preserves the server's validation order, duplicate handling, timeout behavior, masternode lookup, signature checks, and penalties while extending denomination validation to wallet clients. The regression test now covers both non-ready and ready invalid-denomination queues, along with other invalid and valid cases. No in-scope correctness issues were found; build and lint checks for this exact head were still in progress in the supplied CI snapshot.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: dash-core-commit-history); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: dash-core-commit-history); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a contained but cross-cutting peer-facing CoinJoin message-validation change with shared validation, penalty handling, and queue-state behavior, but it does not alter consensus, funds movement, cryptographic key handling, or storage migrations.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — dash-core-commit-history (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — dash-core-commit-history (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

@thepastaclaw thepastaclaw added the pastaclaw:approved thepastaclaw's latest review approved this PR label Oct 29, 2026
@PastaPastaPasta PastaPastaPasta added this to the 24 milestone Nov 7, 2026
@github-actions

Copy link
Copy Markdown

Potential PR merge conflicts

This is advisory only. It does not block CI. This PR and each open PR below change the same files and cannot be merged together cleanly, so whichever of the two merges second will likely need a rebase:

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

Labels

pastaclaw:approved thepastaclaw's latest review approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants