From a899049e0d438e702522913c87c5dc750ba1ebf4 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 --- crates/core/src/checkpoint.rs | 226 +++++++++++++++++++--------------- 1 file changed, 129 insertions(+), 97 deletions(-) diff --git a/crates/core/src/checkpoint.rs b/crates/core/src/checkpoint.rs index 0abadda1d..3a930173a 100644 --- a/crates/core/src/checkpoint.rs +++ b/crates/core/src/checkpoint.rs @@ -10,29 +10,69 @@ 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 height. + height: u32, + /// 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 PartialEq for CheckPoint +where + B: Copy + Clone + 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.inner()); + let other_cps = other.iter().map(|cp| *cp.inner()); self_cps.eq(other_cps) } } -impl CheckPoint { - /// Construct a new base block at the front of a linked list. +impl CheckPoint { + /// Construct a new [`CheckPoint`] at the front of a linked list. pub fn new(block: BlockId) -> Self { - Self(Arc::new(CPInner { block, prev: None })) + Self(Arc::new(CPInner { + height: block.height, + data: block.hash, + prev: None, + })) + } + + /// Construct a checkpoint from the given `header` and block `height`. + /// + /// 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. + /// + /// [`prev`]: CheckPoint::prev + pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self { + let hash = header.block_hash(); + let this_block_id = BlockId { height, hash }; + + let prev_height = match height.checked_sub(1) { + Some(h) => h, + None => return Self::new(this_block_id), + }; + + let prev_block_id = BlockId { + height: prev_height, + hash: header.prev_blockhash, + }; + + CheckPoint::new(prev_block_id) + .push(this_block_id) + .expect("must construct checkpoint") } /// Construct a checkpoint from a list of [`BlockId`]s in ascending height order. @@ -50,36 +90,75 @@ impl CheckPoint { block_ids: impl IntoIterator, ) -> Result> { let mut blocks = block_ids.into_iter(); - let mut acc = CheckPoint::new(blocks.next().ok_or(None)?); + 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) } - /// Construct a checkpoint from the given `header` and block `height`. + /// Extends the checkpoint linked list by a iterator of block ids. /// - /// 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. + /// Returns an `Err(self)` if there is block which does not have a greater height than the + /// previous one. + pub fn extend(self, blockdata: impl IntoIterator) -> Result { + let mut curr = self.clone(); + for block in blockdata { + curr = curr.push(block).map_err(|_| self.clone())?; + } + Ok(curr) + } + + /// Get the block hash of the checkpoint. + pub fn hash(&self) -> BlockHash { + self.0.data + } + + /// Get the [`BlockId`] of the checkpoint. + pub fn block_id(&self) -> BlockId { + BlockId { + height: self.height(), + hash: self.hash(), + } + } + + /// Inserts `block_id` at its height within the chain. /// - /// [`prev`]: CheckPoint::prev - pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self { - let hash = header.block_hash(); - let this_block_id = BlockId { height, hash }; + /// 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 { + assert_ne!(block_id.height, 0, "cannot insert the genesis block"); - let prev_height = match height.checked_sub(1) { - Some(h) => h, - None => return Self::new(this_block_id), - }; + let mut cp = self.clone(); + let mut tail = vec![]; + let base = loop { + if cp.height() == block_id.height { + if cp.hash() == block_id.hash { + return self; + } + // if we have a conflict we just return the inserted block because the tail is by + // implication invalid. + tail = vec![]; + break cp.prev().expect("can't be called on genesis block"); + } - let prev_block_id = BlockId { - height: prev_height, - hash: header.prev_blockhash, + if cp.height() < block_id.height { + break cp; + } + + tail.push(cp.block_id()); + cp = cp.prev().expect("will break before genesis block"); }; - CheckPoint::new(prev_block_id) - .push(this_block_id) - .expect("must construct checkpoint") + let new_cp = core::iter::once(block_id).chain(tail.into_iter().rev()); + //.map(|block| (block.height, block.hash)); + + base.extend(new_cp).expect("tail is in order") } /// Puts another checkpoint onto the linked list representing the blockchain. @@ -89,48 +168,37 @@ impl CheckPoint { pub fn push(self, block: BlockId) -> Result { if self.height() < block.height { Ok(Self(Arc::new(CPInner { - block, + height: block.height, + data: block.hash, prev: Some(self.0), }))) } else { Err(self) } } +} - /// 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) - } - - /// Get the [`BlockId`] of the checkpoint. - pub fn block_id(&self) -> BlockId { - self.0.block +impl CheckPoint +where + B: Copy + Clone, +{ + /// Get reference to the inner type. + pub fn inner(&self) -> &B { + &self.0.data } /// Get the height of the checkpoint. pub fn height(&self) -> u32 { - self.0.block.height - } - - /// Get the block hash of the checkpoint. - pub fn hash(&self) -> BlockHash { - self.0.block.hash + self.0.height } /// Get the previous checkpoint in the chain - pub fn prev(&self) -> Option { + 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 +213,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, { @@ -164,42 +232,6 @@ impl CheckPoint { }) } - /// 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 { - assert_ne!(block_id.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 { - return self; - } - // if we have a conflict we just return the inserted block 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 { - break cp; - } - - tail.push(cp.block_id()); - cp = cp.prev().expect("will break before genesis block"); - }; - - base.extend(core::iter::once(block_id).chain(tail.into_iter().rev())) - .expect("tail is in order") - } - /// 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 +239,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 +253,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 {