Skip to content

feat(dash-spv): block locator + staged fork detection#803

Draft
xdustinface wants to merge 1 commit into
devfrom
feat/fork-detection
Draft

feat(dash-spv): block locator + staged fork detection#803
xdustinface wants to merge 1 commit into
devfrom
feat/fork-detection

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented Jun 4, 2026

Replace the single-hash GetHeaders locator with a dashd-style exponential-backoff locator so peers on a fork can find a recent common ancestor.

Route header batches whose prev_blockhash resolves to a non-tip stored header into a per-peer ForkBuffer, validating each header against PoW, BIP113 MTP, and a port of dashd's DGW v3 retarget anchored at the fork ancestor. Branches age out after a 60s TTL and on peer disconnect. A ForkCandidate is exposed via take_pending_fork_candidate for the sync coordinator to promote once a fork's extension work beats the active chain.

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented Dash Dark Gravity Wave v3 difficulty retargeting for proper network difficulty calculations
    • Added fork candidate buffering for improved fork chain detection during block synchronization
    • Enhanced block header synchronization with locator-based request routing
  • Bug Fixes

    • Added fork chain continuity validation and error detection

Replace the single-hash `GetHeaders` locator with a dashd-style exponential-backoff locator so peers on a fork can find a recent common ancestor.

Route header batches whose `prev_blockhash` resolves to a non-tip stored header into a per-peer `ForkBuffer`, validating each header against PoW, BIP113 MTP, and a port of dashd's DGW v3 retarget anchored at the fork ancestor. Branches age out after a 60s TTL and on peer disconnect. A `ForkCandidate` is exposed via `take_pending_fork_candidate` for the sync coordinator to promote once a fork's extension work beats the active chain.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a staged fork candidate buffering system integrated with Dark Gravity Wave v3 difficulty retargeting. It replaces the legacy ChainTip/ChainTipManager with a ForkCandidate struct, adds comprehensive DGW v3 consensus logic, introduces a per-peer ForkBuffer for validating fork branches, updates the sync pipeline to route forks intelligently, and refactors request APIs to use Dash-style block locators throughout.

Changes

Fork candidate staging and DGW v3 retargeting

Layer / File(s) Summary
Chain primitives refactoring: ForkCandidate, ChainWork, U256 exposure
dash-spv/src/chain/chain_tip.rs, dash-spv/src/chain/chain_work.rs, dash-spv/src/chain/mod.rs, dash-spv/src/client/lifecycle.rs, dash-spv/src/error.rs, dash/src/pow.rs, dash-spv/src/sync/block_headers/manager.rs, dash-spv/src/test_utils/*
ChainTip/ChainTipManager removed and replaced with ForkCandidate (ancestor_height, headers, total_work); ChainWork::accumulate added for bulk work folding; U256 struct, ZERO constant, and big-endian conversion methods exposed publicly; legacy ChainTip::dummy test helper removed; BlockHeadersManager test infrastructure updated.
DGW v3 difficulty retargeting and validation
dash-spv/src/chain/difficulty.rs
Implements consensus-critical next_work_required_dgw_v3 computing next target from 24-block history with timespan clamping and per-network pow limits; min_difficulty_bits handles Dash min-difficulty exceptions for networks allowing them; pow_limit_target selects hardcoded mainnet/testnet vs regtest/devnet limits; comprehensive unit tests validate pow-limit short-circuits, DGW spacing bias, and min-difficulty acceptance.
Fork buffer: per-peer validation and staging
dash-spv/src/sync/block_headers/fork_buffer.rs, dash-spv/src/sync/block_headers/mod.rs
ForkBuffer stores validated fork branches per-peer keyed by (peer, tip_hash) with sophisticated ingestion: enforces header chaining, validates PoW targets, applies strict MTP over ancestor history, verifies DGW v3 expected bits with min-difficulty exception path. Provides expire_stale (TTL eviction), remove_peer (cleanup), take_winning_candidate (strict work-based selection), and branch_tip_hashes (enumeration). Extensive test suite covers acceptance/rejection for PoW, DGW, MTP, pre-window, deduplication, TTL, peer caps, chain discontinuity, and min-difficulty scenarios.
Header manager: fork detection, routing, and locator building
dash-spv/src/sync/block_headers/manager.rs
BlockHeadersManager gains fork buffer, pending fork candidate, and fork tip index fields; accepts Network parameter for initialization. Implements pre-pipeline fork detection classifying incoming batches by comparing prev_blockhash height or consulting fork_tip_index; routes forks to ingest_fork and fork continuations with ForkChainBreak error swallowing. Adds build_locator helper constructing Dash-style locators (first 10 by 1, then double-step) from storage tip. Exposes take_pending_fork_candidate for promotion. Updates handle_headers_pipeline signature with peer SocketAddr. Tests verify non-fork extension detection, locator shape, fork routing behavior, and error propagation.
Request API and pipeline locator wiring
dash-spv/src/network/mod.rs, dash-spv/src/sync/block_headers/pipeline.rs, dash-spv/src/sync/block_headers/segment_state.rs
RequestSender::request_block_headers methods now accept Vec<BlockHash> locator instead of single start_hash. HeadersPipeline::send_pending accepts tip_locator and conditionally sends full locator for tip segment (when matching current_tip_hash) or falls back to single-entry locator. SegmentState::send_request takes locator vector with debug-asserts for non-empty and proper start, sending requests using full locator. Tests updated and new test verifies tip-segment locator selection logic.
Sync manager: fork buffer maintenance and lifecycle integration
dash-spv/src/sync/block_headers/sync_manager.rs
Adds FORK_BUFFER_TTL (60s) constant. During start_sync, builds and passes locator to pipeline. On handle_message for Headers, forwards peer address. In tick: expires stale branches, prunes fork_tip_index, logs candidate readiness, uses empty locator during initial sync, rebuilds locator for stale-announcement fallback. On handle_network_event: builds locator for tip announcement to new peers; on disconnect, removes peer from buffer and prunes index; on catch-up, builds and passes locator into pipeline.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

ready-for-review

Suggested reviewers

  • ZocoLini

🐰 A fork in the chain, now staged with care,
DGW v3 dancing through the air!
Buffers validated, locators built so tight,
Fork candidates await their rightful flight. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(dash-spv): block locator + staged fork detection' directly and clearly summarizes the main changes: implementing block locators for GetHeaders requests and adding fork detection via a staged buffer.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fork-detection

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 and usage tips.

@xdustinface
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 96.51972% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.89%. Comparing base (a132945) to head (99e8f20).

Files with missing lines Patch % Lines
dash-spv/src/sync/block_headers/manager.rs 92.99% 22 Missing ⚠️
dash-spv/src/chain/difficulty.rs 97.31% 4 Missing ⚠️
dash-spv/src/sync/block_headers/fork_buffer.rs 99.42% 2 Missing ⚠️
dash-spv/src/sync/block_headers/pipeline.rs 94.44% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #803      +/-   ##
==========================================
+ Coverage   72.67%   72.89%   +0.21%     
==========================================
  Files         322      323       +1     
  Lines       71363    72034     +671     
==========================================
+ Hits        51866    52507     +641     
- Misses      19497    19527      +30     
Flag Coverage Δ
core 76.54% <100.00%> (ø)
ffi 46.20% <ø> (-0.22%) ⬇️
rpc 20.00% <ø> (ø)
spv 90.62% <96.51%> (+0.31%) ⬆️
wallet 71.29% <ø> (ø)
Files with missing lines Coverage Δ
dash-spv/src/chain/chain_work.rs 84.92% <100.00%> (+6.45%) ⬆️
dash-spv/src/client/lifecycle.rs 78.23% <100.00%> (+0.11%) ⬆️
dash-spv/src/error.rs 33.33% <ø> (ø)
dash-spv/src/network/mod.rs 98.03% <100.00%> (-0.06%) ⬇️
dash-spv/src/sync/block_headers/segment_state.rs 97.43% <100.00%> (+0.06%) ⬆️
dash-spv/src/sync/block_headers/sync_manager.rs 84.61% <ø> (ø)
dash/src/pow.rs 93.67% <100.00%> (ø)
dash-spv/src/sync/block_headers/fork_buffer.rs 99.42% <99.42%> (ø)
dash-spv/src/sync/block_headers/pipeline.rs 94.19% <94.44%> (-0.03%) ⬇️
dash-spv/src/chain/difficulty.rs 97.31% <97.31%> (ø)
... and 1 more

... and 19 files with indirect coverage changes

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
dash-spv/src/sync/block_headers/fork_buffer.rs (1)

142-150: 💤 Low value

Clarify total_work semantics in BufferedBranch vs ForkCandidate.

The total_work stored in BufferedBranch (line 150) is the fork extension's work only (accumulated from ChainWork::zero()), not the cumulative work including the ancestor chain. This is compared against active_extension_work in take_winning_candidate, which is correct for determining the winner.

However, the returned ForkCandidate.total_work (line 194) inherits this same "extension-only" value. If downstream code expects cumulative chain work, this could cause issues. Consider either:

  1. Renaming to extension_work for clarity, or
  2. Adding a doc comment to ForkCandidate::total_work clarifying it represents extension work only.
🤖 Prompt for 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.

In `@dash-spv/src/sync/block_headers/fork_buffer.rs` around lines 142 - 150, The
BufferedBranch.total_work currently holds only the fork-extension work (computed
via ChainWork::accumulate(ChainWork::zero(), ...)), but
ForkCandidate::total_work is populated with that same extension-only value and
may be misleading; update the code to make the semantics explicit: either rename
BufferedBranch.total_work to extension_work and propagate that rename everywhere
(including where branches are inserted and read in take_winning_candidate) or
add a clear doc comment on ForkCandidate::total_work stating it represents
extension-only work; if you choose to expose cumulative work instead, compute
cumulative_work by adding the ancestor chain work to the extension (use the
ancestor chain's ChainWork when constructing ForkCandidate in
take_winning_candidate) and return that value. Ensure all references to
BufferedBranch.total_work, ForkCandidate::total_work, and the comparison in
take_winning_candidate remain consistent after the change.
🤖 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.

Nitpick comments:
In `@dash-spv/src/sync/block_headers/fork_buffer.rs`:
- Around line 142-150: The BufferedBranch.total_work currently holds only the
fork-extension work (computed via ChainWork::accumulate(ChainWork::zero(),
...)), but ForkCandidate::total_work is populated with that same extension-only
value and may be misleading; update the code to make the semantics explicit:
either rename BufferedBranch.total_work to extension_work and propagate that
rename everywhere (including where branches are inserted and read in
take_winning_candidate) or add a clear doc comment on ForkCandidate::total_work
stating it represents extension-only work; if you choose to expose cumulative
work instead, compute cumulative_work by adding the ancestor chain work to the
extension (use the ancestor chain's ChainWork when constructing ForkCandidate in
take_winning_candidate) and return that value. Ensure all references to
BufferedBranch.total_work, ForkCandidate::total_work, and the comparison in
take_winning_candidate remain consistent after the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6f571b9f-9afd-443b-9c85-4d3c1d6dfccd

📥 Commits

Reviewing files that changed from the base of the PR and between a132945 and 99e8f20.

📒 Files selected for processing (16)
  • dash-spv/src/chain/chain_tip.rs
  • dash-spv/src/chain/chain_work.rs
  • dash-spv/src/chain/difficulty.rs
  • dash-spv/src/chain/mod.rs
  • dash-spv/src/client/lifecycle.rs
  • dash-spv/src/error.rs
  • dash-spv/src/network/mod.rs
  • dash-spv/src/sync/block_headers/fork_buffer.rs
  • dash-spv/src/sync/block_headers/manager.rs
  • dash-spv/src/sync/block_headers/mod.rs
  • dash-spv/src/sync/block_headers/pipeline.rs
  • dash-spv/src/sync/block_headers/segment_state.rs
  • dash-spv/src/sync/block_headers/sync_manager.rs
  • dash-spv/src/test_utils/chain_tip.rs
  • dash-spv/src/test_utils/mod.rs
  • dash/src/pow.rs
💤 Files with no reviewable changes (2)
  • dash-spv/src/test_utils/mod.rs
  • dash-spv/src/test_utils/chain_tip.rs

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.

1 participant