Self(Arc::new(CPInner { block, prev: None }))
}
+ /// 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.
+ ///
+ /// 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)
+ }
+
/// 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,
-use bdk_chain::local_chain::{
- AlterCheckPointError, CannotConnectError, ChangeSet, LocalChain, MissingGenesisError, Update,
+use bdk_chain::{
+ local_chain::{
+ AlterCheckPointError, CannotConnectError, ChangeSet, CheckPoint, LocalChain,
+ MissingGenesisError, Update,
+ },
+ BlockId,
};
use bitcoin::BlockHash;
);
}
}
+
+#[test]
+fn checkpoint_from_block_ids() {
+ struct TestCase<'a> {
+ name: &'a str,
+ blocks: &'a [(u32, BlockHash)],
+ exp_result: Result<(), Option<(u32, BlockHash)>>,
+ }
+
+ let test_cases = [
+ TestCase {
+ name: "in_order",
+ blocks: &[(0, h!("A")), (1, h!("B")), (3, h!("D"))],
+ exp_result: Ok(()),
+ },
+ TestCase {
+ name: "with_duplicates",
+ blocks: &[(1, h!("B")), (2, h!("C")), (2, h!("C'"))],
+ exp_result: Err(Some((2, h!("C")))),
+ },
+ TestCase {
+ name: "not_in_order",
+ blocks: &[(1, h!("B")), (3, h!("D")), (2, h!("C"))],
+ exp_result: Err(Some((3, h!("D")))),
+ },
+ TestCase {
+ name: "empty",
+ blocks: &[],
+ exp_result: Err(None),
+ },
+ TestCase {
+ name: "single",
+ blocks: &[(21, h!("million"))],
+ exp_result: Ok(()),
+ },
+ ];
+
+ for (i, t) in test_cases.into_iter().enumerate() {
+ println!("running test case {}: '{}'", i, t.name);
+ let result = CheckPoint::from_block_ids(
+ t.blocks
+ .iter()
+ .map(|&(height, hash)| BlockId { height, hash }),
+ );
+ match t.exp_result {
+ Ok(_) => {
+ assert!(result.is_ok(), "[{}:{}] should be Ok", i, t.name);
+ let result_vec = {
+ let mut v = result
+ .unwrap()
+ .into_iter()
+ .map(|cp| (cp.height(), cp.hash()))
+ .collect::<Vec<_>>();
+ v.reverse();
+ v
+ };
+ assert_eq!(
+ &result_vec, t.blocks,
+ "[{}:{}] not equal to original block ids",
+ i, t.name
+ );
+ }
+ Err(exp_last) => {
+ assert!(result.is_err(), "[{}:{}] should be Err", i, t.name);
+ let err = result.unwrap_err();
+ assert_eq!(
+ err.as_ref()
+ .map(|last_cp| (last_cp.height(), last_cp.hash())),
+ exp_last,
+ "[{}:{}] error's last cp height should be {:?}, got {:?}",
+ i,
+ t.name,
+ exp_last,
+ err
+ );
+ }
+ }
+ }
+}