test: cover CoinJoin client restart handling#7420
Conversation
|
@coderabbitai review |
✅ Action performedReview finished.
|
WalkthroughThis PR adds a new functional test, Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Test as WalletCoinJoinMixingTest
participant WalletA
participant WalletB
participant Masternode
Test->>WalletA: fund with denom/collateral UTXOs
Test->>WalletB: fund with denom/collateral UTXOs
Test->>WalletA: start mixing
Test->>WalletB: start mixing
WalletA->>Masternode: create mixing session/queue
WalletB->>Masternode: join queue
Test->>WalletA: stop node mid-session
Test->>WalletB: verify release/reset and cleared locked UTXOs
Test->>WalletA: restart node
Test->>Test: pump_mixing (advance mocktime, mine blocks)
Test->>WalletA: stop and reset mixing
Test->>WalletB: stop and reset mixing
Test->>Test: verify_sessions_and_queues
Related issues: None specified. Related PRs: None specified. Suggested labels: tests, functional-tests, coinjoin Suggested reviewers: dash core functional-test maintainers familiar with CoinJoin/PrivateSend logic 🐕 A test pup sniffs through mixing queues, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@test/functional/feature_coinjoin_mixing.py`:
- Line 348: The restart sequence in feature_coinjoin_mixing.py is using direct
stop_node()/restart_node() calls even though the test enables wallet backups,
which can trigger BACKUP_EXISTS_WARNING on stderr. Update the affected node
restart in the coinjoin mixing test to use the backup-tolerant helper used
elsewhere in this suite, and apply it consistently around the node 2 restart so
the stop/start flow avoids the default stderr check.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 3581b034-dde9-45d2-84b7-fbf8f2c81571
📥 Commits
Reviewing files that changed from the base of the PR and between f2ae9f0 and bad0a36f51814393148a1a741fbc36b757266dd5.
📒 Files selected for processing (2)
test/functional/feature_coinjoin_mixing.pytest/functional/test_runner.py
✅ No Merge Conflicts DetectedThis PR currently has no conflicts with other open PRs. |
bad0a36 to
d02f297
Compare
|
@coderabbitai review Updated the PR after local build/test validation. Scope is now narrowed to deterministic CoinJoin client restart/reset coverage with the #7259 semantic dependency kept documented; full anonymization/reorg/spend coverage was removed from this PR because it was not passing reliably locally. |
|
(=^・ω・^=) noted the scope change, digging in now ✏️ Learnings added
✅ Action performedReview finished.
|
|
Handled knst's naming point in 7f80f9bd5cb: the narrowed test is wallet/client-side CoinJoin restart coverage now, so I renamed it to |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@test/functional/wallet_coinjoin_mixing.py`:
- Around line 187-195: The polling predicate in the CoinJoin session handling
can fail too early when sessions[0].get('protxhash') is still None during
initialization. Update the logic around the wait_until callback in
wallet_coinjoin_mixing.py to return False until protxhash is assigned, before
calling get_mninfo or asserting on the masternode lookup. Keep the existing
connected_mns check and connection setup in the same predicate, but only proceed
once a valid protxhash is available.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 2f1d5da8-af53-4e0b-a44a-f0e6063b4203
📥 Commits
Reviewing files that changed from the base of the PR and between bad0a36f51814393148a1a741fbc36b757266dd5 and 7f80f9bd5cbdf3beb634477a89a90d1c80f73aa5.
📒 Files selected for processing (2)
test/functional/test_runner.pytest/functional/wallet_coinjoin_mixing.py
✅ Files skipped from review due to trivial changes (1)
- test/functional/test_runner.py
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
CI note for the current head Both All other visible checks passed, the test passes locally in the configured worktree, and the current review threads are resolved. I am leaving the branch unchanged rather than rerunning CI or pushing a no-op change. |
4db7b42 to
bbfea64
Compare
|
✅ Final review complete — no blockers (commit 9e09480) |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
The test is well scoped and the three historical review issues are resolved at the current head. Two assertions can still pass without exercising the participant-restart and reset-unlock paths the test claims to cover; the corrective follow-up commits and stale draft-status comment should also be cleaned up before merge.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— final-verifier (fallback) - Sonnet reviewers:
claude-sonnet-5— general (completed),claude-sonnet-5— dash-core-commit-history (failed),claude-sonnet-5— dash-core-commit-history (completed)
🟡 3 suggestion(s) | 💬 1 nitpick(s)
1 additional finding(s) omitted (not in diff).
🤖 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 `test/functional/wallet_coinjoin_mixing.py`:
- [SUGGESTION] test/functional/wallet_coinjoin_mixing.py:244-250: Wait for node 2 to join the active session
`wait_for_joiner_visible_queue()` only returns after node 1 reports a session. The current `any(...)` predicate is therefore normally true immediately after `coinjoin('start')` on node 2, even though that RPC only enables mixing and does not synchronously create a session. Node 2 can be stopped before participating at all, while node 1 eventually times out and lets the test pass. Wait until both wallets report sessions using the same non-null masternode hash before restarting node 2.
- [SUGGESTION] test/functional/wallet_coinjoin_mixing.py:127-133: Assert reset cleanup before manually unlocking coins
`lockunspent(True)` unconditionally unlocks every wallet output after `coinjoin reset`, so the later `listlockunspent() == []` checks pass even if reset failed to release CoinJoin's session locks. The node 2 check is weakened further by the preceding process restart, which inherently clears CoinJoin's non-persistent locks. `CCoinJoinClientManager::resetPool()` synchronously invokes `ResetPool()` and `UnlockCoins()` for each session before clearing the session deque, so assert those postconditions directly rather than repairing them first.
In `<commit:f8d93a0b>`:
- [SUGGESTION] <commit:f8d93a0b>:1: Squash the corrective commits into the test-introducing commit
Commits `f8d93a0b` and `bbfea64b` only refine the new test introduced by `5a2ca996`: the first renames and reclassifies the file, and the second adds a null guard to its polling predicate. Neither is an independently useful historical step, and the intermediate test revision contains the known polling bug. Fold both into the introducing commit and replace its transient statement that the PR intentionally remains draft with durable rationale referencing the lifetime fix from #7259, which is already present in this branch's ancestry.
Add a regtest functional test for CoinJoin client queue/session startup, participant restart while a shared session is active, stop/reset cleanup, and masternode-side RPC behavior. The restart path exercises the CoinJoin client lifetime handling fixed by dashpay#7259 and keeps this regression coverage scoped to functional tests.
bbfea64 to
9e09480
Compare
|
Addressed the latest review findings in signed commit
The three-commit PR stack was folded into one clean test commit. Local validation passed: Python compilation, targeted repository flake8 checks, |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
This delta squashes the branch into a single clean commit and correctly fixes all four previously flagged issues: node 2 is now confirmed to have joined the same masternode session before being restarted, stop_and_reset_mixing asserts empty sessions/locked-unspent directly after the reset RPC instead of masking failures with a manual lockunspent, the corrective commits are folded into one coherent commit, and the stale draft-status comment was replaced with durable rationale. One legitimate test-coverage nitpick remains: the survivor's reset call at line 269 happens after CCoinJoinClientSession::CheckTimeout() has already unlocked coins via the automatic POOL_STATE_ERROR transition, so the adjacent listlockunspent()==[] assertion at that specific call site doesn't actually exercise reset-driven unlocking (that behavior is still validated elsewhere in test_stop_and_reset against a live, non-error session).
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
claude-sonnet-5— final-verifier - Sonnet reviewers:
claude-sonnet-5— general (failed),claude-sonnet-5— dash-core-commit-history (completed),claude-sonnet-5— general (failed),claude-sonnet-5— general (completed)
🟡 1 suggestion(s)
🤖 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 `test/functional/wallet_coinjoin_mixing.py`:
- [SUGGESTION] test/functional/wallet_coinjoin_mixing.py:261-269: Survivor reset call doesn't exercise reset-driven coin unlocking
survivor_released_session() waits until the survivor's session has disappeared or reached POOL_STATE_ERROR before calling coinjoin('reset'). In src/coinjoin/client.cpp, CCoinJoinClientSession::CheckTimeout() sets POOL_STATE_ERROR and calls UnlockCoins() synchronously in the same step (lines 314-315), before the deque entry ever surfaces the ERROR state to the test's polling predicate. By the time stop_and_reset_mixing([self.w1]) runs coinjoin('reset') at line 269, the session's locked coins have already been released by the automatic timeout path, not by CCoinJoinClientSession::ResetPool()'s own UnlockCoins() call. So the listlockunspent()==[] assertion added inside stop_and_reset_mixing passes here regardless of whether reset() itself unlocks coins correctly — this specific call site provides no coverage for that behavior (it's still covered by the test_stop_and_reset() call against a live, non-error session later in the test, so this is a coverage gap rather than a correctness bug).
| def survivor_released_session(): | ||
| self.pump_mixing(wallets=[self.w1]) | ||
| info = self.w1.getcoinjoininfo() | ||
| return not info['sessions'] or any(session['state'] == 'ERROR' for session in info['sessions']) | ||
|
|
||
| self.wait_until(survivor_released_session, timeout=120, sleep=0.25) | ||
| # Reset the survivor before restarting it so cleanup is attributable | ||
| # to the RPC rather than process restart. | ||
| self.stop_and_reset_mixing([self.w1]) |
There was a problem hiding this comment.
🟡 Suggestion: Survivor reset call doesn't exercise reset-driven coin unlocking
survivor_released_session() waits until the survivor's session has disappeared or reached POOL_STATE_ERROR before calling coinjoin('reset'). In src/coinjoin/client.cpp, CCoinJoinClientSession::CheckTimeout() sets POOL_STATE_ERROR and calls UnlockCoins() synchronously in the same step (lines 314-315), before the deque entry ever surfaces the ERROR state to the test's polling predicate. By the time stop_and_reset_mixing([self.w1]) runs coinjoin('reset') at line 269, the session's locked coins have already been released by the automatic timeout path, not by CCoinJoinClientSession::ResetPool()'s own UnlockCoins() call. So the listlockunspent()==[] assertion added inside stop_and_reset_mixing passes here regardless of whether reset() itself unlocks coins correctly — this specific call site provides no coverage for that behavior (it's still covered by the test_stop_and_reset() call against a live, non-error session later in the test, so this is a coverage gap rather than a correctness bug).
source: ['codex']
Issue being fixed or feature implemented
Remakes the closed CoinJoin functional test PR #7407 as a signed, test-only
change focused on the active CoinJoin client lifetime path.
The test covers real regtest CoinJoin queue/session startup, participant restart
while a session is active, client stop/reset cleanup, and the masternode-side
CoinJoin RPC shape.
This was initially kept draft while PR #7259 fixed CoinJoin client-lifetime handling around wallet operations. #7259 merged on 2026-07-10, and this branch is now rebased onto develop containing the fix. It remains draft until the new-head TSAN and multiprocess jobs confirm the deadlock regression is gone.
What was done?
wallet_coinjoin_mixing.py.test_runner.py.-coinjoinrandomroundsruntime optionto cover full anonymization, reorgs, and mixed-fund spends in one PR
protxhashassignment before resolving theselected masternode
How Has This Been Tested?
Initial local validation in a built worktree:
After narrowing/renaming the test and addressing review feedback:
All local commands above passed. The functional test run printed the standard
warning that unrelated
dashdprocesses were already running on the machine, butthe test completed successfully.
The branch is rebased on the merged #7259 fix. The PR remains draft while new-head CI confirms the TSAN and multiprocess deadlock failures are resolved.
Breaking Changes
None.
Checklist
and collaborators only)