Skip to content

Commit

Permalink
refactor(core): CheckPoint takes a generic WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
LagginTimes committed Sep 3, 2024
1 parent 775e4ae commit c278804
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 79 deletions.
5 changes: 4 additions & 1 deletion crates/bitcoind_rpc/tests/test_emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,10 @@ fn process_block(
block: Block,
block_height: u32,
) -> anyhow::Result<()> {
recv_chain.apply_update(CheckPoint::from_header(&block.header, block_height))?;
recv_chain.apply_update(CheckPoint::blockhash_checkpoint_from_header(
&block.header,
block_height,
))?;
let _ = recv_graph.apply_block(block, block_height);
Ok(())
}
Expand Down
256 changes: 178 additions & 78 deletions crates/core/src/checkpoint.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::ops::RangeBounds;

use alloc::sync::Arc;
use bitcoin::BlockHash;
use bitcoin::{block::Header, BlockHash};

use crate::BlockId;

Expand All @@ -10,51 +10,64 @@ use crate::BlockId;
/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse
/// block chains.
#[derive(Debug, Clone)]
pub struct CheckPoint(Arc<CPInner>);
pub struct CheckPoint<B = BlockHash>(Arc<CPInner<B>>);

/// The internal contents of [`CheckPoint`].
#[derive(Debug, Clone)]
struct CPInner {
/// Block id (hash and height).
block: BlockId,
struct CPInner<B> {
/// Block data.
block_id: BlockId,
/// Data.
data: B,
/// Previous checkpoint (if any).
prev: Option<Arc<CPInner>>,
prev: Option<Arc<CPInner<B>>>,
}

impl PartialEq for CheckPoint {
/// TODO: ToBlockHash doc
pub trait ToBlockHash {
/// TODO: to_blockhash doc
fn to_blockhash(&self) -> BlockHash;
}

impl ToBlockHash for BlockHash {
fn to_blockhash(&self) -> BlockHash {
*self
}
}

impl ToBlockHash for Header {
fn to_blockhash(&self) -> BlockHash {
self.block_hash()
}
}

impl<B> PartialEq for CheckPoint<B>
where
B: Copy + core::cmp::PartialEq,
{
fn eq(&self, other: &Self) -> bool {
let self_cps = self.iter().map(|cp| cp.block_id());
let other_cps = other.iter().map(|cp| cp.block_id());
let self_cps = self.iter().map(|cp| cp.0.block_id);
let other_cps = other.iter().map(|cp| cp.0.block_id);
self_cps.eq(other_cps)
}
}

impl CheckPoint {
/// Construct a new base block at the front of a linked list.
impl CheckPoint<BlockHash> {
/// Construct a new base [`CheckPoint`] at the front of a linked list.
pub fn new(block: BlockId) -> Self {
Self(Arc::new(CPInner { block, prev: None }))
CheckPoint::from_data(block.height, block.hash)
}

/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
///
/// # Errors
///
/// This method will error if any of the follow occurs:
///
/// - The `blocks` iterator is empty, in which case, the error will be `None`.
/// - The `blocks` iterator is not in ascending height order.
/// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
/// Construct a checkpoint from the given `header` and block `height`.
///
/// The error type is the last successful checkpoint constructed (if any).
pub fn from_block_ids(
block_ids: impl IntoIterator<Item = BlockId>,
) -> Result<Self, Option<Self>> {
let mut blocks = block_ids.into_iter();
let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
for id in blocks {
acc = acc.push(id).map_err(Some)?;
}
Ok(acc)
/// If `header` is of the genesis block, the checkpoint won't have a `prev` node. Otherwise,
/// we return a checkpoint linked with the previous block.
#[deprecated(
since = "0.1.1",
note = "Please use [`CheckPoint::blockhash_checkpoint_from_header`] instead. To create a CheckPoint<Header>, please use [`CheckPoint::from_data`]."
)]
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
CheckPoint::blockhash_checkpoint_from_header(header, height)
}

/// Construct a checkpoint from the given `header` and block `height`.
Expand All @@ -63,7 +76,7 @@ impl CheckPoint {
/// we return a checkpoint linked with the previous block.
///
/// [`prev`]: CheckPoint::prev
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
pub fn blockhash_checkpoint_from_header(header: &bitcoin::block::Header, height: u32) -> Self {
let hash = header.block_hash();
let this_block_id = BlockId { height, hash };

Expand All @@ -82,55 +95,93 @@ impl CheckPoint {
.expect("must construct checkpoint")
}

/// Puts another checkpoint onto the linked list representing the blockchain.
/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
///
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
/// are pushing on to.
pub fn push(self, block: BlockId) -> Result<Self, Self> {
if self.height() < block.height {
Ok(Self(Arc::new(CPInner {
block,
prev: Some(self.0),
})))
} else {
Err(self)
/// # Errors
///
/// This method will error if any of the follow occurs:
///
/// - The `blocks` iterator is empty, in which case, the error will be `None`.
/// - The `blocks` iterator is not in ascending height order.
/// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
///
/// The error type is the last successful checkpoint constructed (if any).
pub fn from_block_ids(
block_ids: impl IntoIterator<Item = BlockId>,
) -> Result<Self, Option<Self>> {
let mut blocks = block_ids.into_iter();
let block = blocks.next().ok_or(None)?;
let mut acc = CheckPoint::new(block);
for id in blocks {
acc = acc.push(id).map_err(Some)?;
}
Ok(acc)
}

/// Extends the checkpoint linked list by a iterator of block ids.
///
/// Returns an `Err(self)` if there is block which does not have a greater height than the
/// previous one.
pub fn extend(self, blocks: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
let mut curr = self.clone();
for block in blocks {
curr = curr.push(block).map_err(|_| self.clone())?;
}
Ok(curr)
pub fn extend(self, blockdata: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
self.extend_data(
blockdata
.into_iter()
.map(|block| (block.height, block.hash)),
)
}

/// Inserts `block_id` at its height within the chain.
///
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
/// it. If the height already existed and has a conflicting block hash then it will be purged
/// along with all block followin it. The returned chain will have a tip of the `block_id`
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
#[must_use]
pub fn insert(self, block_id: BlockId) -> Self {
self.insert_data(block_id.height, block_id.hash)
}

/// Puts another checkpoint onto the linked list representing the blockchain.
///
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
/// are pushing on to.
pub fn push(self, block: BlockId) -> Result<Self, Self> {
self.push_data(block.height, block.hash)
}
}

impl<B> CheckPoint<B>
where
B: Copy,
{
/// Get the `data` of the checkpoint.
pub fn data(&self) -> &B {
&self.0.data
}

/// Get the [`BlockId`] of the checkpoint.
pub fn block_id(&self) -> BlockId {
self.0.block
self.0.block_id
}

/// Get the height of the checkpoint.
/// Get the `height` of the checkpoint.
pub fn height(&self) -> u32 {
self.0.block.height
self.0.block_id.height
}

/// Get the block hash of the checkpoint.
pub fn hash(&self) -> BlockHash {
self.0.block.hash
self.0.block_id.hash
}

/// Get the previous checkpoint in the chain
pub fn prev(&self) -> Option<CheckPoint> {
/// Get the previous checkpoint in the chain.
pub fn prev(&self) -> Option<CheckPoint<B>> {
self.0.prev.clone().map(CheckPoint)
}

/// Iterate from this checkpoint in descending height.
pub fn iter(&self) -> CheckPointIter {
pub fn iter(&self) -> CheckPointIter<B> {
self.clone().into_iter()
}

Expand All @@ -145,7 +196,7 @@ impl CheckPoint {
///
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
/// height).
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint<B>>
where
R: RangeBounds<u32>,
{
Expand All @@ -163,56 +214,105 @@ impl CheckPoint {
core::ops::Bound::Unbounded => true,
})
}
}

/// Inserts `block_id` at its height within the chain.
impl<B> CheckPoint<B>
where
B: Copy + core::fmt::Debug + ToBlockHash,
{
/// Construct a new base [`CheckPoint`] from given `height` and `data` at the front of a linked
/// list.
pub fn from_data(height: u32, data: B) -> Self {
Self(Arc::new(CPInner {
block_id: BlockId {
height,
hash: data.to_blockhash(),
},
data,
prev: None,
}))
}

/// Extends the checkpoint linked list by a iterator containing `height` and `data`.
///
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
/// it. If the height already existed and has a conflicting block hash then it will be purged
/// along with all block followin it. The returned chain will have a tip of the `block_id`
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
/// Returns an `Err(self)` if there is block which does not have a greater height than the
/// previous one.
pub fn extend_data(self, blockdata: impl IntoIterator<Item = (u32, B)>) -> Result<Self, Self> {
let mut curr = self.clone();
for (height, data) in blockdata {
curr = curr.push_data(height, data).map_err(|_| self.clone())?;
}
Ok(curr)
}

/// Inserts `data` at its `height` within the chain.
///
/// The effect of `insert` depends on whether a `height` already exists. If it doesn't, the
/// `data` we inserted and all pre-existing `data` at higher heights will be re-inserted after
/// it. If the `height` already existed and has a conflicting block hash then it will be purged
/// along with all block following it. The returned chain will have a tip with the `data`
/// passed in. Of course, if the `data` was already present then this just returns `self`.
#[must_use]
pub fn insert(self, block_id: BlockId) -> Self {
assert_ne!(block_id.height, 0, "cannot insert the genesis block");
pub fn insert_data(self, height: u32, data: B) -> Self {
assert_ne!(height, 0, "cannot insert the genesis block");

let mut cp = self.clone();
let mut tail = vec![];
let base = loop {
if cp.height() == block_id.height {
if cp.hash() == block_id.hash {
if cp.height() == height {
if cp.hash() == data.to_blockhash() {
return self;
}
// if we have a conflict we just return the inserted block because the tail is by
// if we have a conflict we just return the inserted data because the tail is by
// implication invalid.
tail = vec![];
break cp.prev().expect("can't be called on genesis block");
}

if cp.height() < block_id.height {
if cp.height() < height {
break cp;
}

tail.push(cp.block_id());
tail.push((cp.height(), *cp.data()));
cp = cp.prev().expect("will break before genesis block");
};

base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
base.extend_data(core::iter::once((height, data)).chain(tail.into_iter().rev()))
.expect("tail is in order")
}

/// Puts another checkpoint onto the linked list representing the blockchain.
///
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
/// are pushing on to.
pub fn push_data(self, height: u32, data: B) -> Result<Self, Self> {
if self.height() < height {
Ok(Self(Arc::new(CPInner {
block_id: BlockId {
height,
hash: data.to_blockhash(),
},
data,
prev: Some(self.0),
})))
} else {
Err(self)
}
}

/// This method tests for `self` and `other` to have equal internal pointers.
pub fn eq_ptr(&self, other: &Self) -> bool {
Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
}
}

/// Iterates over checkpoints backwards.
pub struct CheckPointIter {
current: Option<Arc<CPInner>>,
pub struct CheckPointIter<B = BlockHash> {
current: Option<Arc<CPInner<B>>>,
}

impl Iterator for CheckPointIter {
type Item = CheckPoint;
impl<B> Iterator for CheckPointIter<B> {
type Item = CheckPoint<B>;

fn next(&mut self) -> Option<Self::Item> {
let current = self.current.clone()?;
Expand All @@ -221,9 +321,9 @@ impl Iterator for CheckPointIter {
}
}

impl IntoIterator for CheckPoint {
type Item = CheckPoint;
type IntoIter = CheckPointIter;
impl<B> IntoIterator for CheckPoint<B> {
type Item = CheckPoint<B>;
type IntoIter = CheckPointIter<B>;

fn into_iter(self) -> Self::IntoIter {
CheckPointIter {
Expand Down

0 comments on commit c278804

Please sign in to comment.