Skip to content

Conversation

LagginTimes
Copy link
Contributor

@LagginTimes LagginTimes commented Aug 29, 2024

Implements #1937.

Description

This PR is a step towards header checkpointing for bdk_electrum. The goal is to be able to store whole headers in CheckPoint so they do not have to be re-downloaded. Storing headers this way would be a prerequisite for caching of merkle proofs and for median time passed.

Notes to the reviewers

Changelog notice

  • CheckPoint takes in a generic.
  • LocalChain and ChangeSet take in generics.
  • spk_client types can take in generics.

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@LagginTimes LagginTimes marked this pull request as draft August 29, 2024 09:59
@LagginTimes LagginTimes changed the title refactor(core): CheckPoint takes a generic refactor(core): Implement generics for CheckPoint and LocalChain Aug 29, 2024
@LagginTimes LagginTimes force-pushed the generic_checkpoint branch 3 times, most recently from a899049 to 7c13781 Compare September 2, 2024 02:53
@LagginTimes LagginTimes force-pushed the generic_checkpoint branch 2 times, most recently from ea1cf8b to bf90ea5 Compare September 3, 2024 06:15
@LagginTimes LagginTimes force-pushed the generic_checkpoint branch 5 times, most recently from c278804 to 6300d7c Compare September 5, 2024 03:43
@LagginTimes LagginTimes changed the title refactor(core): Implement generics for CheckPoint and LocalChain refactor(core): Implement generics for CheckPoint, LocalChain, and spk_client types Sep 5, 2024
@LagginTimes LagginTimes force-pushed the generic_checkpoint branch 2 times, most recently from 58a5566 to dabb869 Compare July 4, 2025 20:12
@ValuedMammal
Copy link
Collaborator

ACK dabb869

@LagginTimes LagginTimes force-pushed the generic_checkpoint branch 2 times, most recently from 9aef131 to fed0c50 Compare August 8, 2025 18:04
@notmandatory notmandatory added the api A breaking API change label Aug 12, 2025
@LagginTimes LagginTimes force-pushed the generic_checkpoint branch 4 times, most recently from 8906806 to 0a39fa7 Compare August 13, 2025 07:47
@ValuedMammal ValuedMammal moved this to Needs Review in BDK Wallet Aug 13, 2025
Copy link
Collaborator

@ValuedMammal ValuedMammal left a comment

Choose a reason for hiding this comment

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

ACK 0a39fa7 pending a CI fix.

I left comments mostly for discussion. Also there was a comment #1582 (review) suggesting to use B instead of D. I don't have much preference though.

@notmandatory
Copy link
Member

I haven't done a deep review but I support merging this now that we're done with the bdk_wallet 2.1 release.

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.

utACK d4bd7e5

The code looks good to me, I only left a minor question and nit. I didn't test it on a complicated example yet.

@ValuedMammal
Copy link
Collaborator

ACK 4dce5ad

Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

utACK 4dce5ad

I also re-read through the whole history and this one has certainly been a journey so thanks to @LagginTimes and all the reviewers for sticking with it.

I look forward to the followup PRs to integrate chain client specific data in the checkpoints. I expect custom data will break existing persistence so being able to persist the data in a generic way and having a good persistence test suite such as #2012 will be handy.

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.

ACK 4dce5ad

@evanlinjin
Copy link
Member

evanlinjin commented Aug 22, 2025

Looking back through the codebase, I noticed that we've introduced a condition that wasn't there before, and I’m wondering about the best way to handle it.

Since a Header contains the previous block hash, two CheckPoint<Header> chains can be merged even if their heights differ. For example, if chain A ends at height 10 and chain B only has a single block at height 11, we can still connect them if B’s previous block points to A’s height 10.


One idea is to modify ToBlockHash to have knowledge of whether the type has the previous blockhash.

pub trait ToBlockHash {
    /* OTHER METHODS */

    /// Returns `None` if the type has no knowledge of the previous blockhash.
    fn prev_blockhash(&self) -> Option<BlockHash>;
}

Then we need to modify merge_chains to take it into account.

Should we modify CheckPoint to allow empty values (knowing the block hash & height, but not the data yet)?

  • Edit: On second thought, no. Allowing CheckPoint to hold empty data would increase the risk of inconsistent representations—for example, having a Header at height 10 while forgetting to include the “ghost checkpoint” at height 9.
  • A better idea?: I think we should modify the CheckPointIter instead to also return "ghost checkpoint"s. The Iterator::Item type needs to be changed to MaybeCheckPoint<D> or something.
  • Caveat: Methods that construct/extend/insert-into CheckPoint-chains should really error/fail if an inserted block's prevhash does not match the previous value (if it exists).

@ValuedMammal
Copy link
Collaborator

Since a Header contains the previous block hash, two CheckPoint<Header> chains can be merged even if their heights differ. For example, if chain A ends at height 10 and chain B only has a single block at height 11, we can still connect them if B’s previous block points to A’s height 10.

One idea is to modify ToBlockHash to have knowledge of whether the type has the previous blockhash.

pub trait ToBlockHash {
    /* OTHER METHODS */

    /// Returns `None` if the type has no knowledge of the previous blockhash.
    fn prev_blockhash(&self) -> Option<BlockHash>;
}

Then we need to modify merge_chains to take it into account.

Yes in principle I agree with this if the update is strictly extending the parent chain by the next consecutive block. If the type doesn't know the previous hash, like in the case of CheckPoint<BlockHash>, then I think we'd still require the existence of a point of agreement.

@evanlinjin
Copy link
Member

evanlinjin commented Aug 25, 2025

I've started experimenting with my suggestion in #1582 (comment).

This is the WIP commit: evanlinjin@8f374cc. I'm quite happy with the implementation of it (unless proven otherwise).

What is missing:

  • Figure out best practice for handling CheckPointEntry::Backrefs (ghost-checkpoints) that connect.
  • Methods that build on CheckPoint (i.e. push, from_blocks, extend, insert) need to check Backrefs match.
  • Tests.
  • More docs.

Feel free to continue this work as you see fit.


I'm wondering if we should merge this PR as is, and introduce "ghost-checkpoint"s in a separate PR?

@LagginTimes
Copy link
Contributor Author

LagginTimes commented Aug 25, 2025

I've started experimenting with my suggestion in #1582 (comment).

This is the WIP commit: evanlinjin@8f374cc. I'm quite happy with the implementation of it (unless proven otherwise).

What is missing:

* Figure out best practice for handling `CheckPointEntry::Backref`s (ghost-checkpoints) that connect.

* Methods that build on `CheckPoint` (i.e. `push`, `from_blocks`, `extend`, `insert`) need to check `Backref`s match.

* Tests.

* More docs.

Feel free to continue this work as you see fit.

I'm wondering if we should merge this PR as is, and introduce "ghost-checkpoint"s in a separate PR?

I think it makes sense to merge #1582 as it is now. The additional work around ghost-checkpoints, backref checks, and more docs/tests can be done in a follow-up PR to keep the scope focused and avoid resetting review. I would be happy to continue the work in a follow-up PR.

@evanlinjin
Copy link
Member

evanlinjin commented Aug 28, 2025

@LagginTimes I'll be happy with that. Do we have a plan for back-porting the non-merged chain-source fixes (#2005, #2011, #2000)? Note that the CheckPointEntry PR may break the API again.

@ValuedMammal
Copy link
Collaborator

if chain A ends at height 10 and chain B only has a single block at height 11, we can still connect them if B’s previous block points to A’s height 10.

For this I would look at things we already have such as insert_block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api A breaking API change module-blockchain new feature New feature or request
Projects
Status: Needs Review
Status: Needs Review
Development

Successfully merging this pull request may close these issues.

Implement generics for CheckPoint, LocalChain, and spk_client types
8 participants