Skip to content

test(dash-spv): dashd integration tests for reorg cascade#171

Merged
xdustinface merged 2 commits into
devfrom
test/dashd-reorg-integration
May 26, 2026
Merged

test(dash-spv): dashd integration tests for reorg cascade#171
xdustinface merged 2 commits into
devfrom
test/dashd-reorg-integration

Conversation

@xdustinface

Copy link
Copy Markdown
Owner

Summary

  • Adds dash-spv/tests/dashd_sync/tests_reorg.rs with 13 reorg integration tests against a regtest dashd, driven via invalidateblock + generatetoaddress (single-node).
  • Extends DashCoreNode with invalidate_block, reconsider_block, and get_block_hash helpers so future reorg tests don't reach around the harness.
  • Registers the new test module in dash-spv/tests/dashd_sync/main.rs.
  • 5 tests are active, asserting that SyncEvent::ChainReorg and WalletEvent::Reorg fire on the cascade boundary for shallow, deep-within-cap, descendant-tx, in-flight-filter-fetch, and event-handler join scenarios.
  • 8 tests are #[ignore]'d with one-line reasons linking the gating issue (#141, #142, #143).

Test plan

  • cargo fmt --check
  • cargo clippy -p dash-spv --all-targets --all-features -- -D warnings
  • Verified via cargo test -p dash-spv --test dashd_sync reorg:: with a live regtest dashd (eval $(python3 contrib/setup-dashd.py)); the 5 active tests pass.

Observations from this work (worth raising separately, not addressed here)

  1. Post-cascade resync stall. After handle_reorg truncates and downstream managers log cascading ChainReorg, resetting pipeline, none of them re-request data. Header sync halts one block short of dashd's tip, filter/block pipelines sit in WaitForEvents. This is the root cause of several #[ignore]'d tests. Likely belongs against the Phase 3 cascade work (#140).
  2. DeepReorgDetected unreachable on a fresh client. The fresh-client fork floor short-circuits before the depth-cap guard runs, so the cap-exceeded case rejects via the floor rather than emitting DeepReorgDetected. Either the floor should defer to the cap check, or the floor rejection should also emit DeepReorgDetected.
  3. generatetoaddress on the same miner across an invalidateblock produces a duplicate coinbase that dashd rejects. Worked around by minting fork blocks to a fresh getnewaddress. Worth documenting on DashCoreNode.

Refs #147.

Drives real chain reorgs against a single regtest dashd via
`invalidateblock` + `generatetoaddress` and asserts the
[`SyncEvent::ChainReorg`](https://github.com/xdustinface/rust-dashcore/blob/dev/dash-spv/src/sync/events.rs)
and [`WalletEvent::Reorg`](https://github.com/xdustinface/rust-dashcore/blob/dev/key-wallet-manager/src/events.rs)
events fire with the expected `fork_height` and a bumped generation
counter.

Exposes `invalidate_block`, `reconsider_block`, and `get_block_hash` on
`DashCoreNode` so the new module can drive forks without reaching into
the underlying RPC client.

Phase 6 of [#147](#147).
Cases that need infrastructure that is not yet present in the harness
(CLSIG injection, masternode-backed LLMQs for IS-locks and DIP-24
rotation, post-cascade downstream resync) ship as scaffolded
`#[ignore]` tests with a link to the gating issue so the matrix stays
visible and can be re-enabled in place once the gap is closed.
@manki-review

manki-review Bot commented May 26, 2026

Copy link
Copy Markdown

Manki — Review complete

Planner (21s)
    test · 393 lines · 2 agents
    review effort: medium · judge effort: low

Review — 5 findings
    ✅ Testing & Coverage — 2 (189s)
    ✅ Correctness & Logic — 3 (212s)

Judge — 2 kept · 0 dropped (11s)
    kept: 1 suggestion · 1 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: medium (auto, 393 lines)
  • Team: Testing & Coverage, Correctness & Logic
  • Memory: disabled

Judge decisions:

  • ✓ Kept: "Broadcast RecvError::Lagged silently treated as fatal in event-waiter helpers" (nitpick, high confidence) — "With a 10k-slot buffer lag is unlikely in practice, and on lag the test would just panic instead of silently passing — diagnostic improvement, not a correctness bug. Merged findings 2 and 5."
  • ✓ Kept: "test_event_handler_observes_chain_reorg_and_wallet_reorg: fresh wallet may not observe the sent transaction" (suggestion, medium confidence) — "Single reviewer; the concern is real but the failure mode is a 60s timeout with a clear panic message rather than silent pass. Adding a precondition assert is a diagnostic improvement, not a correctness fix."

Timing:

  • Parse: 2.9s
  • Review agents: 239.2s
  • Judge: 11.2s
  • Total: 253.2s

@manki-review manki-review Bot 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.

Reorg integration tests land, but the 'descendant chain' test doesn't actually build a parent→child UTXO spend — it's two independent receives, so cascade-demotion logic goes uncovered.

📊 2 findings (1 suggestion, 1 nitpick) · 393 lines · 253s

Manki context
{
  "meta": {
    "prNumber": 171,
    "commitSha": "21210d350922c35e9cf2e00054cac95332d21279",
    "round": 1,
    "timestamp": "2026-05-26T16:02:07.230Z",
    "mankiVersion": "5.3.0",
    "cap": {
      "priorRoundCount": 0,
      "maxAutoRounds": 5,
      "skipCap": false,
      "forceReview": false,
      "bypassReason": "within_cap"
    },
    "trigger": {
      "event": "pull_request:opened",
      "sender": "xdustinface"
    }
  },
  "config": {
    "reviewLevel": "medium",
    "memoryEnabled": false,
    "reviewPasses": 1
  },
  "diff": {
    "lines": 393,
    "additions": 393,
    "deletions": 0,
    "filesReviewed": 3,
    "fileTypes": {
      ".rs": 3
    },
    "oversizedHandled": false,
    "perFile": [
      {
        "path": "dash-spv/src/test_utils/node.rs",
        "additions": 21,
        "deletions": 0,
        "changeType": "modified"
      },
      {
        "path": "dash-spv/tests/dashd_sync/main.rs",
        "additions": 1,
        "deletions": 0,
        "changeType": "modified"
      },
      {
        "path": "dash-spv/tests/dashd_sync/tests_reorg.rs",
        "additions": 371,
        "deletions": 0,
        "changeType": "added"
      }
    ]
  },
  "models": {
    "planner": "claude-haiku-4-5",
    "reviewer": "claude-sonnet-4-6",
    "judge": "claude-opus-4-7",
    "dedup": "claude-haiku-4-5"
  },
  "planner": {
    "source": "planner",
    "used": true,
    "coreAgentInjections": [],
    "priorRoundEffortDowngrades": [],
    "teamSize": 2,
    "reviewerEffort": "medium",
    "judgeEffort": "low",
    "prType": "test",
    "durationMs": 21443,
    "language": "rust",
    "context": "Dash SPV blockchain client",
    "agents": [
      {
        "name": "Testing & Coverage",
        "effort": "high"
      },
      {
        "name": "Correctness & Logic",
        "effort": "high"
      }
    ]
  },
  "reviewers": {
    "agents": [
      "Testing & Coverage",
      "Correctness & Logic"
    ],
    "agentMetrics": [
      {
        "name": "Testing & Coverage",
        "findingsRaw": 2,
        "findingsKept": 1,
        "durationMs": 188948,
        "status": "success",
        "responseLength": 3166,
        "inputTokens": 4,
        "outputTokens": 9426,
        "retryCount": 0,
        "model": "claude-sonnet-4-6",
        "effort": "high"
      },
      {
        "name": "Correctness & Logic",
        "findingsRaw": 3,
        "findingsKept": 2,
        "durationMs": 211499,
        "status": "success",
        "responseLength": 3685,
        "inputTokens": 3,
        "outputTokens": 12513,
        "retryCount": 0,
        "model": "claude-sonnet-4-6",
        "effort": "high"
      }
    ]
  },
  "judge": {
    "summary": "Reorg integration tests land, but the 'descendant chain' test doesn't actually build a parent→child UTXO spend — it's two independent receives, so cascade-demotion logic goes uncovered.",
    "confidenceDistribution": {
      "high": 1,
      "medium": 1,
      "low": 0
    },
    "severityChanges": 2,
    "mergedDuplicates": 3,
    "durationMs": 11188,
    "retryCount": 0,
    "verdictReason": "only_nit_or_suggestion",
    "defensiveHardeningCount": 1,
    "verdictTrace": {
      "survivingBlockers": [],
      "novelWarnings": [],
      "unresolvedPriors": []
    },
    "openThreadsState": "empty",
    "openThreadCount": 0,
    "resolvedThreadIdCount": 0,
    "interRoundDiffState": "unknown",
    "interRoundDiffTruncated": false
  },
  "dedup": {
    "staticDropped": 0,
    "llmDropped": 0,
    "sameRoundLlmDropped": 3
  },
  "memory": {
    "loadStatus": "disabled"
  },
  "findings": {
    "count": 2,
    "severityCounts": {
      "blocker": 0,
      "warning": 0,
      "suggestion": 1,
      "nitpick": 1
    },
    "entries": [
      {
        "fingerprint": {
          "file": "dash-spv/tests/dashd_sync/tests_reorg.rs",
          "lineStart": 50,
          "lineEnd": 50,
          "slug": "Broadcast-RecvError--Lagged-silently-treated-as-fatal-in-event-waiter-helpers"
        },
        "severity": "nitpick",
        "specialist": "Testing & Coverage",
        "suggestedFix": "recv = receiver.recv() => match recv {\n    Ok(event @ SyncEvent::ChainReorg { fork_height, .. })\n        if fork_height == expected_fork_height => return event,\n    Ok(_) => continue,\n    Err(broadcast::error::RecvError::Lagged(_)) => continue,\n    Err(err) => panic!(\"sync event channel closed waiti",
        "title": "Broadcast RecvError::Lagged silently treated as fatal in event-waiter helpers",
        "judgeNotes": "With a 10k-slot buffer lag is unlikely in practice, and on lag the test would just panic instead of silently passing — diagnostic improvement, not a correctness bug. Merged findings 2 and 5.",
        "judgeConfidence": "high",
        "reachability": "hypothetical",
        "reachabilityReasoning": "Broadcast buffer is 10,000 slots and tests emit far fewer events before the awaited reorg, so Lagged is not reached under normal test execution.",
        "tags": [
          "defensive-hardening"
        ],
        "originalSeverity": "suggestion"
      },
      {
        "fingerprint": {
          "file": "dash-spv/tests/dashd_sync/tests_reorg.rs",
          "lineStart": 328,
          "lineEnd": 328,
          "slug": "test-event-handler-observes-chain-reorg-and-wallet-reorg--fresh-wallet-may-not-observe-the-sent-transaction"
        },
        "severity": "suggestion",
        "specialist": "Correctness & Logic",
        "suggestedFix": "// after wait_for_sync(original_tip)\nlet txid = ctx.dashd.node.send_to_address(...);\n// ...\nwait_for_sync(&mut client.progress_receiver, original_tip).await;\nassert!(ctx.has_transaction(&txid).await, \"wallet must observe tx before reorg\");",
        "title": "test_event_handler_observes_chain_reorg_and_wallet_reorg: fresh wallet may not observe the sent transaction",
        "judgeNotes": "Single reviewer; the concern is real but the failure mode is a 60s timeout with a clear panic message rather than silent pass. Adding a precondition assert is a diagnostic improvement, not a correctness fix.",
        "judgeConfidence": "medium",
        "reachability": "unknown"
      }
    ]
  },
  "usage": {
    "inputTokens": 21,
    "outputTokens": 24462,
    "totalTokens": 24483,
    "perStage": {
      "planner": {
        "inputTokens": 9,
        "outputTokens": 1937,
        "totalTokens": 1946
      },
      "reviewer": {
        "inputTokens": 7,
        "outputTokens": 21939,
        "totalTokens": 21946
      },
      "judge": {
        "inputTokens": 5,
        "outputTokens": 586,
        "totalTokens": 591
      }
    }
  },
  "verdict": "APPROVE",
  "recap": {
    "priorRoundCount": 0,
    "reclassifiedPriorCount": 0
  }
}

Reviewed commit 21210d3

Comment thread dash-spv/tests/dashd_sync/tests_reorg.rs Outdated
Comment thread dash-spv/tests/dashd_sync/tests_reorg.rs
@codecov-commenter

codecov-commenter commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.76%. Comparing base (062e73c) to head (ae48b48).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #171      +/-   ##
==========================================
- Coverage   73.82%   73.76%   -0.06%     
==========================================
  Files         325      325              
  Lines       75204    75204              
==========================================
- Hits        55518    55476      -42     
- Misses      19686    19728      +42     
Flag Coverage Δ
core 76.48% <ø> (ø)
ffi 46.30% <ø> (ø)
rpc 20.00% <ø> (ø)
spv 91.16% <ø> (-0.21%) ⬇️
wallet 71.51% <ø> (ø)
see 13 files with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Map `broadcast::error::RecvError::Lagged` to a logged `continue` in `wait_for_chain_reorg` and `wait_for_wallet_reorg` so a saturated broadcast buffer surfaces as a clear timeout instead of an opaque panic. The closed-channel branch still panics with a more specific message.

Addresses manki-review comment on PR #171
#171 (comment)
@manki-review

manki-review Bot commented May 26, 2026

Copy link
Copy Markdown

Manki — Review complete

Planner (25s)
    test · 401 lines · 2 agents
    review effort: medium · judge effort: medium

Review — 1 findings
    ✅ Testing & Coverage — 0 (3s)
    ✅ Correctness & Logic — 1 (26s)

Judge — 0 kept · 0 dropped (14s)

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: medium (auto, 401 lines)
  • Team: Testing & Coverage, Correctness & Logic
  • Memory: disabled

Timing:

  • Parse: 3.6s
  • Review agents: 58.4s
  • Judge: 13.5s
  • Total: 75.4s

@manki-review manki-review Bot 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.

Lag handling in the event waiters is fixed cleanly, but the fresh-wallet derivation concern in test_event_handler_observes_chain_reorg_and_wallet_reorg is untouched — same code shape as round 1.

📊 0 findings (none) · 401 lines · 75s

Manki context
{
  "meta": {
    "prNumber": 171,
    "commitSha": "ae48b4854a1bde2bd09be7bc1c2f3a77462739ee",
    "round": 2,
    "timestamp": "2026-05-26T16:11:19.123Z",
    "mankiVersion": "5.3.0",
    "cap": {
      "priorRoundCount": 1,
      "maxAutoRounds": 5,
      "skipCap": false,
      "forceReview": false,
      "bypassReason": "within_cap"
    },
    "trigger": {
      "event": "pull_request:synchronize",
      "sender": "xdustinface"
    }
  },
  "config": {
    "reviewLevel": "medium",
    "memoryEnabled": false,
    "reviewPasses": 1
  },
  "diff": {
    "lines": 401,
    "additions": 401,
    "deletions": 0,
    "filesReviewed": 3,
    "fileTypes": {
      ".rs": 3
    },
    "oversizedHandled": false,
    "perFile": [
      {
        "path": "dash-spv/src/test_utils/node.rs",
        "additions": 21,
        "deletions": 0,
        "changeType": "modified"
      },
      {
        "path": "dash-spv/tests/dashd_sync/main.rs",
        "additions": 1,
        "deletions": 0,
        "changeType": "modified"
      },
      {
        "path": "dash-spv/tests/dashd_sync/tests_reorg.rs",
        "additions": 379,
        "deletions": 0,
        "changeType": "added"
      }
    ]
  },
  "models": {
    "planner": "claude-haiku-4-5",
    "reviewer": "claude-sonnet-4-6",
    "judge": "claude-opus-4-7",
    "dedup": "claude-haiku-4-5"
  },
  "planner": {
    "source": "planner",
    "used": true,
    "coreAgentInjections": [],
    "priorRoundEffortDowngrades": [],
    "teamSize": 2,
    "reviewerEffort": "medium",
    "judgeEffort": "medium",
    "prType": "test",
    "durationMs": 25399,
    "language": "rust",
    "context": "Dash cryptocurrency SPV client",
    "agents": [
      {
        "name": "Testing & Coverage",
        "effort": "medium"
      },
      {
        "name": "Correctness & Logic",
        "effort": "medium"
      }
    ]
  },
  "reviewers": {
    "agents": [
      "Testing & Coverage",
      "Correctness & Logic"
    ],
    "agentMetrics": [
      {
        "name": "Testing & Coverage",
        "findingsRaw": 0,
        "findingsKept": 0,
        "durationMs": 3282,
        "status": "success",
        "responseLength": 2,
        "inputTokens": 3,
        "outputTokens": 4,
        "retryCount": 0,
        "model": "claude-sonnet-4-6",
        "effort": "medium"
      },
      {
        "name": "Correctness & Logic",
        "findingsRaw": 1,
        "findingsKept": 0,
        "durationMs": 25806,
        "status": "success",
        "responseLength": 1179,
        "inputTokens": 3,
        "outputTokens": 932,
        "retryCount": 0,
        "model": "claude-sonnet-4-6",
        "effort": "medium"
      }
    ]
  },
  "judge": {
    "summary": "Lag handling in the event waiters is fixed cleanly, but the fresh-wallet derivation concern in test_event_handler_observes_chain_reorg_and_wallet_reorg is untouched — same code shape as round 1.",
    "confidenceDistribution": {
      "high": 0,
      "medium": 0,
      "low": 0
    },
    "severityChanges": 0,
    "mergedDuplicates": 0,
    "durationMs": 13515,
    "retryCount": 0,
    "verdictReason": "only_nit_or_suggestion",
    "threadEvaluations": [
      {
        "threadId": "PRRT_kwDOQSlaXs6E2fsW",
        "status": "not_addressed",
        "reason": "The inter-round diff only modifies the wait_for_chain_reorg/wait_for_wallet_reorg helpers to handle Lagged. The test at line 348+ still creates a fresh wallet via create_test_wallet(&ctx.dashd.wallet.mnemonic, ...) and uses ctx.receive_address() without any reconciliation of derivation state."
      }
    ],
    "verdictTrace": {
      "survivingBlockers": [],
      "novelWarnings": [],
      "unresolvedPriors": []
    },
    "openThreadsState": "fetched",
    "openThreadCount": 1,
    "resolvedThreadIdCount": 1,
    "interRoundDiffState": "changed",
    "interRoundDiffBytes": 1547,
    "interRoundDiffTruncated": false
  },
  "dedup": {
    "staticDropped": 0,
    "llmDropped": 1,
    "duplicateMatches": [
      {
        "droppedTitle": "Target reorg event may be silently dropped when receiver lags",
        "matchedTitle": "Broadcast RecvError::Lagged silently treated as fatal in event-waiter helpers",
        "matchType": "llm"
      }
    ],
    "durationMs": 5437
  },
  "memory": {
    "loadStatus": "disabled"
  },
  "findings": {
    "count": 0,
    "severityCounts": {
      "blocker": 0,
      "warning": 0,
      "suggestion": 0,
      "nitpick": 0
    },
    "entries": []
  },
  "usage": {
    "inputTokens": 30,
    "outputTokens": 3932,
    "totalTokens": 3962,
    "perStage": {
      "planner": {
        "inputTokens": 9,
        "outputTokens": 2231,
        "totalTokens": 2240
      },
      "reviewer": {
        "inputTokens": 6,
        "outputTokens": 936,
        "totalTokens": 942
      },
      "judge": {
        "inputTokens": 5,
        "outputTokens": 312,
        "totalTokens": 317
      },
      "dedup": {
        "inputTokens": 10,
        "outputTokens": 453,
        "totalTokens": 463
      }
    }
  },
  "verdict": "APPROVE",
  "recap": {
    "priorRoundCount": 1,
    "reclassifiedPriorCount": 0
  }
}

Reviewed commit ae48b48

@manki-review

manki-review Bot commented May 26, 2026

Copy link
Copy Markdown

Review skipped for ae48b48 — a review has already been posted for this commit. Force a re-review:

  • Force review

@xdustinface xdustinface merged commit c2ec75d into dev May 26, 2026
39 of 40 checks passed
@xdustinface xdustinface deleted the test/dashd-reorg-integration branch May 26, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants