Skip to content

feat(chain)!: make CheckPoint data field optional#2024

Closed
LagginTimes wants to merge 7 commits intobitcoindevkit:masterfrom
LagginTimes:optional_data
Closed

feat(chain)!: make CheckPoint data field optional#2024
LagginTimes wants to merge 7 commits intobitcoindevkit:masterfrom
LagginTimes:optional_data

Conversation

@LagginTimes
Copy link
Contributor

@LagginTimes LagginTimes commented Sep 10, 2025

Resolves #2021.

Description

This PR makes the CheckPoint::data field optional. Gaps inferred from prev_blockhash are represented as placeholder checkpoints where data is None. Subsequent insert()s will provide data for the placeholders when a matching block arrives.

See: #2017 (comment)

Notes to the reviewers

WIP

Changelog notice

Checklists

All Submissions:

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

@LagginTimes LagginTimes self-assigned this Sep 10, 2025
@LagginTimes LagginTimes marked this pull request as draft September 10, 2025 06:19
@LagginTimes LagginTimes reopened this Sep 10, 2025
@LagginTimes LagginTimes moved this from Done to In Progress in BDK Chain Sep 10, 2025
@LagginTimes LagginTimes added this to the Wallet 3.0.0 milestone Sep 10, 2025
@LagginTimes LagginTimes force-pushed the optional_data branch 2 times, most recently from cf98f58 to eddadd4 Compare September 10, 2025 17:26
Copy link
Member

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

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

.

@evanlinjin
Copy link
Member

Some recommended test scenarios:

  • Update chain that adds data to a placeholder checkpoint in the original chain.
  • Update chain which has a PoA with the original chain at a placeholder checkpoint (and vise-versa).

With the above tests:

  • Ensure the resultant chain has no "floating" checkpoints.

Copy link
Collaborator

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

cACK c24dd6a

It looking good code-wise, though we probably need further discussion on what's the best approach, as discussed by Evan and VM in discord.

@evanlinjin
Copy link
Member

@LagginTimes please review the modifications I made to push and insert here: evanlinjin@1939dd2

This implementation builds upon and improves the original PR's approach to optional checkpoint data in several key ways:

  1. Cleaner insert logic
    The original PR had complex nested conditions for handling insertions. This version:

    • Splits the chain into base and tail components upfront
    • Uses a systematic rebuild approach that leverages push for validation
    • Handles all edge cases (displacement, purging, placeholder filling) in a unified flow
  2. Proper displacement handling

    • Clear distinction: When a prev_blockhash conflicts, the checkpoint is displaced (converted to placeholder) rather than purged, preserving the chain structure
    • Orphan management: Checkpoints that become orphaned after displacement are correctly purged
    • The original PR struggled with maintaining consistency when conflicts occurred at different points in the chain
  3. Placeholder cleanup in push

    • Added logic to remove trailing placeholder checkpoints before pushing new data
    • Prevents accumulation of unnecessary placeholders at the chain tip
    • Maintains cleaner chain structure over time

LagginTimes and others added 6 commits January 8, 2026 04:47
* Base tip must always have data.
* Update should be able to introduce data to a placeholder checkpoint in
  the original chain.
* Remove unnecessary logic.
Add comprehensive tests for CheckPoint::push error cases:
- Push fails when height is not greater than current
- Push fails when prev_blockhash conflicts with self
- Push succeeds when prev_blockhash matches
- Push creates placeholder for non-contiguous heights

Include tests for CheckPoint::insert conflict handling:
- Insert with conflicting prev_blockhash
- Insert purges conflicting tail
- Insert between conflicting checkpoints

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@evanlinjin evanlinjin force-pushed the optional_data branch 2 times, most recently from 1939dd2 to 1f62476 Compare January 8, 2026 07:54
@evanlinjin
Copy link
Member

@LagginTimes I've rebased on master. Would you be okay if I took over this PR?

Rework CheckPoint::insert to properly handle conflicts:
- Split chain into base (below insert height) and tail (at/above)
- Rebuild chain by pushing tail items back, handling conflicts
- Displace checkpoints to placeholders when prev_blockhash conflicts
- Purge orphaned checkpoints that can no longer connect to chain
- Fix edge cases where placeholders weren't handled correctly

Improve CheckPoint::push:
- Remove trailing placeholder checkpoints before pushing
- Maintain existing behavior of creating placeholders for gaps

Documentation improvements:
- Clarify displacement vs purging rules without complex if-else chains
- Add concrete examples showing the behavior
- Add inline comments explaining the complex rebuild logic

Add comprehensive test coverage for displacement scenarios including
inserting at new/existing heights with various conflict types.
@@ -51,6 +54,7 @@
.expect("extension is strictly greater than base"),
Copy link
Member

Choose a reason for hiding this comment

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

This expect is now wrong. A block in the extension may have a prev_blockhash that doesn't line up.

Copy link
Member

@evanlinjin evanlinjin Jan 8, 2026

Choose a reason for hiding this comment

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

There seems to be a lot of additional logic in LocalChain to manage ChangeSets. This is non-ideal as the mutating logic of CheckPoint is now more complex - so we really want to keep the complexity in CheckPoint only.

Instead of having apply_changeset_to_checkpoint, I'm thinking of using methods of CheckPoint directly and comparing the original and updated checkpoint chains to derive the checkpoint.

update_tip: CheckPoint<D>,
) -> Result<(CheckPoint<D>, ChangeSet<D>), CannotConnectError>
where
D: ToBlockHash + fmt::Debug + Copy,
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if the + Copy here will be problematic.

@evanlinjin
Copy link
Member

Replaced by #2115

@evanlinjin evanlinjin closed this Feb 7, 2026
@github-project-automation github-project-automation bot moved this from In Progress to Done in BDK Chain Feb 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Ensure CheckPoint chain methods validate and link via previous blockhash

3 participants