Skip to content

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
Contributor

@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.
let new_tip = match base {
Some(base) => base
.extend(extension)
.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.

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

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Ensure CheckPoint chain methods validate and link via previous blockhash

3 participants