Skip to content

Make bdk_bitcoind_rpc emitters support any checkpoint data type#2105

Draft
evanlinjin wants to merge 4 commits intobitcoindevkit:masterfrom
evanlinjin:feature/bitcoind_rpc_emit_header
Draft

Make bdk_bitcoind_rpc emitters support any checkpoint data type#2105
evanlinjin wants to merge 4 commits intobitcoindevkit:masterfrom
evanlinjin:feature/bitcoind_rpc_emit_header

Conversation

@evanlinjin
Copy link
Member

@evanlinjin evanlinjin commented Jan 26, 2026

Description

Make bdk_bitcoind_rpc emitters support emitting CheckPoint updates with both BlockHash and Header data. To make this easier to implement, a bdk_core::fromBlockHash trait is introduced.

Rationale

I think BDK should move towards making Header the most-used checkpoint data type (so chain-sources should be able to output that!). This allows us to accurately calculate time (MTP), provides the merkle root for verification (maybe anchors in the future can include the merkle proof), and provides the prev_blockhash value.

However, we cannot force this change as users may want persisted data to be compatible with newer versions of BDK (and retain their BlockId-based persistence data).

Changelog notice

Added
- `FromBlockHash` trait which `BlockHash` and `Header` implement. This makes it easier for chain-sources to output `CheckPoint` updates with both types of data (and more in the future).
- `bdk_bitcoind_rpc` emitters now have new methods for emitting events with custom "checkpoint data" types.

Changed
- `bdk_bitcoind_rpc` emitters now have generic types for "checkpoint data" with backwards compatible defaults.

Checklists

All Submissions:

New Features:

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

@luisschwab
Copy link
Member

This couples nicely with Floresta, since we already keep a chain of headers. Cool idea.

@ValuedMammal
Copy link
Collaborator

Proposal - Make FilterIter generic over the checkpoint data type D

Add fetch_data member field to FilterIter

pub struct FilterIter<'a, D> {
    /// User provided closure which defines the method of fetching checkpoint data `D`.
    fetch_data: Box<FetchData<D>>,
    
    // ...
}

/// Type representing the "fetch data" closure.
type FetchData<D> = dyn Fn(&Client, &BlockHash) -> Result<D, Error> + 'static;

Change constructor to take a user-provided fetch_data closure.

pub fn new(
    client: &'a bitcoincore_rpc::Client,
    cp: CheckPoint<D>,
    spks: impl IntoIterator<Item = ScriptBuf>,
    fetch_data: impl Fn(&Client, &BlockHash) -> Result<D, Error> + 'static,
) -> Self {
    Self {
        client,
        spks: spks.into_iter().collect(),
        cp,
        header: None,
        fetch_data: Box::new(fetch_data),
    }
}

Update next function to fetch the checkpoint data D given a reference to the RPC client and the next block hash.

let next_data = (self.fetch_data)(self.client, &next_hash)?;
cp = cp.insert(next_height, next_data);

For example

The current, non-generic implementation can be achieved by passing a function that simply returns the next hash unchanged. For more complex data types, e.g. Header, the RPC client can be used to fetch it.

fn fetch_blockhash() -> impl Fn(&Client, &BlockHash) -> Result<BlockHash, Error> {
    |_client, &hash| Ok(hash)
}

fn fetch_header() -> impl Fn(&Client, &BlockHash) -> Result<Header, Error> {
    |client, hash| client.get_block_header(hash).map_err(Error::Rpc)
}

@evanlinjin evanlinjin force-pushed the feature/bitcoind_rpc_emit_header branch 2 times, most recently from 912d0f7 to b76cb25 Compare January 29, 2026 10:07
@oleonardolima oleonardolima moved this to In Progress in BDK Chain Feb 2, 2026
@oleonardolima oleonardolima added module-blockchain api A breaking API change labels Feb 2, 2026
@oleonardolima oleonardolima self-requested a review February 2, 2026 15:37
Comment on lines +58 to +62
/// Trait that converts [`Header`] to subset data.
pub trait FromBlockHeader {
/// Returns the subset data from a block `header`.
fn from_blockheader(header: Header) -> Self;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

@evanlinjin Can you clarify why the new trait it's really need ? Can't we achieve the same by just implementing ToBlockHash for Header ? (I'll do another round to better understand it)

Copy link
Member Author

Choose a reason for hiding this comment

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

We want Header -> checkpoint data. So a chain-source crate can provide the Header, and the checkpoint data can be made from that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of a new trait, could the same thing be expressed as a D: From<Header> bound?

@evanlinjin evanlinjin added this to the Chain 0.24.0 milestone Feb 21, 2026
…dress

Refactor block mining in `TestEnv` to use `getblocktemplate` RPC properly:

- Add `MineParams` struct to configure mining (empty blocks, custom
  timestamp, custom coinbase address)
- Add `mine_block()` method that builds blocks from the template with
  proper BIP34 coinbase scriptSig, witness commitment, and merkle root
- Add `min_time_for_next_block()` and `get_block_template()` helpers
- Refactor `mine_empty_block()` to use the new `mine_block()` API
- Include mempool transactions when `empty: false`
This allows us to use any subset of a `Header` to construct checkpoint
data. Currently, only `BlockHash` and `Header` implement this.

Chain sources can bound the checkpoint data generic to this trait, so all
checkpoint data types that implement `FromBlockHeader` is supported by
the chain source.
@evanlinjin evanlinjin force-pushed the feature/bitcoind_rpc_emit_header branch from b76cb25 to 4755d8c Compare March 3, 2026 06:55
@evanlinjin
Copy link
Member Author

Hey @ValuedMammal, re: your proposal to use Fn(&Client, &BlockHash) -> Result<D, Error> — the key insight is that in FilterIter::try_next, get_block_header_info is already called for navigation, and its result contains all the fields needed to reconstruct a bitcoin::block::Header. So we can hand that directly to the closure — no extra RPC call, no client needed, no fallibility. For Emitter, the block is always fetched for tx processing, so &Block (which includes block.header) covers everything too. Giving the closure access to (&Client, &BlockHash) would almost certainly cause a redundant get_block_header round-trip and needlessly complicate user implementations.

@ValuedMammal
Copy link
Collaborator

ValuedMammal commented Mar 3, 2026

and its result contains all the fields needed to reconstruct a bitcoin::block::Header

Ah that's a good point. I do think it's unfortunate that D is limited to only what can be derived from a Header, since there could be custom checkpoint data types that include additional info, like median_time for example. For now making the implementation Header-centric is an acceptable tradeoff.

… `Emitter`

Store `Fn(Header) -> D` in `FilterIter` and `Fn(&Block) -> D` in
`Emitter`, enabling `Iterator` for any `D` without a `FromBlockHeader`
bound. Add `new_with` constructors for custom `D` types; `new` delegates
to `new_with` for the common `FromBlockHeader` case.

Remove `FilterIter::next_with` and `Emitter::next_block_with` — callers
should use `new_with` at construction time instead.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@evanlinjin evanlinjin force-pushed the feature/bitcoind_rpc_emit_header branch from 4755d8c to 25e8feb Compare March 4, 2026 07:39
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

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants