Skip to content

feat(dash-spv): accelerate compressed header sync - #950

Open
PastaPastaPasta wants to merge 6 commits into
dashpay:devfrom
PastaPastaPasta:perf/reuse-headers2-hashes
Open

feat(dash-spv): accelerate compressed header sync#950
PastaPastaPasta wants to merge 6 commits into
dashpay:devfrom
PastaPastaPasta:perf/reuse-headers2-hashes

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

  • retain each X11 hash already computed while decompressing a DIP-0025 headers2 batch and carry it into HashedBlockHeader, avoiding the second hash previously performed by the ordered sync pipeline
  • preserve per-peer wire order across both compressed and fallback regular header messages when concurrent decompression finishes out of order, preventing later announcements from overtaking their parents
  • cancel in-flight decompression with its peer reader session so stale results cannot reach a later connection reusing the same address
  • decompress independent, self-contained headers2 messages concurrently, bounded globally to the lesser of four workers or the host's available parallelism
  • limit speculative download work to an eight-segment sliding window, reducing peak buffered headers without reducing local throughput
  • reset decompression history at every message boundary, matching Dash Core's per-message compression state

Each checkpoint segment still permits only one in-flight request, so messages for the same segment cannot be reordered. Work from different segments may complete in any order, which the existing checkpoint pipeline already supports across peers.

Real testnet benchmark

The release binaries synced all 1,531,863 testnet headers from the same settled local Dash Core node, with full header validation, one configured peer, filters/masternodes/mempool disabled, and a fresh SPV storage directory for every run. Before and after builds were alternated. Warmups were excluded. A sequence during which the Core process restarted was discarded; the reported final sequence was collected only after Core reported Done loading and another warmup completed.

Network Runs Before median After median Improvement
Local 3 per build 11.30 s 2.96 s 73.8% less time (3.82x)
Proxy, 50 ms each direction (~100 ms RTT) 5 per build 11.25 s 6.13 s 45.5% less time (1.84x)

Local runs were 11.34/11.30/11.28 s before and 2.96/2.98/2.91 s after. RTT runs were 11.22/11.15/11.61/11.25/11.49 s before and 6.38/6.13/8.15/6.01/6.04 s after.

An intermediate hash-reuse-only build measured 11.43 -> 10.84 s locally (5.2%) and 11.31 -> 10.74 s at ~100 ms RTT (5.0%). Sampling explained the smaller wall-clock change: before this PR, decompression and pipeline hashing were two similarly hot X11 stages already overlapped on separate Tokio tasks. Removing the second hash approximately halves that CPU work but makes the remaining single decompressor the throughput limiter; bounded parallel decompression realizes the larger wall-clock gain.

Peak buffered headers fell from a 1,423,863 median to 342,000, a 76.0% reduction. Profiling showed X11 rather than storage writes as the CPU bottleneck; the large buffer came from all 31 checkpoint segments downloading ahead of the earliest segment that could be stored in order.

Clean current-Core rerun

After rebasing onto current dev, the release build was rerun against a freshly built, clean Dash Core develop@4defcfbe7b5a on local testnet at fixed height 1,532,360. That Core revision includes merged Dash Core PRs #7573 (configured SHA256 acceleration) and #7574 (compact-filter file-handle reuse), with no experimental cfilters2 changes. Each measurement used fresh SPV storage; one warm-up was excluded.

Workflow Measured runs Median
Headers only, full validation 2.86 / 2.83 / 2.88 s 2.86 s
Default full sync 12.61 / 11.95 / 12.46 s 12.46 s

The default run synchronized 1,532,360 headers, 1,532,361 filter headers and filters, masternode data, and mempool setup. The test mnemonic matched 733 filters; 733 historical blocks were processed, containing 6 relevant transactions. The masternode path processed 27 diffs, one QRInfo, and one validated cycle.

A reviewer-suggested host-wide decompression test was also run on the 14-CPU host. Four workers measured a 2.90 s median; 14 workers measured 4.31 s, a 48.6% regression, so the four-worker global cap remains intentional.

Validation

  • cargo test -p dash-spv --lib (546 passed, 2 ignored)
  • cargo test -p dashcore --lib (578 passed, 15 ignored)
  • cargo test -p dashcore --test headers2_compatibility_test --all-features (12 passed)
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo build -p dash-spv --release
  • final-head real-node series: headers median 2.86 s; default full-sync median 12.46 s
  • exact CI-failing post-sync FFI scenario: 5/5 passes on the final bounded ordering implementation
  • deterministic peer-reader compressed/regular wire-order integration test: 10/10 passes

This pull request was created by Codex.

Summary by CodeRabbit

  • Performance

    • Improved compressed-header processing with bounded parallel decompression.
    • Reduced repeated hash calculations during header synchronization.
  • Reliability

    • Invalid compressed-header data is handled more safely.
    • Added validation for mismatched header and hash counts.
    • Compression state resets correctly between message batches.
    • Preserved message ordering during asynchronous processing.
  • Sync Improvements

    • Limited header synchronization to an active eight-segment window.
    • Preserved header hashes throughout synchronization.
    • Improved ordered processing of multi-header batches and pipeline refilling.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change propagates precomputed header hashes through Headers processing, adds an eight-segment request window, and moves Headers2 decompression to bounded blocking tasks with shutdown-aware ordered dispatch.

Changes

Header synchronization and decompression

Layer / File(s) Summary
Headers2 hash-aware decompression
dash/src/network/message_headers2.rs
CompressionState validates cached hashes, returns hashes from batch decompression, and resets state between batches.
Header message hash propagation
dash-spv/src/network/message_dispatcher.rs, dash-spv/src/sync/block_headers/sync_manager.rs, dash-spv/src/types.rs
Message stores optional header hashes. Headers handling validates hash counts and creates HashedBlockHeader values.
Hashed header pipeline and active window
dash-spv/src/sync/block_headers/manager.rs, dash-spv/src/sync/block_headers/pipeline.rs, dash-spv/src/sync/block_headers/segment_state.rs
Pipeline and segment processing consume hashed headers. Pending requests use an eight-segment active window.
Bounded asynchronous Headers2 handling
dash-spv/src/network/manager.rs, dash-spv/tests/header_dispatch_order.rs
Peer readers share a semaphore capped at four tasks. Headers2 messages use spawn_blocking and dispatch results in wire order. Invalid data disables Headers2 and penalizes the peer.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PeerReader
  participant Semaphore
  participant BlockingWorker
  participant MessageDispatcher
  PeerReader->>Semaphore: acquire Headers2 permit
  Semaphore->>BlockingWorker: run bounded decompression
  PeerReader->>BlockingWorker: decompress Headers2
  BlockingWorker-->>PeerReader: return headers, hashes, or error
  PeerReader->>MessageDispatcher: dispatch headers in wire order
Loading

Possibly related PRs

Suggested labels: ready-for-review

Suggested reviewers: zocolini

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: faster compressed header synchronization in dash-spv.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@dash-spv/src/sync/block_headers/pipeline.rs`:
- Line 145: Update receive_headers to reject unsolicited non-empty batches when
the sync is complete, requiring exactly one header before resetting the tip and
forwarding it to SegmentState::receive_headers; preserve the existing
requested-sync path for legitimate batches. Add an in-module test using two
chained headers that verifies an error is returned and the completed tip state
remains unchanged.
- Around line 128-129: Update handle_headers_pipeline to call send_pending after
take_ready_to_store so the active window is immediately refilled when draining
advances next_to_store. Add in-module and integration tests covering all active
segments completing with the final lowest-index segment, verifying the next
segment is requested in the same response cycle rather than waiting for the next
tick.

In `@dash/src/network/message_headers2.rs`:
- Around line 275-276: Keep the cached hash paired with the exact previous
header it was computed from, rather than storing only prev_header_hash. Update
is_sequential and compress to use the cached hash only when that stored header
still equals the current public prev_header; otherwise recompute or follow the
normal non-cached path. Add a unit test that changes or clears prev_header after
decompress_with_hash and verifies compression does not omit an incorrect
prev_blockhash.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 15ecb17a-5430-4778-b163-1c9a1c5b482a

📥 Commits

Reviewing files that changed from the base of the PR and between 5a80bd7 and d4ea644.

📒 Files selected for processing (8)
  • dash-spv/src/network/manager.rs
  • dash-spv/src/network/message_dispatcher.rs
  • dash-spv/src/sync/block_headers/manager.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/types.rs
  • dash/src/network/message_headers2.rs

Comment thread dash-spv/src/sync/block_headers/pipeline.rs
Comment thread dash-spv/src/sync/block_headers/pipeline.rs
Comment thread dash/src/network/message_headers2.rs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

@github-actions github-actions Bot added the merge-conflict The PR conflicts with the target branch. label Aug 11, 2026

@ZocoLini ZocoLini 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.

This is not going through CI but the idea is definitely good

Comment thread dash-spv/src/types.rs Outdated
&mut self,
compressed: &CompressedHeader,
) -> Result<Header, DecompressionError> {
) -> Result<(Header, BlockHash), DecompressionError> {

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.

Maybe return a HashedBlockHeader

@PastaPastaPasta PastaPastaPasta Aug 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

HashedBlockHeader is defined in dash-spv, while this decompressor is in the lower-level dash crate. Returning that type here would introduce an inverted/circular dependency. The tuple keeps the protocol layer independent, and dash-spv immediately combines the exact (Header, BlockHash) pair with HashedBlockHeader::with_trusted_hash at its boundary.


🤖 Posted autonomously by Codex on behalf of pasta.

std::thread::available_parallelism()
.map(|parallelism| parallelism.get().min(MAX_CONCURRENT_HEADERS2_DECOMPRESSIONS))
.unwrap_or(1)
}

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.

I don't think this function ads real value, did you try what happens if you compute as many as you can using the Tokio runtime?? I don't see it overloading the CPU

@PastaPastaPasta PastaPastaPasta Aug 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tested this today against clean current Dash Core at testnet height 1,532,360. With fresh storage after warm-up, the four-worker median was 2.90 s (2.91/2.90/2.86); using all 14 available CPUs regressed to 4.31 s (4.25/4.31/4.69), about 48.6% slower. The global bound also applies consistently to both network-manager construction paths, so I retained the helper and four-worker cap. I added the A/B result to the PR description.


🤖 Posted autonomously by Codex on behalf of pasta.

@PastaPastaPasta
PastaPastaPasta force-pushed the perf/reuse-headers2-hashes branch from d4ea644 to 9b14095 Compare August 11, 2026 21:29
@github-actions github-actions Bot removed the merge-conflict The PR conflicts with the target branch. label Aug 11, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 11, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dash-spv/src/network/manager.rs (1)

631-721: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve ordering across both header message variants.

The ordered buffer releases only Headers2 outcomes. After Line 721, the reader can receive and dispatch a later regular Headers message before this earlier Headers2 batch completes. The regular Headers branch remains accepted and forwarded by this reader.

Route both header message variants through one ordered dispatch barrier. This prevents a later header announcement from overtaking an earlier compressed announcement.

🤖 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/network/manager.rs` around lines 631 - 721, Extend the existing
ordered dispatch mechanism around ordered_headers2_results so regular
NetworkMessage::Headers and compressed Headers2 share the same sequence and
release barrier. Assign every header message a sequence number, enqueue the
regular Headers outcome instead of dispatching it immediately, and only forward
results in sequence order; preserve the existing Headers2 decompression handling
and ensure later regular headers cannot overtake an earlier Headers2 batch.
🤖 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 `@dash-spv/src/network/manager.rs`:
- Around line 2143-2155: Expand coverage beyond
`OrderedHeaders2Results::complete` with in-module tests for peer-session
cancellation, regular `Headers` ordering, invalid decompression handling, and
permit release after task failure. Add an integration test under the crate’s
`tests` directory that exercises ordered dispatch through the peer reader,
preserving receive-order behavior.
- Around line 655-695: Update the Headers2 decompression task spawned in the
peer reader to use a reader-scoped cancellation token or connection generation,
rather than checking only shutdown_token. Cancel or invalidate that scope before
the reader removes the peer, and check it before dispatching each completed
result in the ordered-results loop so ended sessions cannot forward headers to a
later connection using the same SocketAddr.

---

Outside diff comments:
In `@dash-spv/src/network/manager.rs`:
- Around line 631-721: Extend the existing ordered dispatch mechanism around
ordered_headers2_results so regular NetworkMessage::Headers and compressed
Headers2 share the same sequence and release barrier. Assign every header
message a sequence number, enqueue the regular Headers outcome instead of
dispatching it immediately, and only forward results in sequence order; preserve
the existing Headers2 decompression handling and ensure later regular headers
cannot overtake an earlier Headers2 batch.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c0f1cfa-bfde-4af6-a282-12df0de22472

📥 Commits

Reviewing files that changed from the base of the PR and between 34e4175 and fd8999a.

📒 Files selected for processing (1)
  • dash-spv/src/network/manager.rs

Comment thread dash-spv/src/network/manager.rs Outdated
Comment thread dash-spv/src/network/manager.rs
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.52066% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.77%. Comparing base (37b1a36) to head (055b73c).

Files with missing lines Patch % Lines
dash-spv/src/network/manager.rs 95.59% 10 Missing ⚠️
dash-spv/src/sync/block_headers/pipeline.rs 97.56% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #950      +/-   ##
==========================================
+ Coverage   75.62%   75.77%   +0.14%     
==========================================
  Files         329      329              
  Lines       79258    79671     +413     
==========================================
+ Hits        59939    60367     +428     
+ Misses      19319    19304      -15     
Flag Coverage Δ
core 77.36% <100.00%> (+0.06%) ⬆️
ffi 49.03% <ø> (ø)
rpc 20.00% <ø> (ø)
spv 91.84% <97.03%> (+0.21%) ⬆️
wallet 77.57% <ø> (ø)
Files with missing lines Coverage Δ
dash-spv/src/network/message_dispatcher.rs 100.00% <100.00%> (ø)
dash-spv/src/sync/block_headers/manager.rs 91.90% <100.00%> (+1.13%) ⬆️
dash-spv/src/sync/block_headers/segment_state.rs 97.35% <100.00%> (-0.02%) ⬇️
dash-spv/src/sync/block_headers/sync_manager.rs 87.50% <100.00%> (+0.83%) ⬆️
dash-spv/src/types.rs 88.78% <ø> (ø)
dash/src/network/message_headers2.rs 87.78% <100.00%> (+2.26%) ⬆️
dash-spv/src/sync/block_headers/pipeline.rs 96.18% <97.56%> (+0.80%) ⬆️
dash-spv/src/network/manager.rs 79.31% <95.59%> (+4.72%) ⬆️

... and 4 files with indirect coverage changes

coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 `@dash-spv/tests/header_dispatch_order.rs`:
- Around line 20-24: Parameterize the header-dispatch ordering tests and their
send_message fixture to accept both a Network value and listener address,
running the scenario for Network::Mainnet and Network::Testnet. In send_message,
derive RawNetworkMessage.magic from the selected network instead of hardcoding
Network::Regtest.magic().
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dfff518e-b782-45c9-8440-371e09f857d2

📥 Commits

Reviewing files that changed from the base of the PR and between fd8999a and 055b73c.

📒 Files selected for processing (2)
  • dash-spv/src/network/manager.rs
  • dash-spv/tests/header_dispatch_order.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • dash-spv/src/network/manager.rs

Comment thread dash-spv/tests/header_dispatch_order.rs
@PastaPastaPasta
PastaPastaPasta dismissed coderabbitai[bot]’s stale review August 12, 2026 02:51

Dismissed after investigation: the sole finding requested duplicate Mainnet/Testnet executions of a network-independent Regtest localhost ordering regression. The thread documents the call-path evidence and is resolved; no code change is warranted.


🤖 Posted autonomously by Codex on behalf of pasta.

@github-actions github-actions Bot added ready-for-review CodeRabbit has approved this PR and removed ready-for-review CodeRabbit has approved this PR labels Aug 12, 2026
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