From: 志宇 Date: Fri, 9 Jan 2026 09:26:07 +0000 (+0800) Subject: feat(core): Initial work on `CheckPointEntry` X-Git-Url: http://internal-gitweb-vhost/?a=commitdiff_plain;h=68d1ef4466cf64104b0338a3feed902080a35e05;p=bdk feat(core): Initial work on `CheckPointEntry` --- diff --git a/crates/core/src/checkpoint.rs b/crates/core/src/checkpoint.rs index edb420dc..bff83362 100644 --- a/crates/core/src/checkpoint.rs +++ b/crates/core/src/checkpoint.rs @@ -5,7 +5,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use bitcoin::{block::Header, BlockHash}; -use crate::BlockId; +use crate::{BlockId, CheckPointEntry, CheckPointEntryIter}; /// A checkpoint is a node of a reference-counted linked list of [`BlockId`]s. /// @@ -210,6 +210,26 @@ impl CheckPoint { } } +impl CheckPoint +where + D: ToBlockHash, +{ + /// Iterate entries from this checkpoint in descending height. + pub fn entry_iter(&self) -> CheckPointEntryIter { + self.to_entry().into_iter() + } + + /// Transforms this checkpoint into a [`CheckPointEntry`]. + pub fn into_entry(self) -> CheckPointEntry { + CheckPointEntry::Occupied(self) + } + + /// Creates a [`CheckPointEntry`]. + pub fn to_entry(&self) -> CheckPointEntry { + CheckPointEntry::Occupied(self.clone()) + } +} + // Methods where `D: ToBlockHash` impl CheckPoint where @@ -349,15 +369,15 @@ where /// Iterates over checkpoints backwards. pub struct CheckPointIter { - current: Option>>, + next: Option>>, } impl Iterator for CheckPointIter { type Item = CheckPoint; fn next(&mut self) -> Option { - let current = self.current.clone()?; - self.current.clone_from(¤t.prev); + let current = self.next.clone()?; + self.next.clone_from(¤t.prev); Some(CheckPoint(current)) } } @@ -367,9 +387,7 @@ impl IntoIterator for CheckPoint { type IntoIter = CheckPointIter; fn into_iter(self) -> Self::IntoIter { - CheckPointIter { - current: Some(self.0), - } + CheckPointIter { next: Some(self.0) } } } diff --git a/crates/core/src/checkpoint_entry.rs b/crates/core/src/checkpoint_entry.rs new file mode 100644 index 00000000..8e866b97 --- /dev/null +++ b/crates/core/src/checkpoint_entry.rs @@ -0,0 +1,207 @@ +use core::ops::RangeBounds; + +use bitcoin::BlockHash; + +use crate::{BlockId, CheckPoint, ToBlockHash}; + +/// An entry yielded by [`CheckPointIter`]. +#[derive(Debug, Clone)] +pub enum CheckPointEntry { + /// A placeholder entry: there is no checkpoint stored at this height, + /// but the checkpoint one height above links back here via its `prev_blockhash`. + Placeholder { + /// The block ID at *this* height. + block_id: BlockId, + /// The checkpoint one height *above* that links back to `block_id`. + checkpoint_above: CheckPoint, + }, + /// A real checkpoint recorded at this height. + Occupied(CheckPoint), +} + +impl CheckPointEntry { + /// The checkpoint at this height (if any). + pub fn checkpoint(&self) -> Option> { + match self { + CheckPointEntry::Placeholder { .. } => None, + CheckPointEntry::Occupied(checkpoint) => Some(checkpoint.clone()), + } + } + + /// Returns `true` if this entry is a placeholder (inferred from `prev_blockhash`). + pub fn is_placeholder(&self) -> bool { + matches!(self, CheckPointEntry::Placeholder { .. }) + } + + /// The checkpoint that is the *source* of this entry. + /// + /// For an `Occupied` entry, this is the checkpoint itself. + /// For a `Placeholder` entry, this is the checkpoint above that references this height. + pub fn source_checkpoint(&self) -> CheckPoint { + match self { + CheckPointEntry::Placeholder { + checkpoint_above, .. + } => checkpoint_above.clone(), + CheckPointEntry::Occupied(checkpoint) => checkpoint.clone(), + } + } + + /// Returns the checkpoint at or below this entry's height. + pub fn floor_checkpoint(&self) -> Option> { + match self { + CheckPointEntry::Placeholder { + checkpoint_above: linking_checkpoint, + .. + } => linking_checkpoint.prev(), + CheckPointEntry::Occupied(checkpoint) => Some(checkpoint.clone()), + } + } + + /// Returns a reference to the data recorded at this exact height (if any). + pub fn data_ref(&self) -> Option<&D> { + match self { + CheckPointEntry::Placeholder { .. } => None, + CheckPointEntry::Occupied(checkpoint) => Some(checkpoint.data_ref()), + } + } + + /// Returns the data recorded at this exact height (if any). + pub fn data(&self) -> Option + where + D: Clone, + { + self.data_ref().cloned() + } +} + +impl CheckPointEntry { + /// The block ID of this entry. + pub fn block_id(&self) -> BlockId { + match self { + CheckPointEntry::Placeholder { block_id, .. } => *block_id, + CheckPointEntry::Occupied(checkpoint) => checkpoint.block_id(), + } + } + + /// The blockhash of this entry. + pub fn hash(&self) -> BlockHash { + self.block_id().hash + } + + /// The block height of this entry. + pub fn height(&self) -> u32 { + self.block_id().height + } + + /// Get the previous entry in the chain. + pub fn prev(&self) -> Option { + let checkpoint = match self { + Self::Placeholder { + checkpoint_above, .. + } => { + return checkpoint_above.prev().map(Self::Occupied); + } + Self::Occupied(checkpoint) => checkpoint, + }; + + let prev_height = checkpoint.height().checked_sub(1)?; + + let prev_blockhash = match checkpoint.data_ref().prev_blockhash() { + Some(blockhash) => blockhash, + None => return checkpoint.prev().map(Self::Occupied), + }; + + if let Some(prev_checkpoint) = checkpoint.prev() { + if prev_checkpoint.height() == prev_height { + return Some(Self::Occupied(prev_checkpoint)); + } + } + + Some(Self::Placeholder { + block_id: BlockId { + height: prev_height, + hash: prev_blockhash, + }, + checkpoint_above: checkpoint.clone(), + }) + } + + /// Iterate over checkpoint entries backwards. + pub fn iter(&self) -> CheckPointEntryIter + where + D: Clone, + { + self.clone().into_iter() + } + + /// Get checkpoint entry at `height`. + /// + /// Returns `None` if checkpoint at `height` does not exist. + pub fn get(&self, height: u32) -> Option + where + D: Clone, + { + self.range(height..=height).next() + } + + /// Iterate checkpoints over a height range. + pub fn range(&self, range: R) -> impl Iterator> + where + D: Clone, + R: RangeBounds, + { + let start_bound = range.start_bound().cloned(); + let end_bound = range.end_bound().cloned(); + self.iter() + .skip_while(move |cp_entry| match end_bound { + core::ops::Bound::Included(inc_bound) => cp_entry.height() > inc_bound, + core::ops::Bound::Excluded(exc_bound) => cp_entry.height() >= exc_bound, + core::ops::Bound::Unbounded => false, + }) + .take_while(move |cp_entry| match start_bound { + core::ops::Bound::Included(inc_bound) => cp_entry.height() >= inc_bound, + core::ops::Bound::Excluded(exc_bound) => cp_entry.height() > exc_bound, + core::ops::Bound::Unbounded => true, + }) + } + + /// Returns the entry at `height` if one exists, otherwise the nearest checkpoint at a lower + /// height. + pub fn floor_at(&self, height: u32) -> Option + where + D: Clone, + { + self.range(..=height).next() + } + + /// Returns the entry located a number of heights below this one. + pub fn floor_below(&self, offset: u32) -> Option + where D: Clone + { + self.floor_at(self.height().checked_sub(offset)?) + } +} + +/// Iterates over checkpoint entries backwards. +pub struct CheckPointEntryIter { + next: Option>, +} + +impl Iterator for CheckPointEntryIter { + type Item = CheckPointEntry; + + fn next(&mut self) -> Option { + let item = self.next.take()?; + self.next = item.prev(); + Some(item) + } +} + +impl IntoIterator for CheckPointEntry { + type Item = CheckPointEntry; + type IntoIter = CheckPointEntryIter; + + fn into_iter(self) -> Self::IntoIter { + CheckPointEntryIter { next: Some(self) } + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 95bebe90..cf02a99b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -65,6 +65,9 @@ pub use block_id::*; mod checkpoint; pub use checkpoint::*; +mod checkpoint_entry; +pub use checkpoint_entry::*; + mod tx_update; pub use tx_update::*;