Skip to content

feat(dash-spv): anchor sync at wallet birth height checkpoint#848

Merged
xdustinface merged 1 commit into
devfrom
feat/anchor-to-checkpoint
Jul 9, 2026
Merged

feat(dash-spv): anchor sync at wallet birth height checkpoint#848
xdustinface merged 1 commit into
devfrom
feat/anchor-to-checkpoint

Conversation

@xdustinface

@xdustinface xdustinface commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

When no explicit start_from_height is configured, DashSpvClient::new now derives the sync anchor from the wallet's earliest_required_height and starts from the nearest checkpoint at or before it. Previously the wallet birth height only short-circuited the compact-filter download, while block headers and filter headers always synced from genesis. Explicit start_from_height still takes precedence.

This also fixes the checkpoint anchoring itself, which never worked. initialize_genesis_block rebuilt the checkpoint header with a hardcoded version 0x20000000 and verified the recomputed hash against the stored block_hash. Old blocks use version 2, so the hash never matched, the code logged a warning and silently fell back to a full genesis sync, defeating start_from_height entirely.

The Checkpoint does not store the block version, so the exact header bytes cannot be reconstructed. Since the checkpoint block_hash is a trusted constant and chain linkage only ever compares against the stored hash (never a recomputed one), we now store the anchor via the new HashedBlockHeader::with_trusted_hash, pairing the reconstructed header with the trusted hash. time and bits still come from the checkpoint for difficulty checks of later headers.

Based on:

Summary by CodeRabbit

  • New Features

    • The client now starts syncing from a more appropriate checkpoint based on wallet activity or an explicit start height.
    • Network-specific checkpoint handling now works automatically across supported networks.
  • Bug Fixes

    • Improved chain anchoring so initial sync begins from the nearest trusted checkpoint instead of always rebuilding from scratch.
    • Genesis handling is more reliable when no checkpoint is available.
  • Tests

    • Added coverage for wallet birth height and manual start height behavior during initial sync.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a CheckpointManager::for_network constructor for selecting checkpoints by network, a HashedBlockHeader::with_trusted_hash constructor for trusted-hash header creation, and reworks SPV client startup to compute an effective start height and anchor storage to the nearest checkpoint via trusted hashes, with new tests.

Changes

Checkpoint anchoring for SPV client startup

Layer / File(s) Summary
CheckpointManager::for_network constructor
dash-spv/src/chain/checkpoints.rs
Adds Network import and a for_network(network) constructor selecting mainnet/testnet checkpoint lists or empty for other networks.
HashedBlockHeader trusted-hash constructor
dash-spv/src/types.rs
Adds with_trusted_hash(header, hash) to build a HashedBlockHeader from a caller-supplied hash without recomputation.
Client startup start_from_height computation and checkpoint manager wiring
dash-spv/src/client/lifecycle.rs
Reworks imports, computes start_from_height from config or wallet earliest_required_height(), passes it into genesis initialization, and switches to CheckpointManager::for_network.
Genesis/checkpoint initialization rework
dash-spv/src/client/lifecycle.rs
Extends initialize_genesis_block to accept start_from_height, selects nearest checkpoint at/below it, and anchors storage using HashedBlockHeader::with_trusted_hash instead of the prior hash-verification flow.
Anchoring test helper and coverage
dash-spv/src/client/mod.rs
Adds TEST_MNEMONIC, an anchored_tip helper, and a tokio test validating checkpoint anchoring across different birth heights and explicit start_from_height overrides.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • dashpay/rust-dashcore#693: Adds birth_height to wallet import/FFI that seeds the earliest_required_height this PR uses to derive start_from_height.
  • dashpay/rust-dashcore#784: Also modifies DashSpvClient::new in lifecycle.rs, affecting config preparation that composes with this PR's checkpoint anchoring logic.
  • dashpay/rust-dashcore#834: Prior HashedBlockHeader-wrapper storage refactor that this PR's checkpoint anchoring builds on.

Suggested labels: ready-for-review

🚥 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 clearly matches the main change: syncing from the wallet birth-height checkpoint instead of always starting from genesis.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/anchor-to-checkpoint

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 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.53%. Comparing base (832bab2) to head (efef0e9).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #848      +/-   ##
==========================================
+ Coverage   73.43%   73.53%   +0.09%     
==========================================
  Files         324      324              
  Lines       73150    73197      +47     
==========================================
+ Hits        53721    53826     +105     
+ Misses      19429    19371      -58     
Flag Coverage Δ
core 77.08% <ø> (ø)
ffi 46.07% <ø> (+0.20%) ⬆️
rpc 20.00% <ø> (ø)
spv 90.78% <100.00%> (+0.25%) ⬆️
wallet 72.97% <ø> (ø)
Files with missing lines Coverage Δ
dash-spv/src/chain/checkpoints.rs 98.26% <100.00%> (+0.01%) ⬆️
dash-spv/src/client/lifecycle.rs 92.22% <100.00%> (+14.95%) ⬆️
dash-spv/src/client/mod.rs 100.00% <100.00%> (ø)
dash-spv/src/types.rs 88.78% <100.00%> (+0.66%) ⬆️

... and 17 files with indirect coverage changes

Base automatically changed from feat/feed-wallet-synced-height to dev July 7, 2026 12:28
@xdustinface
xdustinface force-pushed the feat/anchor-to-checkpoint branch from cd6c04e to 02850d4 Compare July 8, 2026 22:50
When no explicit `start_from_height` is configured, `DashSpvClient::new` now derives the sync anchor from the wallet's `earliest_required_height` and starts from the nearest checkpoint at or before it. Previously the wallet birth height only short-circuited the compact-filter download, while block headers and filter headers always synced from genesis. Explicit `start_from_height` still takes precedence.

This also fixes the checkpoint anchoring itself, which never worked. `initialize_genesis_block` rebuilt the checkpoint header with a hardcoded version `0x20000000` and verified the recomputed hash against the stored `block_hash`. Old blocks use version `2`, so the hash never matched, the code logged a warning and silently fell back to a full genesis sync, defeating `start_from_height` entirely.

The `Checkpoint` does not store the block version, so the exact header bytes cannot be reconstructed. Since the checkpoint `block_hash` is a trusted constant and chain linkage only ever compares against the stored hash (never a recomputed one), we now store the anchor via the new `HashedBlockHeader::with_trusted_hash`, pairing the reconstructed header with the trusted hash. `time` and `bits` still come from the checkpoint for difficulty checks of later headers.
@xdustinface
xdustinface marked this pull request as ready for review July 9, 2026 06:46
@xdustinface
xdustinface force-pushed the feat/anchor-to-checkpoint branch from 02850d4 to efef0e9 Compare July 9, 2026 06:47
@xdustinface

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

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

🧹 Nitpick comments (1)
dash-spv/src/client/lifecycle.rs (1)

282-284: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use checkpoint.target.to_compact_lossy() directly

The extra CompactTarget::from_consensus(...to_consensus()) round-trip is redundant here; Header::bits already takes a CompactTarget.

🤖 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/client/lifecycle.rs` around lines 282 - 284, `Header::bits` is
being assigned through a redundant
`CompactTarget::from_consensus(...to_consensus())` round-trip in `lifecycle.rs`.
Simplify the `checkpoint.target` conversion in the `Header` construction by
using `checkpoint.target.to_compact_lossy()` directly for the `bits` field, and
remove the unnecessary consensus conversion chain.
🤖 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/client/lifecycle.rs`:
- Around line 282-284: `Header::bits` is being assigned through a redundant
`CompactTarget::from_consensus(...to_consensus())` round-trip in `lifecycle.rs`.
Simplify the `checkpoint.target` conversion in the `Header` construction by
using `checkpoint.target.to_compact_lossy()` directly for the `bits` field, and
remove the unnecessary consensus conversion chain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4e4970ae-da78-4460-8fea-0bcf32833503

📥 Commits

Reviewing files that changed from the base of the PR and between 832bab2 and efef0e9.

📒 Files selected for processing (4)
  • dash-spv/src/chain/checkpoints.rs
  • dash-spv/src/client/lifecycle.rs
  • dash-spv/src/client/mod.rs
  • dash-spv/src/types.rs

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label Jul 9, 2026
@xdustinface
xdustinface requested a review from ZocoLini July 9, 2026 10:05

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

looks good, sam said something about main net starting in 720.000, are you working on that in a different PR??

@xdustinface

Copy link
Copy Markdown
Collaborator Author

looks good, sam said something about main net starting in 720.000, are you working on that in a different PR??

From the conversation in standup i was under the impression thats already being done in the app. If not we can follow up to this PR. I think it feels a bit more like something we should do in the app even though it would be centralized in rust-dashcore.. Its just, we could potentially add support for non-hd keys.. just no idea if there would be any demand or something we want at all.

@xdustinface
xdustinface merged commit e4f11a1 into dev Jul 9, 2026
38 of 39 checks passed
@xdustinface
xdustinface deleted the feat/anchor-to-checkpoint branch July 9, 2026 13:13
@ZocoLini

ZocoLini commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

yeah, I understand. now that you mention it, I think I saw something a long time ago, but that was with the old sync pipeline, I don't know if it still there

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants