Skip to content

refactor(dash-spv): network manager refactor and sync pipelines optimized #902

Draft
ZocoLini wants to merge 22 commits into
devfrom
refactor/network-mod
Draft

refactor(dash-spv): network manager refactor and sync pipelines optimized #902
ZocoLini wants to merge 22 commits into
devfrom
refactor/network-mod

Conversation

@ZocoLini

@ZocoLini ZocoLini commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Incredible big PR that does many things to get to a point where the sync time is as low as possible, I will be working on refactoring and cleaning the introduced code in the next PRs but since we need a fast sync for a good user experience at the iOS release I would like to get this merge as long as it can sync

  • New dash-spv-bench crate that I used tot set different scenarios and be able to optimice and validate my changes|
  • New non-pub NetworkManager that is able to efficiently distribute work
  • Mechanism to connect to extra peers when under heavy load
  • Sync pipelines no longer wait for item to be stored to disc, they step forward as soon as the item they need is in memory
  • NetworkMessages enums fields in dashcore are being stored Behind a Box structure, to reduce memory consumption during sync
  • Items are stored to disc as soon as they arrive to avoid high memory usage

ZocoLini and others added 11 commits July 10, 2026 18:11
Resolved conflicts:
- client/mod.rs: restored dev's two client tests
  (birth_height_anchors_chain_to_nearest_checkpoint, client_exposes_shared_wallet_manager),
  adapted to the new DashSpvClient::new signature (network manager no longer passed in).
- sync/filters/manager.rs:
  - start_download: kept our frontier-aware scan_start computation AND dev's
    stored_covers_scan guard (#893, discard stored filters that don't reach scan_start);
    they are sequential concerns, not alternatives.
  - tests: dropped dev's 46-test `mod tests`. It is built on the pre-refactor
    network flow (RequestSender/NetworkRequest, removed here) and the old 4-arg
    FiltersManager::new, so it no longer compiles against this branch. Our
    scan_state_tests are kept; porting the salvageable subset is left as follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the sync checkpoint

The merge of dev brought in the checkpoint floor (a birth-0 HD wallet now
anchors mainnet sync at the 200000 checkpoint instead of genesis, since no HD
wallet has transactions before it). That exposed a latent bug in the filters
manager's stale-wallet check: it compared each wallet's raw `synced_height`
against `committed_height`. A fresh wallet reports `synced_height = 0`, but a
checkpoint sync sets `committed_height = checkpoint - 1` (199999), so the wallet
read as perpetually "behind" — every tick reset and restarted the scan, an
infinite loop that starved block/filter-header processing and stalled the whole
sync at ~12%.

No filters exist below the sync floor, so a wallet cannot be behind there: clamp
its effective scanned baseline to `header_start_height` before the comparison. A
genuinely late-added wallet (synced within the synced range) still triggers a
rescan; a fresh below-floor wallet no longer does.

Verified on a real mainnet bench: headers and filter headers now reach 100%
(were stuck at ~12%), zero "restarting scan" loops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…in-memory checkpoint sync

Second checkpoint-sync stall (after the rescan loop): the filters phase spun
without ever committing or matching — committed stuck at the checkpoint,
downloaded/matched 0 — because verifying the first filter batch failed with
"Missing filter header at height <checkpoint-1>".

Filter-header sync anchors the chain differently per mode. The storage path
stores the anchor (`cfheaders.previous_filter_header`) at `header_start - 1`, so
filter bodies verify from `header_start`. The in-memory path (which runs on a
fresh checkpoint sync) anchors it at `header_start` itself, so filter_header
[header_start - 1] never exists — yet the filters manager still tried to download
and verify a body at `header_start`, which needs exactly that missing header.

Raise the filters download/scan floor to `filter_header_start + 1` when the
anchor sits above genesis, so the first body downloaded is the lowest one whose
predecessor filter header is actually known. No-op for genesis (anchor at 0,
predecessor is the all-zeros sentinel) and for the storage/resume path (anchor at
header_start - 1, floor already equals header_start).

Verified on a real mainnet checkpoint sync: zero "Missing filter header" errors,
filters commit and match (blocks download with relevant transactions), phases
reach 100%. Integration: lib 184/184, dashd basic 3/3, restart 4/4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92ba2101-2e00-4a3e-a206-7bb19de16158

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/network-mod

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.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.17728% with 149 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.61%. Comparing base (19690d3) to head (a976adc).

Files with missing lines Patch % Lines
dash-spv/src/network/peer.rs 83.27% 46 Missing ⚠️
dash-spv/src/timer.rs 0.00% 28 Missing ⚠️
dash/src/network/message.rs 80.00% 17 Missing ⚠️
dash-spv/src/main.rs 0.00% 11 Missing ⚠️
dash-spv/src/network/discovery.rs 64.00% 9 Missing ⚠️
dash-spv/src/storage/segments.rs 95.89% 6 Missing ⚠️
dash-spv/src/storage/mod.rs 87.80% 5 Missing ⚠️
dash-spv/src/sync/filter_headers/pipeline.rs 92.18% 5 Missing ⚠️
dash-spv/src/sync/block_headers/segment_state.rs 89.47% 4 Missing ⚠️
dash-spv/src/sync/blocks/pipeline.rs 90.90% 3 Missing ⚠️
... and 9 more
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #902      +/-   ##
==========================================
- Coverage   74.54%   72.61%   -1.94%     
==========================================
  Files         327      319       -8     
  Lines       75032    69270    -5762     
==========================================
- Hits        55936    50297    -5639     
+ Misses      19096    18973     -123     
Flag Coverage Δ
core 77.31% <80.45%> (+0.01%) ⬆️
ffi 51.26% <88.88%> (+0.31%) ⬆️
rpc 20.00% <ø> (ø)
spv 87.63% <87.71%> (-3.58%) ⬇️
wallet 74.41% <ø> (ø)
Files with missing lines Coverage Δ
dash-spv-ffi/src/callbacks.rs 83.27% <100.00%> (-0.31%) ⬇️
dash-spv/src/client/core.rs 63.04% <100.00%> (+0.82%) ⬆️
dash-spv/src/client/event_handler.rs 93.76% <100.00%> (-0.13%) ⬇️
dash-spv/src/client/events.rs 100.00% <100.00%> (ø)
dash-spv/src/client/lifecycle.rs 92.85% <100.00%> (+0.58%) ⬆️
dash-spv/src/client/mod.rs 100.00% <100.00%> (ø)
dash-spv/src/client/queries.rs 14.81% <ø> (-6.40%) ⬇️
dash-spv/src/client/sync_coordinator.rs 78.49% <ø> (ø)
dash-spv/src/client/transactions.rs 100.00% <100.00%> (+10.00%) ⬆️
dash-spv/src/lib.rs 46.66% <ø> (ø)
... and 42 more

... and 24 files with indirect coverage changes

ZocoLini and others added 11 commits July 16, 2026 14:47
Intermittently a mainnet sync stalled at the very start of header sync with only
the first segment stored. Cause: at startup the reconnector races the probe —
it connects peers from the discoverer and the sync sends them the initial
per-segment GetHeaders, then `probe_and_select` settles the final peer set and
replaced `connected` wholesale, dropping those peers mid-request. The stranded
requests only recovered on the pipelines' own slow timeout, and occasionally a
run never remounted (the surviving peers were slow/quiet), stalling all segments
above the first.

Retire displaced peers by draining instead of dropping: if a peer has requests
in flight, keep its connection alive in the background (its reader keeps
delivering responses and decrementing `in_flight`) and close it only once it has
drained, capped at STALE_REQUEST so a peer that never drains is still reaped. A
peer with nothing in flight is closed immediately as before. Bonus: previously
these dropped peers leaked their reader tasks until global shutdown; now they
close cleanly.

Verified: 8/8 back-to-back mainnet syncs completed with zero stalls (a prior
run of 5 stalled 1); the one run that hit the churn scenario recovered via the
drain+retry instead of hanging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed number_prefix

cargo-deny fails the branch: indicatif 0.17.11 pulls number_prefix 0.4.0, flagged
unmaintained (RUSTSEC-2025-0119). indicatif 0.18 replaced number_prefix with
unit-prefix, so the bump removes the advisory (and one duplicate windows-sys) from
the tree. Drop-in — the bench only uses stable MultiProgress/ProgressBar/
ProgressStyle APIs. (Cargo.lock is gitignored, so CI resolves this fresh.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… doc

`cargo doc` with `-D warnings` failed: the public trait method
`SyncManager::handle_network_event` doc-linked `default_handle_network_event`,
which is `pub(super)` — a private-intra-doc-link, denied by
`rustdoc::private_intra_doc_links`.

The delegation advice ("override and defer the rest to the default body; Rust
can't call a trait's default body from an override") is guidance for the crate's
own sync-manager implementors, not the public API — an external reader can't call
the `pub(super)` helper anyway. So drop it from the public method doc, which now
just describes behavior, and move the full note onto `default_handle_network_event`
itself (a private item, only rendered under `--document-private-items`, where the
link resolves cleanly). `default_handle_network_event` stays private — exposing it
publicly just to satisfy a link would grow the API surface for no external benefit.

Verified: `RUSTDOCFLAGS="-D warnings" cargo doc -p dash-spv --all-features --no-deps` clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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