From c2788046cbfee74bde360a090a877fe36ef22cf4 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Sat, 31 Aug 2024 19:06:44 +0800 Subject: [PATCH] refactor(core): `CheckPoint` takes a generic WIP --- crates/bitcoind_rpc/tests/test_emitter.rs | 5 +- crates/core/src/checkpoint.rs | 256 +++++++++++++++------- 2 files changed, 182 insertions(+), 79 deletions(-) diff --git a/crates/bitcoind_rpc/tests/test_emitter.rs b/crates/bitcoind_rpc/tests/test_emitter.rs index 3a5c67055..7e859453d 100644 --- a/crates/bitcoind_rpc/tests/test_emitter.rs +++ b/crates/bitcoind_rpc/tests/test_emitter.rs @@ -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(()) } diff --git a/crates/core/src/checkpoint.rs b/crates/core/src/checkpoint.rs index 0abadda1d..c2a0d56b5 100644 --- a/crates/core/src/checkpoint.rs +++ b/crates/core/src/checkpoint.rs @@ -1,7 +1,7 @@ use core::ops::RangeBounds; use alloc::sync::Arc; -use bitcoin::BlockHash; +use bitcoin::{block::Header, BlockHash}; use crate::BlockId; @@ -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); +pub struct CheckPoint(Arc>); /// The internal contents of [`CheckPoint`]. #[derive(Debug, Clone)] -struct CPInner { - /// Block id (hash and height). - block: BlockId, +struct CPInner { + /// Block data. + block_id: BlockId, + /// Data. + data: B, /// Previous checkpoint (if any). - prev: Option>, + prev: Option>>, } -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 PartialEq for CheckPoint +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 { + /// 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, - ) -> Result> { - 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
, 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`. @@ -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 }; @@ -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 { - 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, + ) -> Result> { + 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) -> Result { - 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) -> Result { + 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.push_data(block.height, block.hash) + } +} + +impl CheckPoint +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 { + /// Get the previous checkpoint in the chain. + pub fn prev(&self) -> Option> { self.0.prev.clone().map(CheckPoint) } /// Iterate from this checkpoint in descending height. - pub fn iter(&self) -> CheckPointIter { + pub fn iter(&self) -> CheckPointIter { self.clone().into_iter() } @@ -145,7 +196,7 @@ impl CheckPoint { /// /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip /// height). - pub fn range(&self, range: R) -> impl Iterator + pub fn range(&self, range: R) -> impl Iterator> where R: RangeBounds, { @@ -163,43 +214,92 @@ impl CheckPoint { core::ops::Bound::Unbounded => true, }) } +} - /// Inserts `block_id` at its height within the chain. +impl CheckPoint +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) -> Result { + 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 { + 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) @@ -207,12 +307,12 @@ impl CheckPoint { } /// Iterates over checkpoints backwards. -pub struct CheckPointIter { - current: Option>, +pub struct CheckPointIter { + current: Option>>, } -impl Iterator for CheckPointIter { - type Item = CheckPoint; +impl Iterator for CheckPointIter { + type Item = CheckPoint; fn next(&mut self) -> Option { let current = self.current.clone()?; @@ -221,9 +321,9 @@ impl Iterator for CheckPointIter { } } -impl IntoIterator for CheckPoint { - type Item = CheckPoint; - type IntoIter = CheckPointIter; +impl IntoIterator for CheckPoint { + type Item = CheckPoint; + type IntoIter = CheckPointIter; fn into_iter(self) -> Self::IntoIter { CheckPointIter {