use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
use bdk_chain::local_chain::LocalChain;
use bdk_chain::miniscript::Descriptor;
-use bdk_chain::{BlockId, ConfirmationBlockTime, IndexedTxGraph, SpkIterator};
+use bdk_chain::{ConfirmationBlockTime, IndexedTxGraph, SpkIterator};
use bdk_testenv::anyhow;
// This example shows how BDK chain and tx-graph structures are updated using compact
let secp = Secp256k1::new();
let (descriptor, _) = Descriptor::parse_descriptor(&secp, EXTERNAL)?;
let (change_descriptor, _) = Descriptor::parse_descriptor(&secp, INTERNAL)?;
- let (mut chain, _) = LocalChain::from_genesis_hash(genesis_block(NETWORK).block_hash());
+ let (mut chain, _) = LocalChain::from_genesis(genesis_block(NETWORK).block_hash());
let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<&str>>::new({
let mut index = KeychainTxOutIndex::default();
});
// Assume a minimum birthday height
- let block = BlockId {
- height: START_HEIGHT,
- hash: START_HASH.parse()?,
- };
- let _ = chain.insert_block(block)?;
+ let _ = chain.insert_block(START_HEIGHT, START_HASH.parse()?)?;
// Configure RPC client
let url = std::env::var("RPC_URL").context("must set RPC_URL")?;
//! [1]: https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki
use bdk_core::bitcoin;
-use bdk_core::{BlockId, CheckPoint};
+use bdk_core::CheckPoint;
+use bitcoin::BlockHash;
use bitcoin::{bip158::BlockFilter, Block, ScriptBuf};
use bitcoincore_rpc;
use bitcoincore_rpc::{json::GetBlockHeaderResult, RpcApi};
/// SPK inventory
spks: Vec<ScriptBuf>,
/// checkpoint
- cp: CheckPoint,
+ cp: CheckPoint<BlockHash>,
/// Header info, contains the prev and next hashes for each header.
header: Option<GetBlockHeaderResult>,
}
next_hash = next_header.hash;
let next_height: u32 = next_header.height.try_into()?;
- cp = cp.insert(BlockId {
- height: next_height,
- hash: next_hash,
- });
+ cp = cp.insert(next_height, next_hash);
let mut block = None;
let filter =
/// The checkpoint of the last-emitted block that is in the best chain. If it is later found
/// that the block is no longer in the best chain, it will be popped off from here.
- last_cp: CheckPoint,
+ last_cp: CheckPoint<BlockHash>,
/// The block result returned from rpc of the last-emitted block. As this result contains the
/// next block's block hash (which we use to fetch the next block), we set this to `None`
/// If it is known that the wallet is empty, [`NO_EXPECTED_MEMPOOL_TXS`] can be used.
pub fn new(
client: C,
- last_cp: CheckPoint,
+ last_cp: CheckPoint<BlockHash>,
start_height: u32,
expected_mempool_txs: impl IntoIterator<Item = impl Into<Arc<Transaction>>>,
) -> Self {
///
/// This is important as BDK structures require block-to-apply to be connected with another
/// block in the original chain.
- pub checkpoint: CheckPoint,
+ pub checkpoint: CheckPoint<BlockHash>,
}
impl<B> BlockEvent<B> {
NoMoreBlocks,
/// Fetched block is not in the best chain.
BlockNotInBestChain,
- AgreementFound(bitcoincore_rpc_json::GetBlockResult, CheckPoint),
+ AgreementFound(bitcoincore_rpc_json::GetBlockResult, CheckPoint<BlockHash>),
/// Force the genesis checkpoint down the receiver's throat.
AgreementPointNotFound(BlockHash),
}
fn poll<C, V, F>(
emitter: &mut Emitter<C>,
get_item: F,
-) -> Result<Option<(CheckPoint, V)>, bitcoincore_rpc::Error>
+) -> Result<Option<(CheckPoint<BlockHash>, V)>, bitcoincore_rpc::Error>
where
C: Deref,
C::Target: RpcApi,
let new_cp = emitter
.last_cp
.clone()
- .push(BlockId { height, hash })
+ .push(height, hash)
.expect("must push");
emitter.last_cp = new_cp.clone();
emitter.last_block = Some(res);
continue;
}
PollResponse::AgreementPointNotFound(genesis_hash) => {
- emitter.last_cp = CheckPoint::new(BlockId {
- height: 0,
- hash: genesis_hash,
- });
+ emitter.last_cp = CheckPoint::new(0, genesis_hash);
emitter.last_block = None;
continue;
}
#[test]
fn test_expected_mempool_txids_accumulate_and_remove() -> anyhow::Result<()> {
let env = TestEnv::new()?;
- let chain = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?).0;
+ let chain = LocalChain::from_genesis(env.rpc_client().get_block_hash(0)?).0;
let chain_tip = chain.tip();
let mut emitter = Emitter::new(
env.rpc_client(),
pub fn test_sync_local_chain() -> anyhow::Result<()> {
let env = TestEnv::new()?;
let network_tip = env.rpc_client().get_block_count()?;
- let (mut local_chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?);
+ let (mut local_chain, _) = LocalChain::from_genesis(env.rpc_client().get_block_hash(0)?);
let mut emitter = Emitter::new(
env.rpc_client(),
local_chain.tip(),
env.mine_blocks(101, None)?;
- let (mut chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?);
+ let (mut chain, _) = LocalChain::from_genesis(env.rpc_client().get_block_hash(0)?);
let mut indexed_tx_graph = IndexedTxGraph::<BlockId, _>::new({
let mut index = SpkTxOutIndex::<usize>::default();
index.insert_spk(0, addr_0.script_pubkey());
let env = TestEnv::new()?;
let mut emitter = Emitter::new(
env.rpc_client(),
- CheckPoint::new(BlockId {
- height: 0,
- hash: env.rpc_client().get_block_hash(0)?,
- }),
+ CheckPoint::new(0, env.rpc_client().get_block_hash(0)?),
EMITTER_START_HEIGHT as _,
NO_EXPECTED_MEMPOOL_TXS,
);
block: Block,
block_height: u32,
) -> anyhow::Result<()> {
- recv_chain.apply_update(CheckPoint::from_header(&block.header, block_height))?;
+ recv_chain.apply_header(&block.header, block_height)?;
let _ = recv_graph.apply_block(block, block_height);
Ok(())
}
let env = TestEnv::new()?;
let mut emitter = Emitter::new(
env.rpc_client(),
- CheckPoint::new(BlockId {
- height: 0,
- hash: env.rpc_client().get_block_hash(0)?,
- }),
+ CheckPoint::new(0, env.rpc_client().get_block_hash(0)?),
0,
NO_EXPECTED_MEMPOOL_TXS,
);
let addr_to_track = Address::from_script(&spk_to_track, Network::Regtest)?;
// setup receiver
- let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.rpc_client().get_block_hash(0)?);
+ let (mut recv_chain, _) = LocalChain::from_genesis(env.rpc_client().get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<BlockId, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
let env = TestEnv::new()?;
let mut emitter = Emitter::new(
env.rpc_client(),
- CheckPoint::new(BlockId {
- height: 0,
- hash: env.rpc_client().get_block_hash(0)?,
- }),
+ CheckPoint::new(0, env.rpc_client().get_block_hash(0)?),
0,
NO_EXPECTED_MEMPOOL_TXS,
);
// start height is 99
let mut emitter = Emitter::new(
env.rpc_client(),
- CheckPoint::new(BlockId {
- height: 0,
- hash: env.rpc_client().get_block_hash(0)?,
- }),
+ CheckPoint::new(0, env.rpc_client().get_block_hash(0)?),
(PREMINE_COUNT - 2) as u32,
NO_EXPECTED_MEMPOOL_TXS,
);
.0;
let spk = desc.at_derivation_index(0)?.script_pubkey();
- let mut chain = LocalChain::from_genesis_hash(genesis_block(Network::Regtest).block_hash()).0;
+ let mut chain = LocalChain::from_genesis(genesis_block(Network::Regtest).block_hash()).0;
let chain_tip = chain.tip().block_id();
let mut index = SpkTxOutIndex::default();
let mut emitter = Emitter::new(env.rpc_client(), chain.tip(), 1, core::iter::once(tx_1));
while let Some(emission) = emitter.next_block()? {
let height = emission.block_height();
- chain.apply_update(CheckPoint::from_header(&emission.block.header, height))?;
+ chain.apply_header(&emission.block.header, height)?;
}
let changeset = graph.batch_insert_unconfirmed(emitter.mempool()?.update);
let mut emitter = Emitter::new(
env.rpc_client(),
- CheckPoint::new(BlockId {
- height: 0,
- hash: env.rpc_client().get_block_hash(0)?,
- }),
+ CheckPoint::new(0, env.rpc_client().get_block_hash(0)?),
0,
NO_EXPECTED_MEMPOOL_TXS,
);
use bdk_bitcoind_rpc::bip158::{Error, FilterIter};
-use bdk_core::{BlockId, CheckPoint};
+use bdk_core::CheckPoint;
use bdk_testenv::{anyhow, bitcoind, TestEnv};
use bitcoin::{Address, Amount, Network, ScriptBuf};
use bitcoincore_rpc::RpcApi;
let _ = env.mine_blocks(1, None);
let genesis_hash = env.genesis_hash()?;
- let cp = CheckPoint::new(BlockId {
- height: 0,
- hash: genesis_hash,
- });
+ let cp = CheckPoint::new(0, genesis_hash);
let iter = FilterIter::new(&env.bitcoind.client, cp, [addr.script_pubkey()]);
let _ = env.mine_blocks(10, None)?;
// Try to initialize FilterIter with a CP on the wrong network
- let block_id = BlockId {
- height: 0,
- hash: bitcoin::hashes::Hash::hash(b"wrong-hash"),
- };
- let cp = CheckPoint::new(block_id);
+ let cp = CheckPoint::new(0, bitcoin::hashes::Hash::hash(b"wrong-hash"));
let mut iter = FilterIter::new(&env.bitcoind.client, cp, [ScriptBuf::new()]);
assert!(matches!(iter.next(), Some(Err(Error::ReorgDepthExceeded))));
}
let genesis_hash = env.genesis_hash()?;
- let cp = CheckPoint::new(BlockId {
- height: 0,
- hash: genesis_hash,
- });
+ let cp = CheckPoint::new(0, genesis_hash);
let spk = ScriptBuf::from_hex("0014446906a6560d8ad760db3156706e72e171f3a2aa")?;
let mut iter = FilterIter::new(&env.bitcoind.client, cp, [spk]);
fn setup<F: Fn(&mut KeychainTxGraph, &LocalChain)>(f: F) -> (KeychainTxGraph, LocalChain) {
const DESC: &str = "tr([ab28dc00/86h/1h/0h]tpubDCdDtzAMZZrkwKBxwNcGCqe4FRydeD9rfMisoi7qLdraG79YohRfPW4YgdKQhpgASdvh612xXNY5xYzoqnyCgPbkpK4LSVcH5Xv4cK7johH/0/*)";
- let cp = CheckPoint::from_block_ids([genesis_block_id(), tip_block_id()])
- .expect("blocks must be chronological");
+ let cp = CheckPoint::from_blocks(
+ [genesis_block_id(), tip_block_id()]
+ .into_iter()
+ .map(|block_id| (block_id.height, block_id.hash)),
+ )
+ .expect("blocks must be chronological");
let chain = LocalChain::from_tip(cp).unwrap();
let (desc, _) =
.unwrap()
.0;
- let cp = CheckPoint::from_block_ids([genesis_block_id(), tip_block_id()]).unwrap();
+ let cp = CheckPoint::from_blocks(
+ [genesis_block_id(), tip_block_id()]
+ .into_iter()
+ .map(|block_id| (block_id.height, block_id.hash)),
+ )
+ .unwrap();
let chain = LocalChain::from_tip(cp).unwrap();
let mut index = KeychainTxOutIndex::new(LOOKAHEAD, USE_SPK_CACHE);
//! The [`LocalChain`] is a local implementation of [`ChainOracle`].
use core::convert::Infallible;
+use core::fmt;
use core::ops::RangeBounds;
use crate::collections::BTreeMap;
use crate::{BlockId, ChainOracle, Merge};
+use bdk_core::ToBlockHash;
pub use bdk_core::{CheckPoint, CheckPointIter};
use bitcoin::block::Header;
use bitcoin::BlockHash;
/// Apply `changeset` to the checkpoint.
-fn apply_changeset_to_checkpoint(
- mut init_cp: CheckPoint,
- changeset: &ChangeSet,
-) -> Result<CheckPoint, MissingGenesisError> {
+fn apply_changeset_to_checkpoint<D>(
+ mut init_cp: CheckPoint<D>,
+ changeset: &ChangeSet<D>,
+) -> Result<CheckPoint<D>, MissingGenesisError>
+where
+ D: ToBlockHash + fmt::Debug + Copy,
+{
if let Some(start_height) = changeset.blocks.keys().next().cloned() {
// changes after point of agreement
let mut extension = BTreeMap::default();
// point of agreement
- let mut base: Option<CheckPoint> = None;
+ let mut base: Option<CheckPoint<D>> = None;
for cp in init_cp.iter() {
if cp.height() >= start_height {
- extension.insert(cp.height(), cp.hash());
+ extension.insert(cp.height(), cp.data());
} else {
base = Some(cp);
break;
}
}
- for (&height, &hash) in &changeset.blocks {
- match hash {
- Some(hash) => {
- extension.insert(height, hash);
+ for (&height, &data) in &changeset.blocks {
+ match data {
+ Some(data) => {
+ extension.insert(height, data);
}
None => {
extension.remove(&height);
let new_tip = match base {
Some(base) => base
- .extend(extension.into_iter().map(BlockId::from))
+ .extend(extension)
.expect("extension is strictly greater than base"),
None => LocalChain::from_blocks(extension)?.tip(),
};
}
/// This is a local implementation of [`ChainOracle`].
-#[derive(Debug, Clone, PartialEq)]
-pub struct LocalChain {
- tip: CheckPoint,
+#[derive(Debug, Clone)]
+pub struct LocalChain<D = BlockHash> {
+ tip: CheckPoint<D>,
+}
+
+impl<D> PartialEq for LocalChain<D> {
+ fn eq(&self, other: &Self) -> bool {
+ self.tip == other.tip
+ }
}
-impl ChainOracle for LocalChain {
+impl<D> ChainOracle for LocalChain<D> {
type Error = Infallible;
fn is_block_in_chain(
}
}
-impl LocalChain {
- /// Get the genesis hash.
- pub fn genesis_hash(&self) -> BlockHash {
- self.tip.get(0).expect("genesis must exist").hash()
- }
-
- /// Construct [`LocalChain`] from genesis `hash`.
- #[must_use]
- pub fn from_genesis_hash(hash: BlockHash) -> (Self, ChangeSet) {
- let height = 0;
- let chain = Self {
- tip: CheckPoint::new(BlockId { height, hash }),
- };
- let changeset = chain.initial_changeset();
- (chain, changeset)
- }
-
- /// Construct a [`LocalChain`] from an initial `changeset`.
- pub fn from_changeset(changeset: ChangeSet) -> Result<Self, MissingGenesisError> {
- let genesis_entry = changeset.blocks.get(&0).copied().flatten();
- let genesis_hash = match genesis_entry {
- Some(hash) => hash,
- None => return Err(MissingGenesisError),
- };
-
- let (mut chain, _) = Self::from_genesis_hash(genesis_hash);
- chain.apply_changeset(&changeset)?;
-
- debug_assert!(chain._check_changeset_is_applied(&changeset));
-
- Ok(chain)
- }
-
- /// Construct a [`LocalChain`] from a given `checkpoint` tip.
- pub fn from_tip(tip: CheckPoint) -> Result<Self, MissingGenesisError> {
- let genesis_cp = tip.iter().last().expect("must have at least one element");
- if genesis_cp.height() != 0 {
- return Err(MissingGenesisError);
- }
- Ok(Self { tip })
- }
-
- /// Constructs a [`LocalChain`] from a [`BTreeMap`] of height to [`BlockHash`].
- ///
- /// The [`BTreeMap`] enforces the height order. However, the caller must ensure the blocks are
- /// all of the same chain.
- pub fn from_blocks(blocks: BTreeMap<u32, BlockHash>) -> Result<Self, MissingGenesisError> {
- if !blocks.contains_key(&0) {
- return Err(MissingGenesisError);
- }
-
- let mut tip: Option<CheckPoint> = None;
- for block in &blocks {
- match tip {
- Some(curr) => {
- tip = Some(
- curr.push(BlockId::from(block))
- .expect("BTreeMap is ordered"),
- )
- }
- None => tip = Some(CheckPoint::new(BlockId::from(block))),
- }
- }
-
- Ok(Self {
- tip: tip.expect("already checked to have genesis"),
- })
- }
-
- /// Get the highest checkpoint.
- pub fn tip(&self) -> CheckPoint {
- self.tip.clone()
- }
-
- /// Applies the given `update` to the chain.
- ///
- /// The method returns [`ChangeSet`] on success. This represents the changes applied to `self`.
- ///
- /// There must be no ambiguity about which of the existing chain's blocks are still valid and
- /// which are now invalid. That is, the new chain must implicitly connect to a definite block in
- /// the existing chain and invalidate the block after it (if it exists) by including a block at
- /// the same height but with a different hash to explicitly exclude it as a connection point.
- ///
- /// # Errors
- ///
- /// An error will occur if the update does not correctly connect with `self`.
- ///
- /// [module-level documentation]: crate::local_chain
- pub fn apply_update(&mut self, update: CheckPoint) -> Result<ChangeSet, CannotConnectError> {
- let (new_tip, changeset) = merge_chains(self.tip.clone(), update)?;
- self.tip = new_tip;
- debug_assert!(self._check_changeset_is_applied(&changeset));
- Ok(changeset)
- }
-
+// Methods for `LocalChain<BlockHash>`
+impl LocalChain<BlockHash> {
/// Update the chain with a given [`Header`] at `height` which you claim is connected to a
/// existing block in the chain.
///
conn => Some(conn),
};
- let update = CheckPoint::from_block_ids([conn, prev, Some(this)].into_iter().flatten())
- .expect("block ids must be in order");
+ let update = CheckPoint::from_blocks(
+ [conn, prev, Some(this)]
+ .into_iter()
+ .flatten()
+ .map(|block_id| (block_id.height, block_id.hash)),
+ )
+ .expect("block ids must be in order");
self.apply_update(update)
.map_err(ApplyHeaderError::CannotConnect)
ApplyHeaderError::CannotConnect(err) => err,
})
}
+}
+
+// Methods for any `D`
+impl<D> LocalChain<D> {
+ /// Get the highest checkpoint.
+ pub fn tip(&self) -> CheckPoint<D> {
+ self.tip.clone()
+ }
+
+ /// Get the genesis hash.
+ pub fn genesis_hash(&self) -> BlockHash {
+ self.tip.get(0).expect("genesis must exist").block_id().hash
+ }
+
+ /// Iterate over checkpoints in descending height order.
+ pub fn iter_checkpoints(&self) -> CheckPointIter<D> {
+ self.tip.iter()
+ }
+
+ /// Get checkpoint at given `height` (if it exists).
+ ///
+ /// This is a shorthand for calling [`CheckPoint::get`] on the [`tip`].
+ ///
+ /// [`tip`]: LocalChain::tip
+ pub fn get(&self, height: u32) -> Option<CheckPoint<D>> {
+ self.tip.get(height)
+ }
+
+ /// Iterate checkpoints over a height range.
+ ///
+ /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
+ /// height).
+ ///
+ /// This is a shorthand for calling [`CheckPoint::range`] on the [`tip`].
+ ///
+ /// [`tip`]: LocalChain::tip
+ pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint<D>>
+ where
+ R: RangeBounds<u32>,
+ {
+ self.tip.range(range)
+ }
+}
+
+// Methods where `D: ToBlockHash`
+impl<D> LocalChain<D>
+where
+ D: ToBlockHash + fmt::Debug + Copy,
+{
+ /// Constructs a [`LocalChain`] from genesis data.
+ pub fn from_genesis(data: D) -> (Self, ChangeSet<D>) {
+ let height = 0;
+ let chain = Self {
+ tip: CheckPoint::new(height, data),
+ };
+ let changeset = chain.initial_changeset();
+
+ (chain, changeset)
+ }
+
+ /// Constructs a [`LocalChain`] from a [`BTreeMap`] of height and data `D`.
+ ///
+ /// The [`BTreeMap`] enforces the height order. However, the caller must ensure the blocks are
+ /// all of the same chain.
+ pub fn from_blocks(blocks: BTreeMap<u32, D>) -> Result<Self, MissingGenesisError> {
+ if !blocks.contains_key(&0) {
+ return Err(MissingGenesisError);
+ }
+
+ Ok(Self {
+ tip: CheckPoint::from_blocks(blocks).expect("blocks must be in order"),
+ })
+ }
+
+ /// Construct a [`LocalChain`] from an initial `changeset`.
+ pub fn from_changeset(changeset: ChangeSet<D>) -> Result<Self, MissingGenesisError> {
+ let genesis_entry = changeset.blocks.get(&0).copied().flatten();
+ let genesis_data = match genesis_entry {
+ Some(data) => data,
+ None => return Err(MissingGenesisError),
+ };
+
+ let (mut chain, _) = Self::from_genesis(genesis_data);
+ chain.apply_changeset(&changeset)?;
+ debug_assert!(chain._check_changeset_is_applied(&changeset));
+ Ok(chain)
+ }
+
+ /// Construct a [`LocalChain`] from a given `checkpoint` tip.
+ pub fn from_tip(tip: CheckPoint<D>) -> Result<Self, MissingGenesisError> {
+ let genesis_cp = tip.iter().last().expect("must have at least one element");
+ if genesis_cp.height() != 0 {
+ return Err(MissingGenesisError);
+ }
+
+ Ok(Self { tip })
+ }
+
+ /// Applies the given `update` to the chain.
+ ///
+ /// The method returns [`ChangeSet`] on success. This represents the changes applied to `self`.
+ ///
+ /// There must be no ambiguity about which of the existing chain's blocks are still valid and
+ /// which are now invalid. That is, the new chain must implicitly connect to a definite block in
+ /// the existing chain and invalidate the block after it (if it exists) by including a block at
+ /// the same height but with a different hash to explicitly exclude it as a connection point.
+ ///
+ /// # Errors
+ ///
+ /// An error will occur if the update does not correctly connect with `self`.
+ ///
+ /// [module-level documentation]: crate::local_chain
+ pub fn apply_update(
+ &mut self,
+ update: CheckPoint<D>,
+ ) -> Result<ChangeSet<D>, CannotConnectError> {
+ let (new_tip, changeset) = merge_chains(self.tip.clone(), update)?;
+ self.tip = new_tip;
+ debug_assert!(self._check_changeset_is_applied(&changeset));
+ Ok(changeset)
+ }
/// Apply the given `changeset`.
- pub fn apply_changeset(&mut self, changeset: &ChangeSet) -> Result<(), MissingGenesisError> {
+ pub fn apply_changeset(&mut self, changeset: &ChangeSet<D>) -> Result<(), MissingGenesisError> {
let old_tip = self.tip.clone();
let new_tip = apply_changeset_to_checkpoint(old_tip, changeset)?;
self.tip = new_tip;
Ok(())
}
- /// Insert a [`BlockId`].
+ /// Derives an initial [`ChangeSet`], meaning that it can be applied to an empty chain to
+ /// recover the current chain.
+ pub fn initial_changeset(&self) -> ChangeSet<D> {
+ ChangeSet {
+ blocks: self
+ .tip
+ .iter()
+ .map(|cp| (cp.height(), Some(cp.data())))
+ .collect(),
+ }
+ }
+
+ /// Insert block into a [`LocalChain`].
///
/// # Errors
///
/// Replacing the block hash of an existing checkpoint will result in an error.
- pub fn insert_block(&mut self, block_id: BlockId) -> Result<ChangeSet, AlterCheckPointError> {
- if let Some(original_cp) = self.tip.get(block_id.height) {
+ pub fn insert_block(
+ &mut self,
+ height: u32,
+ data: D,
+ ) -> Result<ChangeSet<D>, AlterCheckPointError> {
+ if let Some(original_cp) = self.tip.get(height) {
let original_hash = original_cp.hash();
- if original_hash != block_id.hash {
+ if original_hash != data.to_blockhash() {
return Err(AlterCheckPointError {
- height: block_id.height,
+ height,
original_hash,
- update_hash: Some(block_id.hash),
+ update_hash: Some(data.to_blockhash()),
});
}
return Ok(ChangeSet::default());
}
- let mut changeset = ChangeSet::default();
- changeset
- .blocks
- .insert(block_id.height, Some(block_id.hash));
+ let mut changeset = ChangeSet::<D>::default();
+ changeset.blocks.insert(height, Some(data));
self.apply_changeset(&changeset)
.map_err(|_| AlterCheckPointError {
height: 0,
original_hash: self.genesis_hash(),
- update_hash: changeset.blocks.get(&0).cloned().flatten(),
+ update_hash: changeset
+ .blocks
+ .get(&0)
+ .cloned()
+ .flatten()
+ .map(|d| d.to_blockhash()),
})?;
Ok(changeset)
}
///
/// This will fail with [`MissingGenesisError`] if the caller attempts to disconnect from the
/// genesis block.
- pub fn disconnect_from(&mut self, block_id: BlockId) -> Result<ChangeSet, MissingGenesisError> {
- let mut remove_from = Option::<CheckPoint>::None;
+ pub fn disconnect_from(
+ &mut self,
+ block_id: BlockId,
+ ) -> Result<ChangeSet<D>, MissingGenesisError> {
+ let mut remove_from = Option::<CheckPoint<D>>::None;
let mut changeset = ChangeSet::default();
for cp in self.tip().iter() {
let cp_id = cp.block_id();
Ok(changeset)
}
- /// Derives an initial [`ChangeSet`], meaning that it can be applied to an empty chain to
- /// recover the current chain.
- pub fn initial_changeset(&self) -> ChangeSet {
- ChangeSet {
- blocks: self
- .tip
- .iter()
- .map(|cp| {
- let block_id = cp.block_id();
- (block_id.height, Some(block_id.hash))
- })
- .collect(),
- }
- }
-
- /// Iterate over checkpoints in descending height order.
- pub fn iter_checkpoints(&self) -> CheckPointIter {
- self.tip.iter()
- }
-
- fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool {
- let mut curr_cp = self.tip.clone();
- for (height, exp_hash) in changeset.blocks.iter().rev() {
- match curr_cp.get(*height) {
- Some(query_cp) => {
- if query_cp.height() != *height || Some(query_cp.hash()) != *exp_hash {
+ fn _check_changeset_is_applied(&self, changeset: &ChangeSet<D>) -> bool {
+ let mut cur = self.tip.clone();
+ for (&exp_height, exp_data) in changeset.blocks.iter().rev() {
+ match cur.get(exp_height) {
+ Some(cp) => {
+ if cp.height() != exp_height
+ || Some(cp.hash()) != exp_data.map(|d| d.to_blockhash())
+ {
return false;
}
- curr_cp = query_cp;
+ cur = cp;
}
None => {
- if exp_hash.is_some() {
+ if exp_data.is_some() {
return false;
}
}
}
true
}
-
- /// Get checkpoint at given `height` (if it exists).
- ///
- /// This is a shorthand for calling [`CheckPoint::get`] on the [`tip`].
- ///
- /// [`tip`]: LocalChain::tip
- pub fn get(&self, height: u32) -> Option<CheckPoint> {
- self.tip.get(height)
- }
-
- /// Iterate checkpoints over a height range.
- ///
- /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
- /// height).
- ///
- /// This is a shorthand for calling [`CheckPoint::range`] on the [`tip`].
- ///
- /// [`tip`]: LocalChain::tip
- pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
- where
- R: RangeBounds<u32>,
- {
- self.tip.range(range)
- }
}
/// The [`ChangeSet`] represents changes to [`LocalChain`].
-#[derive(Debug, Default, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
-pub struct ChangeSet {
+pub struct ChangeSet<D = BlockHash> {
/// Changes to the [`LocalChain`] blocks.
///
/// The key represents the block height, and the value either represents added a new
/// [`CheckPoint`] (if [`Some`]), or removing a [`CheckPoint`] (if [`None`]).
- pub blocks: BTreeMap<u32, Option<BlockHash>>,
+ pub blocks: BTreeMap<u32, Option<D>>,
+}
+
+impl<D> Default for ChangeSet<D> {
+ fn default() -> Self {
+ ChangeSet {
+ blocks: BTreeMap::default(),
+ }
+ }
}
-impl Merge for ChangeSet {
+impl<D> Merge for ChangeSet<D> {
fn merge(&mut self, other: Self) {
Merge::merge(&mut self.blocks, other.blocks)
}
}
}
-impl<B: IntoIterator<Item = (u32, Option<BlockHash>)>> From<B> for ChangeSet {
- fn from(blocks: B) -> Self {
+impl<D, I: IntoIterator<Item = (u32, Option<D>)>> From<I> for ChangeSet<D> {
+ fn from(blocks: I) -> Self {
Self {
blocks: blocks.into_iter().collect(),
}
}
}
-impl FromIterator<(u32, Option<BlockHash>)> for ChangeSet {
- fn from_iter<T: IntoIterator<Item = (u32, Option<BlockHash>)>>(iter: T) -> Self {
+impl<D> FromIterator<(u32, Option<D>)> for ChangeSet<D> {
+ fn from_iter<T: IntoIterator<Item = (u32, Option<D>)>>(iter: T) -> Self {
Self {
blocks: iter.into_iter().collect(),
}
}
}
-impl FromIterator<(u32, BlockHash)> for ChangeSet {
- fn from_iter<T: IntoIterator<Item = (u32, BlockHash)>>(iter: T) -> Self {
+impl<D> FromIterator<(u32, D)> for ChangeSet<D> {
+ fn from_iter<T: IntoIterator<Item = (u32, D)>>(iter: T) -> Self {
Self {
blocks: iter
.into_iter()
/// the update and original chain both have a block above the point of agreement, but their
/// heights do not overlap).
/// - The update attempts to replace the genesis block of the original chain.
-fn merge_chains(
- original_tip: CheckPoint,
- update_tip: CheckPoint,
-) -> Result<(CheckPoint, ChangeSet), CannotConnectError> {
- let mut changeset = ChangeSet::default();
+fn merge_chains<D>(
+ original_tip: CheckPoint<D>,
+ update_tip: CheckPoint<D>,
+) -> Result<(CheckPoint<D>, ChangeSet<D>), CannotConnectError>
+where
+ D: ToBlockHash + fmt::Debug + Copy,
+{
+ let mut changeset = ChangeSet::<D>::default();
let mut orig = original_tip.iter();
let mut update = update_tip.iter();
let mut curr_orig = None;
let mut curr_update = None;
- let mut prev_orig: Option<CheckPoint> = None;
- let mut prev_update: Option<CheckPoint> = None;
+ let mut prev_orig: Option<CheckPoint<D>> = None;
+ let mut prev_update: Option<CheckPoint<D>> = None;
let mut point_of_agreement_found = false;
match (curr_orig.as_ref(), curr_update.as_ref()) {
// Update block that doesn't exist in the original chain
(o, Some(u)) if Some(u.height()) > o.map(|o| o.height()) => {
- changeset.blocks.insert(u.height(), Some(u.hash()));
+ changeset.blocks.insert(u.height(), Some(u.data()));
prev_update = curr_update.take();
}
// Original block that isn't in the update
} else {
// We have an invalidation height so we set the height to the updated hash and
// also purge all the original chain block hashes above this block.
- changeset.blocks.insert(u.height(), Some(u.hash()));
+ changeset.blocks.insert(u.height(), Some(u.data()));
for invalidated_height in potentially_invalidated_heights.drain(..) {
changeset.blocks.insert(invalidated_height, None);
}
/// # use bdk_chain::ConfirmationBlockTime;
/// # use bdk_chain::example_utils::*;
/// # use bitcoin::hashes::Hash;
+/// # use bitcoin::BlockHash;
/// // Initialize the local chain with two blocks.
-/// let chain = LocalChain::from_blocks(
+/// let chain = LocalChain::<BlockHash>::from_blocks(
/// [
/// (1, Hash::hash("first".as_bytes())),
/// (2, Hash::hash("second".as_bytes())),
/// # use bdk_chain::tx_graph::TxGraph;
/// # use bdk_chain::{local_chain::LocalChain, CanonicalizationParams, ConfirmationBlockTime};
/// # use bdk_testenv::{hash, utils::new_tx};
- /// # use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
+ /// # use bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
///
/// # let spk = ScriptBuf::from_hex("0014c692ecf13534982a9a2834565cbd37add8027140").unwrap();
/// # let chain =
- /// # LocalChain::from_blocks((0..=15).map(|i| (i as u32, hash!("h"))).collect()).unwrap();
+ /// # LocalChain::<BlockHash>::from_blocks((0..=15).map(|i| (i as u32, hash!("h"))).collect()).unwrap();
/// # let mut graph: TxGraph = TxGraph::default();
/// # let coinbase_tx = Transaction {
/// # input: vec![TxIn {
TestEnv,
};
use bitcoin::{
- secp256k1::Secp256k1, Address, Amount, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut,
- Txid,
+ secp256k1::Secp256k1, Address, Amount, BlockHash, Network, OutPoint, ScriptBuf, Transaction,
+ TxIn, TxOut, Txid,
};
use miniscript::Descriptor;
#[test]
fn test_list_owned_txouts() {
// Create Local chains
- let local_chain =
- LocalChain::from_blocks((0..150).map(|i| (i as u32, hash!("random"))).collect())
- .expect("must have genesis hash");
+ let local_chain = LocalChain::<BlockHash>::from_blocks(
+ (0..150).map(|i| (i as u32, hash!("random"))).collect(),
+ )
+ .expect("must have genesis hash");
// Initiate IndexedTxGraph
});
// Anchors to test
- let blocks = vec![block_id!(0, "g"), block_id!(1, "A"), block_id!(2, "B")];
-
- let cp = CheckPoint::from_block_ids(blocks.clone()).unwrap();
+ let blocks = [block_id!(0, "g"), block_id!(1, "A"), block_id!(2, "B")];
+
+ let cp = CheckPoint::from_blocks(
+ blocks
+ .iter()
+ .map(|block_id| (block_id.height, block_id.hash)),
+ )
+ .unwrap();
let chain = LocalChain::from_tip(cp).unwrap();
// The test will insert a transaction into the indexed tx graph along with any anchors and
struct TestLocalChain<'a> {
name: &'static str,
chain: LocalChain,
- update: CheckPoint,
+ update: CheckPoint<BlockHash>,
exp: ExpectedResult<'a>,
}
for (i, t) in test_cases.into_iter().enumerate() {
let mut chain = t.original;
+ let block_id: BlockId = t.insert.into();
assert_eq!(
- chain.insert_block(t.insert.into()),
+ chain.insert_block(block_id.height, block_id.hash),
t.expected_result,
"[{i}] unexpected result when inserting block",
);
];
for (i, t) in test_cases.into_iter().enumerate() {
- let result = CheckPoint::from_block_ids(
- t.blocks
- .iter()
- .map(|&(height, hash)| BlockId { height, hash }),
- );
+ let result = CheckPoint::<BlockHash>::from_blocks(t.blocks.iter().copied());
match t.exp_result {
Ok(_) => {
assert!(result.is_ok(), "[{}:{}] should be Ok", i, t.name);
}
for t in test_cases.into_iter() {
- let chain = CheckPoint::from_block_ids(
- genesis_block().chain(t.chain.iter().copied().map(BlockId::from)),
+ let chain = CheckPoint::from_blocks(
+ genesis_block()
+ .chain(t.chain.iter().copied().map(BlockId::from))
+ .map(|block_id| (block_id.height, block_id.hash)),
)
.expect("test formed incorrectly, must construct checkpoint chain");
- let exp_final_chain = CheckPoint::from_block_ids(
- genesis_block().chain(t.exp_final_chain.iter().copied().map(BlockId::from)),
+ let exp_final_chain = CheckPoint::from_blocks(
+ genesis_block()
+ .chain(t.exp_final_chain.iter().copied().map(BlockId::from))
+ .map(|block_id| (block_id.height, block_id.hash)),
)
.expect("test formed incorrectly, must construct checkpoint chain");
+ let BlockId { height, hash } = t.to_insert.into();
assert_eq!(
- chain.insert(t.to_insert.into()),
+ chain.insert(height, hash),
exp_final_chain,
"unexpected final chain"
);
)
}
-fn generate_checkpoints(max_height: u32, max_count: usize) -> impl Strategy<Value = CheckPoint> {
+fn generate_checkpoints(
+ max_height: u32,
+ max_count: usize,
+) -> impl Strategy<Value = CheckPoint<BlockHash>> {
proptest::collection::btree_set(1..max_height, 0..max_count).prop_map(|mut heights| {
heights.insert(0); // must have genesis
- CheckPoint::from_block_ids(heights.into_iter().map(|height| {
+ CheckPoint::from_blocks(heights.into_iter().map(|height| {
let hash = bitcoin::hashes::Hash::hash(height.to_le_bytes().as_slice());
- BlockId { height, hash }
+ (height, hash)
}))
.expect("blocks must be in order as it comes from btreeset")
})
#[macro_use]
mod common;
-use bdk_chain::{Balance, BlockId};
+use bdk_chain::{local_chain::LocalChain, Balance, BlockId};
use bdk_testenv::{block_id, hash, local_chain};
-use bitcoin::{Amount, OutPoint, ScriptBuf};
+use bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf};
use common::*;
use std::collections::{BTreeSet, HashSet};
#[test]
fn test_tx_conflict_handling() {
// Create Local chains
- let local_chain = local_chain!(
+ let local_chain: LocalChain<BlockHash> = local_chain!(
(0, hash!("A")),
(1, hash!("B")),
(2, hash!("C")),
+use core::fmt;
use core::ops::RangeBounds;
use alloc::sync::Arc;
-use bitcoin::BlockHash;
+use bitcoin::{block::Header, Block, BlockHash};
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>);
+#[derive(Debug)]
+pub struct CheckPoint<D = BlockHash>(Arc<CPInner<D>>);
+
+impl<D> Clone for CheckPoint<D> {
+ fn clone(&self) -> Self {
+ CheckPoint(Arc::clone(&self.0))
+ }
+}
/// The internal contents of [`CheckPoint`].
-#[derive(Debug, Clone)]
-struct CPInner {
- /// Block id (hash and height).
- block: BlockId,
+#[derive(Debug)]
+struct CPInner<D> {
+ /// Block id
+ block_id: BlockId,
+ /// Data.
+ data: D,
/// Previous checkpoint (if any).
- prev: Option<Arc<CPInner>>,
+ prev: Option<Arc<CPInner<D>>>,
}
/// When a `CPInner` is dropped we need to go back down the chain and manually remove any
/// leads to recursive logic and stack overflows
///
/// https://github.com/bitcoindevkit/bdk/issues/1634
-impl Drop for CPInner {
+impl<D> Drop for CPInner<D> {
fn drop(&mut self) {
// Take out `prev` so its `drop` won't be called when this drop is finished
let mut current = self.prev.take();
}
}
-impl PartialEq for CheckPoint {
- 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());
- self_cps.eq(other_cps)
- }
+/// Trait that converts [`CheckPoint`] `data` to [`BlockHash`].
+///
+/// Implementations of [`ToBlockHash`] must consist exclusively of the data to be hashed — do not
+/// include any extraneous fields.
+pub trait ToBlockHash {
+ /// Returns the [`BlockHash`] for the associated [`CheckPoint`] `data` type.
+ fn to_blockhash(&self) -> BlockHash;
}
-impl CheckPoint {
- /// Construct a new base block at the front of a linked list.
- pub fn new(block: BlockId) -> Self {
- Self(Arc::new(CPInner { block, prev: None }))
+impl ToBlockHash for BlockHash {
+ fn to_blockhash(&self) -> BlockHash {
+ *self
}
+}
- /// 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)
+impl ToBlockHash for Header {
+ fn to_blockhash(&self) -> BlockHash {
+ self.block_hash()
}
+}
- /// 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,
- };
+impl ToBlockHash for Block {
+ fn to_blockhash(&self) -> BlockHash {
+ self.block_hash()
+ }
+}
- CheckPoint::new(prev_block_id)
- .push(this_block_id)
- .expect("must construct checkpoint")
+impl<D> PartialEq for CheckPoint<D> {
+ 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());
+ self_cps.eq(other_cps)
}
+}
- /// 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> {
- if self.height() < block.height {
- Ok(Self(Arc::new(CPInner {
- block,
- prev: Some(self.0),
- })))
- } else {
- Err(self)
- }
+// Methods for any `D`
+impl<D> CheckPoint<D> {
+ /// Get a reference of the `data` of the checkpoint.
+ pub fn data_ref(&self) -> &D {
+ &self.0.data
}
- /// 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)
+ /// Get the `data` of a the checkpoint.
+ pub fn data(&self) -> D
+ where
+ D: Clone,
+ {
+ self.0.data.clone()
}
/// 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.block_id().height
}
/// Get the block hash of the checkpoint.
pub fn hash(&self) -> BlockHash {
- self.0.block.hash
+ self.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<D>> {
self.0.prev.clone().map(CheckPoint)
}
/// Iterate from this checkpoint in descending height.
- pub fn iter(&self) -> CheckPointIter {
+ pub fn iter(&self) -> CheckPointIter<D> {
self.clone().into_iter()
}
///
/// 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<D>>
where
R: RangeBounds<u32>,
{
self.floor_at(self.height().checked_sub(offset)?)
}
- /// Inserts `block_id` at its height within the chain.
+ /// 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)
+ }
+}
+
+// Methods where `D: ToBlockHash`
+impl<D> CheckPoint<D>
+where
+ D: ToBlockHash + fmt::Debug + Copy,
+{
+ /// Construct a new base [`CheckPoint`] from given `height` and `data` at the front of a linked
+ /// list.
+ pub fn new(height: u32, data: D) -> Self {
+ Self(Arc::new(CPInner {
+ block_id: BlockId {
+ height,
+ hash: data.to_blockhash(),
+ },
+ data,
+ prev: None,
+ }))
+ }
+
+ /// Construct from an iterator of block data.
+ ///
+ /// Returns `Err(None)` if `blocks` doesn't yield any data. If the blocks are not in ascending
+ /// height order, then returns an `Err(..)` containing the last checkpoint that would have been
+ /// extended.
+ pub fn from_blocks(blocks: impl IntoIterator<Item = (u32, D)>) -> Result<Self, Option<Self>> {
+ let mut blocks = blocks.into_iter();
+ let (height, data) = blocks.next().ok_or(None)?;
+ let mut cp = CheckPoint::new(height, data);
+ cp = cp.extend(blocks)?;
+
+ Ok(cp)
+ }
+
+ /// Extends the checkpoint linked list by a iterator containing `height` and `data`.
+ ///
+ /// 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<Item = (u32, D)>) -> Result<Self, Self> {
+ let mut cp = self.clone();
+ for (height, data) in blockdata {
+ cp = cp.push(height, data)?;
+ }
+ Ok(cp)
+ }
+
+ /// Inserts `data` 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
///
/// This panics if called with a genesis block that differs from that of `self`.
#[must_use]
- pub fn insert(self, block_id: BlockId) -> Self {
+ pub fn insert(self, height: u32, data: D) -> Self {
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;
}
assert_ne!(cp.height(), 0, "cannot replace genesis block");
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(core::iter::once((height, data)).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)
+ /// 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, height: u32, data: D) -> 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)
+ }
}
}
/// Iterates over checkpoints backwards.
-pub struct CheckPointIter {
- current: Option<Arc<CPInner>>,
+pub struct CheckPointIter<D> {
+ current: Option<Arc<CPInner<D>>>,
}
-impl Iterator for CheckPointIter {
- type Item = CheckPoint;
+impl<D> Iterator for CheckPointIter<D> {
+ type Item = CheckPoint<D>;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current.clone()?;
}
}
-impl IntoIterator for CheckPoint {
- type Item = CheckPoint;
- type IntoIter = CheckPointIter;
+impl<D> IntoIterator for CheckPoint<D> {
+ type Item = CheckPoint<D>;
+ type IntoIter = CheckPointIter<D>;
fn into_iter(self) -> Self::IntoIter {
CheckPointIter {
collections::{BTreeMap, HashMap, HashSet},
CheckPoint, ConfirmationBlockTime, Indexed,
};
-use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
+use bitcoin::{BlockHash, OutPoint, Script, ScriptBuf, Txid};
type InspectSync<I> = dyn FnMut(SyncItem<I>, SyncProgress) + Send + 'static;
///
/// Construct with [`SyncRequest::builder`].
#[must_use]
-pub struct SyncRequestBuilder<I = ()> {
- inner: SyncRequest<I>,
+pub struct SyncRequestBuilder<I = (), D = BlockHash> {
+ inner: SyncRequest<I, D>,
}
impl SyncRequestBuilder<()> {
}
}
-impl<I> SyncRequestBuilder<I> {
+impl<I, D> SyncRequestBuilder<I, D> {
/// Set the initial chain tip for the sync request.
///
/// This is used to update [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html).
- pub fn chain_tip(mut self, cp: CheckPoint) -> Self {
+ pub fn chain_tip(mut self, cp: CheckPoint<D>) -> Self {
self.inner.chain_tip = Some(cp);
self
}
/// html).
///
/// ```rust
+ /// # use bdk_chain::bitcoin::BlockHash;
/// # use bdk_chain::spk_client::SyncRequest;
/// # use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
/// # use bdk_chain::miniscript::{Descriptor, DescriptorPublicKey};
/// let (newly_revealed_spks, _changeset) = indexer
/// .reveal_to_target("descriptor_a", 21)
/// .expect("keychain must exist");
- /// let _request = SyncRequest::builder()
+ /// let _request: SyncRequest<u32, BlockHash> = SyncRequest::builder()
/// .spks_with_indexes(newly_revealed_spks)
/// .build();
///
/// // keychains. Each spk will be indexed with `(&'static str, u32)` where `&'static str` is
/// // the keychain identifier and `u32` is the derivation index.
/// let all_revealed_spks = indexer.revealed_spks(..);
- /// let _request = SyncRequest::builder()
+ /// let _request: SyncRequest<(&str, u32), BlockHash> = SyncRequest::builder()
/// .spks_with_indexes(all_revealed_spks)
/// .build();
/// # Ok::<_, bdk_chain::keychain_txout::InsertDescriptorError<_>>(())
}
/// Build the [`SyncRequest`].
- pub fn build(self) -> SyncRequest<I> {
+ pub fn build(self) -> SyncRequest<I, D> {
self.inner
}
}
/// ```rust
/// # use bdk_chain::{bitcoin::{hashes::Hash, ScriptBuf}, local_chain::LocalChain};
/// # use bdk_chain::spk_client::SyncRequest;
-/// # let (local_chain, _) = LocalChain::from_genesis_hash(Hash::all_zeros());
+/// # let (local_chain, _) = LocalChain::from_genesis(Hash::all_zeros());
/// # let scripts = [ScriptBuf::default(), ScriptBuf::default()];
/// // Construct a sync request.
/// let sync_request = SyncRequest::builder()
/// .build();
/// ```
#[must_use]
-pub struct SyncRequest<I = ()> {
+pub struct SyncRequest<I = (), D = BlockHash> {
start_time: u64,
- chain_tip: Option<CheckPoint>,
+ chain_tip: Option<CheckPoint<D>>,
spks: VecDeque<(I, ScriptBuf)>,
spks_consumed: usize,
spk_expected_txids: HashMap<ScriptBuf, HashSet<Txid>>,
inspect: Box<InspectSync<I>>,
}
-impl<I> From<SyncRequestBuilder<I>> for SyncRequest<I> {
- fn from(builder: SyncRequestBuilder<I>) -> Self {
+impl<I, D> From<SyncRequestBuilder<I, D>> for SyncRequest<I, D> {
+ fn from(builder: SyncRequestBuilder<I, D>) -> Self {
builder.inner
}
}
-impl<I> SyncRequest<I> {
+impl<I, D> SyncRequest<I, D> {
/// Start building [`SyncRequest`] with a given `start_time`.
///
/// `start_time` specifies the start time of sync. Chain sources can use this value to set
///
/// Use [`SyncRequest::builder`] to use the current timestamp as `start_time` (this requires
/// `feature = "std"`).
- pub fn builder_at(start_time: u64) -> SyncRequestBuilder<I> {
+ pub fn builder_at(start_time: u64) -> SyncRequestBuilder<I, D> {
SyncRequestBuilder {
inner: Self {
start_time,
/// is not available.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
- pub fn builder() -> SyncRequestBuilder<I> {
+ pub fn builder() -> SyncRequestBuilder<I, D> {
let start_time = std::time::UNIX_EPOCH
.elapsed()
.expect("failed to get current timestamp")
}
/// Get the chain tip [`CheckPoint`] of this request (if any).
- pub fn chain_tip(&self) -> Option<CheckPoint> {
+ pub fn chain_tip(&self) -> Option<CheckPoint<D>> {
self.chain_tip.clone()
}
pub fn iter_spks_with_expected_txids(
&mut self,
) -> impl ExactSizeIterator<Item = SpkWithExpectedTxids> + '_ {
- SyncIter::<I, SpkWithExpectedTxids>::new(self)
+ SyncIter::<I, D, SpkWithExpectedTxids>::new(self)
}
/// Iterate over [`Txid`]s contained in this request.
pub fn iter_txids(&mut self) -> impl ExactSizeIterator<Item = Txid> + '_ {
- SyncIter::<I, Txid>::new(self)
+ SyncIter::<I, D, Txid>::new(self)
}
/// Iterate over [`OutPoint`]s contained in this request.
pub fn iter_outpoints(&mut self) -> impl ExactSizeIterator<Item = OutPoint> + '_ {
- SyncIter::<I, OutPoint>::new(self)
+ SyncIter::<I, D, OutPoint>::new(self)
}
fn _call_inspect(&mut self, item: SyncItem<I>) {
/// See also [`SyncRequest`].
#[must_use]
#[derive(Debug)]
-pub struct SyncResponse<A = ConfirmationBlockTime> {
+pub struct SyncResponse<A = ConfirmationBlockTime, D = BlockHash> {
/// Relevant transaction data discovered during the scan.
pub tx_update: crate::TxUpdate<A>,
/// Changes to the chain discovered during the scan.
- pub chain_update: Option<CheckPoint>,
+ pub chain_update: Option<CheckPoint<D>>,
}
-impl<A> Default for SyncResponse<A> {
+impl<A, D> Default for SyncResponse<A, D> {
fn default() -> Self {
Self {
tx_update: Default::default(),
///
/// Construct with [`FullScanRequest::builder`].
#[must_use]
-pub struct FullScanRequestBuilder<K> {
- inner: FullScanRequest<K>,
+pub struct FullScanRequestBuilder<K, D = BlockHash> {
+ inner: FullScanRequest<K, D>,
}
-impl<K: Ord> FullScanRequestBuilder<K> {
+impl<K: Ord, D> FullScanRequestBuilder<K, D> {
/// Set the initial chain tip for the full scan request.
///
/// This is used to update [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html).
- pub fn chain_tip(mut self, tip: CheckPoint) -> Self {
+ pub fn chain_tip(mut self, tip: CheckPoint<D>) -> Self {
self.inner.chain_tip = Some(tip);
self
}
}
/// Build the [`FullScanRequest`].
- pub fn build(self) -> FullScanRequest<K> {
+ pub fn build(self) -> FullScanRequest<K, D> {
self.inner
}
}
/// used scripts is not known. The full scan process also updates the chain from the given
/// [`chain_tip`](FullScanRequestBuilder::chain_tip) (if provided).
#[must_use]
-pub struct FullScanRequest<K> {
+pub struct FullScanRequest<K, D = BlockHash> {
start_time: u64,
- chain_tip: Option<CheckPoint>,
+ chain_tip: Option<CheckPoint<D>>,
spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
inspect: Box<InspectFullScan<K>>,
}
-impl<K> From<FullScanRequestBuilder<K>> for FullScanRequest<K> {
- fn from(builder: FullScanRequestBuilder<K>) -> Self {
+impl<K, D> From<FullScanRequestBuilder<K, D>> for FullScanRequest<K, D> {
+ fn from(builder: FullScanRequestBuilder<K, D>) -> Self {
builder.inner
}
}
-impl<K: Ord + Clone> FullScanRequest<K> {
+impl<K: Ord + Clone, D> FullScanRequest<K, D> {
/// Start building a [`FullScanRequest`] with a given `start_time`.
///
/// `start_time` specifies the start time of sync. Chain sources can use this value to set
///
/// Use [`FullScanRequest::builder`] to use the current timestamp as `start_time` (this
/// requires `feature = "std`).
- pub fn builder_at(start_time: u64) -> FullScanRequestBuilder<K> {
+ pub fn builder_at(start_time: u64) -> FullScanRequestBuilder<K, D> {
FullScanRequestBuilder {
inner: Self {
start_time,
/// "std"` is not available.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
- pub fn builder() -> FullScanRequestBuilder<K> {
+ pub fn builder() -> FullScanRequestBuilder<K, D> {
let start_time = std::time::UNIX_EPOCH
.elapsed()
.expect("failed to get current timestamp")
}
/// Get the chain tip [`CheckPoint`] of this request (if any).
- pub fn chain_tip(&self) -> Option<CheckPoint> {
+ pub fn chain_tip(&self) -> Option<CheckPoint<D>> {
self.chain_tip.clone()
}
/// See also [`FullScanRequest`].
#[must_use]
#[derive(Debug)]
-pub struct FullScanResponse<K, A = ConfirmationBlockTime> {
+pub struct FullScanResponse<K, A = ConfirmationBlockTime, D = BlockHash> {
/// Relevant transaction data discovered during the scan.
pub tx_update: crate::TxUpdate<A>,
/// Last active indices for the corresponding keychains (`K`). An index is active if it had a
/// transaction associated with the script pubkey at that index.
pub last_active_indices: BTreeMap<K, u32>,
/// Changes to the chain discovered during the scan.
- pub chain_update: Option<CheckPoint>,
+ pub chain_update: Option<CheckPoint<D>>,
}
-impl<K, A> Default for FullScanResponse<K, A> {
+impl<K, A, D> Default for FullScanResponse<K, A, D> {
fn default() -> Self {
Self {
tx_update: Default::default(),
}
}
-struct SyncIter<'r, I, Item> {
- request: &'r mut SyncRequest<I>,
+struct SyncIter<'r, I, D, Item> {
+ request: &'r mut SyncRequest<I, D>,
marker: core::marker::PhantomData<Item>,
}
-impl<'r, I, Item> SyncIter<'r, I, Item> {
- fn new(request: &'r mut SyncRequest<I>) -> Self {
+impl<'r, I, D, Item> SyncIter<'r, I, D, Item> {
+ fn new(request: &'r mut SyncRequest<I, D>) -> Self {
Self {
request,
marker: core::marker::PhantomData,
}
}
-impl<'r, I, Item> ExactSizeIterator for SyncIter<'r, I, Item> where SyncIter<'r, I, Item>: Iterator {}
+impl<'r, I, D, Item> ExactSizeIterator for SyncIter<'r, I, D, Item> where
+ SyncIter<'r, I, D, Item>: Iterator
+{
+}
-impl<I> Iterator for SyncIter<'_, I, SpkWithExpectedTxids> {
+impl<I, D> Iterator for SyncIter<'_, I, D, SpkWithExpectedTxids> {
type Item = SpkWithExpectedTxids;
fn next(&mut self) -> Option<Self::Item> {
}
}
-impl<I> Iterator for SyncIter<'_, I, Txid> {
+impl<I, D> Iterator for SyncIter<'_, I, D, Txid> {
type Item = Txid;
fn next(&mut self) -> Option<Self::Item> {
}
}
-impl<I> Iterator for SyncIter<'_, I, OutPoint> {
+impl<I, D> Iterator for SyncIter<'_, I, D, OutPoint> {
type Item = OutPoint;
fn next(&mut self) -> Option<Self::Item> {
-use bdk_core::{BlockId, CheckPoint};
+use bdk_core::CheckPoint;
use bdk_testenv::{block_id, hash};
+use bitcoin::BlockHash;
/// Inserting a block that already exists in the checkpoint chain must always succeed.
#[test]
// Index `i` allows us to test with chains of different length.
// Index `j` allows us to test inserting different block heights.
for i in 0..blocks.len() {
- let cp_chain = CheckPoint::from_block_ids(blocks[..=i].iter().copied())
- .expect("must construct valid chain");
+ let cp_chain = CheckPoint::from_blocks(
+ blocks[..=i]
+ .iter()
+ .copied()
+ .map(|block_id| (block_id.height, block_id.hash)),
+ )
+ .expect("must construct valid chain");
for j in 0..=i {
let block_to_insert = cp_chain
.get(j as u32)
.expect("cp of height must exist")
.block_id();
- let new_cp_chain = cp_chain.clone().insert(block_to_insert);
+ let new_cp_chain = cp_chain
+ .clone()
+ .insert(block_to_insert.height, block_to_insert.hash);
assert_eq!(
new_cp_chain, cp_chain,
// this could have caused a stack overflow due to drop recursion in Arc.
// We test that a long linked list can clean itself up without blowing
// out the stack.
- let mut cp = CheckPoint::new(BlockId {
- height: 0,
- hash: hash!("g"),
- });
+ let mut cp = CheckPoint::new(0, hash!("g"));
let end = 10_000;
for height in 1u32..end {
- let hash = bitcoin::hashes::Hash::hash(height.to_be_bytes().as_slice());
- let block = BlockId { height, hash };
- cp = cp.push(block).unwrap();
+ let hash: BlockHash = bitcoin::hashes::Hash::hash(height.to_be_bytes().as_slice());
+ cp = cp.push(height, hash).unwrap();
}
assert_eq!(cp.iter().count() as u32, end);
}
);
// Setup receiver.
- let genesis_cp = CheckPoint::new(bdk_core::BlockId {
- height: 0,
- hash: env.bitcoind.client.get_block_hash(0).unwrap(),
- });
+ let genesis_cp = CheckPoint::new(0, env.bitcoind.client.get_block_hash(0).unwrap());
{
let electrum_client =
/// fetched to construct checkpoint updates with the proper [`BlockHash`] in case of re-org.
fn fetch_tip_and_latest_blocks(
client: &impl ElectrumApi,
- prev_tip: CheckPoint,
-) -> Result<(CheckPoint, BTreeMap<u32, BlockHash>), Error> {
+ prev_tip: CheckPoint<BlockHash>,
+) -> Result<(CheckPoint<BlockHash>, BTreeMap<u32, BlockHash>), Error> {
let HeaderNotification { height, .. } = client.block_headers_subscribe()?;
let new_tip_height = height as u32;
// Find the "point of agreement" (if any).
let agreement_cp = {
- let mut agreement_cp = Option::<CheckPoint>::None;
+ let mut agreement_cp = Option::<CheckPoint<BlockHash>>::None;
for cp in prev_tip.iter() {
let cp_block = cp.block_id();
let hash = match new_blocks.get(&cp_block.height) {
let agreement_height = agreement_cp.height();
move |(height, _)| **height > agreement_height
})
- .map(|(&height, &hash)| BlockId { height, hash });
+ .map(|(&height, &hash)| (height, hash));
let new_tip = agreement_cp
.extend(extension)
.expect("extension heights already checked to be greater than agreement height");
// Add a corresponding checkpoint per anchor height if it does not yet exist. Checkpoints should not
// surpass `latest_blocks`.
fn chain_update(
- mut tip: CheckPoint,
+ mut tip: CheckPoint<BlockHash>,
latest_blocks: &BTreeMap<u32, BlockHash>,
anchors: impl Iterator<Item = (ConfirmationBlockTime, Txid)>,
-) -> Result<CheckPoint, Error> {
+) -> Result<CheckPoint<BlockHash>, Error> {
for (anchor, _txid) in anchors {
let height = anchor.block_id.height;
Some(&hash) => hash,
None => anchor.block_id.hash,
};
- tip = tip.insert(BlockId { hash, height });
+ tip = tip.insert(height, hash);
}
}
Ok(tip)
use crate::{bdk_electrum_client::TxUpdate, electrum_client::ElectrumApi, BdkElectrumClient};
use bdk_chain::bitcoin::Amount;
use bdk_chain::bitcoin::{constants, Network, OutPoint, ScriptBuf, Transaction, TxIn};
- use bdk_chain::{BlockId, CheckPoint};
+ use bdk_chain::CheckPoint;
use bdk_core::{collections::BTreeMap, spk_client::SyncRequest};
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, utils::new_tx, TestEnv};
use core::time::Duration;
let bogus_spks: Vec<ScriptBuf> = Vec::new();
let bogus_genesis = constants::genesis_block(Network::Testnet).block_hash();
- let bogus_cp = CheckPoint::new(BlockId {
- height: 0,
- hash: bogus_genesis,
- });
+ let bogus_cp = CheckPoint::new(0, bogus_genesis);
let req = SyncRequest::builder()
.chain_tip(bogus_cp)
let client = BdkElectrumClient::new(electrum_client);
let mut graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new(SpkTxOutIndex::<()>::default());
- let (chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
// Get receiving address.
let receiver_spk = get_test_spk();
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
// Setup receiver.
- let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (mut recv_chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
// Setup receiver.
- let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (mut recv_chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
// Setup receiver.
- let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (mut recv_chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
// Setup receiver.
- let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (mut recv_chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
async fn chain_update<S: Sleeper>(
client: &esplora_client::AsyncClient<S>,
latest_blocks: &BTreeMap<u32, BlockHash>,
- local_tip: &CheckPoint,
+ local_tip: &CheckPoint<BlockHash>,
anchors: &BTreeSet<(ConfirmationBlockTime, Txid)>,
-) -> Result<CheckPoint, Error> {
+) -> Result<CheckPoint<BlockHash>, Error> {
let mut point_of_agreement = None;
let mut local_cp_hash = local_tip.hash();
let mut conflicts = vec![];
};
tip = tip
- .extend(conflicts.into_iter().rev())
+ .extend(conflicts.into_iter().rev().map(|b| (b.height, b.hash)))
.expect("evicted are in order");
for (anchor, _txid) in anchors {
Some(hash) => hash,
None => continue,
};
- tip = tip.insert(BlockId { height, hash });
+ tip = tip.insert(height, hash);
}
}
// insert the most recent blocks at the tip to make sure we update the tip and make the update
// robust.
for (&height, &hash) in latest_blocks.iter() {
- tip = tip.insert(BlockId { height, hash });
+ tip = tip.insert(height, hash);
}
Ok(tip)
let genesis_hash =
bitcoin::constants::genesis_block(bitcoin::Network::Testnet4).block_hash();
- let cp = bdk_chain::CheckPoint::new(BlockId {
- height: 0,
- hash: genesis_hash,
- });
+ let cp = bdk_chain::CheckPoint::new(0, genesis_hash);
let anchors = BTreeSet::new();
let res = chain_update(&client, &latest_blocks, &cp, &anchors).await;
// craft initial `local_chain`
let local_chain = {
- let (mut chain, _) = LocalChain::from_genesis_hash(env.genesis_hash()?);
+ let (mut chain, _) = LocalChain::from_genesis(env.genesis_hash()?);
// force `chain_update_blocking` to add all checkpoints in `t.initial_cps`
let anchors = t
.initial_cps
fn chain_update(
client: &esplora_client::BlockingClient,
latest_blocks: &BTreeMap<u32, BlockHash>,
- local_tip: &CheckPoint,
+ local_tip: &CheckPoint<BlockHash>,
anchors: &BTreeSet<(ConfirmationBlockTime, Txid)>,
-) -> Result<CheckPoint, Error> {
+) -> Result<CheckPoint<BlockHash>, Error> {
let mut point_of_agreement = None;
let mut local_cp_hash = local_tip.hash();
let mut conflicts = vec![];
};
tip = tip
- .extend(conflicts.into_iter().rev())
+ .extend(conflicts.into_iter().rev().map(|b| (b.height, b.hash)))
.expect("evicted are in order");
for (anchor, _) in anchors {
Some(hash) => hash,
None => continue,
};
- tip = tip.insert(BlockId { height, hash });
+ tip = tip.insert(height, hash);
}
}
// insert the most recent blocks at the tip to make sure we update the tip and make the update
// robust.
for (&height, &hash) in latest_blocks.iter() {
- tip = tip.insert(BlockId { height, hash });
+ tip = tip.insert(height, hash);
}
Ok(tip)
let genesis_hash =
bitcoin::constants::genesis_block(bitcoin::Network::Testnet4).block_hash();
- let cp = bdk_chain::CheckPoint::new(BlockId {
- height: 0,
- hash: genesis_hash,
- });
+ let cp = bdk_chain::CheckPoint::new(0, genesis_hash);
let anchors = BTreeSet::new();
let res = chain_update(&client, &latest_blocks, &cp, &anchors);
// craft initial `local_chain`
let local_chain = {
- let (mut chain, _) = LocalChain::from_genesis_hash(env.genesis_hash()?);
+ let (mut chain, _) = LocalChain::from_genesis(env.genesis_hash()?);
// force `chain_update_blocking` to add all checkpoints in `t.initial_cps`
let anchors = t
.initial_cps
let client = Builder::new(base_url.as_str()).build_async()?;
let mut graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new(SpkTxOutIndex::<()>::default());
- let (chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
// Get receiving address.
let receiver_spk = common::get_test_spk();
let client = Builder::new(base_url.as_str()).build_blocking();
let mut graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new(SpkTxOutIndex::<()>::default());
- let (chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
+ let (chain, _) = LocalChain::from_genesis(env.bitcoind.client.get_block_hash(0)?);
// Get receiving address.
let receiver_spk = common::get_test_spk();
ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid,
},
local_chain::CheckPoint,
- BlockId,
};
use bitcoincore_rpc::{
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
}
/// Create a checkpoint linked list of all the blocks in the chain.
- pub fn make_checkpoint_tip(&self) -> CheckPoint {
- CheckPoint::from_block_ids((0_u32..).map_while(|height| {
+ pub fn make_checkpoint_tip(&self) -> CheckPoint<BlockHash> {
+ CheckPoint::from_blocks((0_u32..).map_while(|height| {
self.bitcoind
.client
.get_block_hash(height as u64)
.ok()
- .map(|hash| BlockId { height, hash })
+ .map(|hash| (height, hash))
}))
.expect("must craft tip")
}
let chain = Mutex::new({
let (mut chain, _) =
- LocalChain::from_genesis_hash(constants::genesis_block(network).block_hash());
+ LocalChain::from_genesis(constants::genesis_block(network).block_hash());
chain.apply_changeset(&changeset.local_chain)?;
chain
});
// create new
let (_, chain_changeset) =
- LocalChain::from_genesis_hash(constants::genesis_block(network).block_hash());
+ LocalChain::from_genesis(constants::genesis_block(network).block_hash());
changeset.network = Some(network);
changeset.local_chain = chain_changeset;
let mut db = Store::<ChangeSet>::create(db_magic, db_path)?;