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
Copy Markdown
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
Comment thread crates/core/src/checkpoint.rs
Comment thread crates/core/src/checkpoint.rs
Comment thread crates/core/src/checkpoint.rs
Comment thread crates/chain/src/local_chain.rs Outdated
@LagginTimes LagginTimes force-pushed the optional_data branch 2 times, most recently from cf98f58 to eddadd4 Compare September 10, 2025 17:26
Copy link
Copy Markdown
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.

.

Comment thread crates/chain/src/local_chain.rs Outdated
Comment thread crates/chain/src/local_chain.rs Outdated
@evanlinjin
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread crates/core/src/checkpoint.rs
Comment thread crates/core/src/checkpoint.rs
Comment thread crates/core/src/checkpoint.rs
Comment thread crates/core/tests/test_checkpoint.rs
Comment thread crates/core/src/checkpoint.rs
@evanlinjin
Copy link
Copy Markdown
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 LagginTimes force-pushed the optional_data branch 2 times, most recently from b301866 to 7ce4d91 Compare October 29, 2025 05:49
@ValuedMammal ValuedMammal mentioned this pull request Nov 24, 2025
4 tasks
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
evanlinjin added a commit that referenced this pull request Apr 22, 2026
c486ba7 docs: address review feedback on `prev_blockhash` validation (志宇)
031b30f docs(core): address review feedback on docs and tests (志宇)
ef35b19 fix(chain)!: make genesis immutable in `merge_chains` (志宇)
d8cb41f test(core): address review feedback on checkpoint tests (志宇)
9c0453c docs(core): Add module-level docs for `checkpoint_entry` (志宇)
3ef6ed8 feat(chain)!: Add `ApplyBlockError` for `prev_blockhash` validation (志宇)
bf6541b feat(chain)!: Relax the generic parameter for `LocalChain<D>` (志宇)
19b9006 test(core): add tests for CheckPoint::push and insert methods (志宇)
9971fda test(chain): Test `apply_update` with a single `CheckPoint<Header>` (valued mammal)
3df0609 test(chain): make `TestLocalChain` generic and add `prev_blockhash` test (志宇)
c314b25 fix(chain): `merge_chains` now takes account of `prev_blockhash`es (志宇)
f10ba0e fix(core): `Checkpoint::insert` now evicts on `prev_blockhash` mismatch (志宇)
046a771 fix(core): `push` now errors on `prev_blockhash` mismatch (志宇)
68d1ef4 feat(core): Initial work on `CheckPointEntry` (志宇)
d4bdff0 feat(core): Add `prev_blockhash` method to `ToBlockHash` trait (志宇)

Pull request description:

  Closes #2021
  Related to #2076
  Replaces #2024
  Replaces #2091

  ### Description

  This PR adds `prev_blockhash` awareness to `CheckPoint`, enabling proper chain validation when merging checkpoint chains that store block headers or similar data with previous block hash information.

  ### Notes to the reviewers

  This PR replaces some prior attempts:

  * #2024 - where we made the `CheckPoint::data` optional - however this resulted in internal complexity and an API with annoying edge cases. The tests from this PR were still useful.

  * #2091 - This second attempt had some good ideas, but was distracted from the goal of #2021. I mostly reused the `CheckPoint::insert` implementation of that PR.

  ### Changelog notice

  ```md
  Added:
  - `ToBlockHash::prev_blockhash()` - optional method to expose previous block hash
  - `CheckPointEntry` - new type for iterating with `prev_blockhash` awareness, yielding "placeholder" entries for heights inferred from `prev_blockhash`
  - `ApplyBlockError` - this is a new error type with two variants; `MissingGenesis` and `PrevBlockhashMismatch`. The second variant is a new error case introduced by `prev_blockhash` awareness.

  Changed:
  - `CheckPoint::push` - now errors when `prev_blockhash` conflicts with current tip (contiguous heights)
  - `CheckPoint::insert` - now evicts/displaces checkpoints on `prev_blockhash` conflict
  - `merge_chains` - now validates `prev_blockhash` consistency when merging
  - `LocalChain<D>` generic parameter - relaxed constraint to `D: Clone` instead of `D: Copy`.

  Fixed:
  - `merge_chains` no longer replaces the genesis block.
  ```

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  evanlinjin:
    self-ACK c486ba7

Tree-SHA512: 5622a8a418034443931c8b5938e708c2222c42c05efbdac71d47a4e0ce1b4f4b0b7bcd2e1e14e2f295cc056e12c03e5750b40b1a1313cdbdf59a009d81d0b8a1
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