feat(chain)!: rm local_chain::Update

The intention is to remove the `Update::introduce_older_blocks`
parameter and update the local chain directly with `CheckPoint`.

This simplifies the API and there is a way to do this efficiently.
This commit is contained in:
志宇 2024-04-17 10:02:12 +08:00
parent 1269b0610e
commit 77d35954c1
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
12 changed files with 39 additions and 122 deletions

View File

@ -107,7 +107,7 @@ pub struct Update {
/// Update for the wallet's internal [`LocalChain`]. /// Update for the wallet's internal [`LocalChain`].
/// ///
/// [`LocalChain`]: local_chain::LocalChain /// [`LocalChain`]: local_chain::LocalChain
pub chain: Option<local_chain::Update>, pub chain: Option<CheckPoint>,
} }
/// The changes made to a wallet by applying an [`Update`]. /// The changes made to a wallet by applying an [`Update`].

View File

@ -4,7 +4,7 @@ use bdk_bitcoind_rpc::Emitter;
use bdk_chain::{ use bdk_chain::{
bitcoin::{Address, Amount, Txid}, bitcoin::{Address, Amount, Txid},
keychain::Balance, keychain::Balance,
local_chain::{self, CheckPoint, LocalChain}, local_chain::{CheckPoint, LocalChain},
Append, BlockId, IndexedTxGraph, SpkTxOutIndex, Append, BlockId, IndexedTxGraph, SpkTxOutIndex,
}; };
use bdk_testenv::TestEnv; use bdk_testenv::TestEnv;
@ -47,10 +47,7 @@ pub fn test_sync_local_chain() -> anyhow::Result<()> {
); );
assert_eq!( assert_eq!(
local_chain.apply_update(local_chain::Update { local_chain.apply_update(emission.checkpoint,)?,
tip: emission.checkpoint,
introduce_older_blocks: false,
})?,
BTreeMap::from([(height, Some(hash))]), BTreeMap::from([(height, Some(hash))]),
"chain update changeset is unexpected", "chain update changeset is unexpected",
); );
@ -95,10 +92,7 @@ pub fn test_sync_local_chain() -> anyhow::Result<()> {
); );
assert_eq!( assert_eq!(
local_chain.apply_update(local_chain::Update { local_chain.apply_update(emission.checkpoint,)?,
tip: emission.checkpoint,
introduce_older_blocks: false,
})?,
if exp_height == exp_hashes.len() - reorged_blocks.len() { if exp_height == exp_hashes.len() - reorged_blocks.len() {
core::iter::once((height, Some(hash))) core::iter::once((height, Some(hash)))
.chain((height + 1..exp_hashes.len() as u32).map(|h| (h, None))) .chain((height + 1..exp_hashes.len() as u32).map(|h| (h, None)))
@ -168,10 +162,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
while let Some(emission) = emitter.next_block()? { while let Some(emission) = emitter.next_block()? {
let height = emission.block_height(); let height = emission.block_height();
let _ = chain.apply_update(local_chain::Update { let _ = chain.apply_update(emission.checkpoint)?;
tip: emission.checkpoint,
introduce_older_blocks: false,
})?;
let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height); let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height);
assert!(indexed_additions.is_empty()); assert!(indexed_additions.is_empty());
} }
@ -232,10 +223,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
{ {
let emission = emitter.next_block()?.expect("must get mined block"); let emission = emitter.next_block()?.expect("must get mined block");
let height = emission.block_height(); let height = emission.block_height();
let _ = chain.apply_update(local_chain::Update { let _ = chain.apply_update(emission.checkpoint)?;
tip: emission.checkpoint,
introduce_older_blocks: false,
})?;
let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height); let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height);
assert!(indexed_additions.graph.txs.is_empty()); assert!(indexed_additions.graph.txs.is_empty());
assert!(indexed_additions.graph.txouts.is_empty()); assert!(indexed_additions.graph.txouts.is_empty());
@ -294,8 +282,7 @@ fn process_block(
block: Block, block: Block,
block_height: u32, block_height: u32,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
recv_chain recv_chain.apply_update(CheckPoint::from_header(&block.header, block_height))?;
.apply_update(CheckPoint::from_header(&block.header, block_height).into_update(false))?;
let _ = recv_graph.apply_block(block, block_height); let _ = recv_graph.apply_block(block, block_height);
Ok(()) Ok(())
} }

View File

@ -96,16 +96,6 @@ impl CheckPoint {
.expect("must construct checkpoint") .expect("must construct checkpoint")
} }
/// Convenience method to convert the [`CheckPoint`] into an [`Update`].
///
/// For more information, refer to [`Update`].
pub fn into_update(self, introduce_older_blocks: bool) -> Update {
Update {
tip: self,
introduce_older_blocks,
}
}
/// Puts another checkpoint onto the linked list representing the blockchain. /// 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 /// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
@ -251,31 +241,6 @@ impl IntoIterator for CheckPoint {
} }
} }
/// Used to update [`LocalChain`].
///
/// This is used as input for [`LocalChain::apply_update`]. It contains the update's chain `tip` and
/// a flag `introduce_older_blocks` which signals whether this update intends to introduce missing
/// blocks to the original chain.
///
/// Block-by-block syncing mechanisms would typically create updates that builds upon the previous
/// tip. In this case, `introduce_older_blocks` would be `false`.
///
/// Script-pubkey based syncing mechanisms may not introduce transactions in a chronological order
/// so some updates require introducing older blocks (to anchor older transactions). For
/// script-pubkey based syncing, `introduce_older_blocks` would typically be `true`.
#[derive(Debug, Clone, PartialEq)]
pub struct Update {
/// The update chain's new tip.
pub tip: CheckPoint,
/// Whether the update allows for introducing older blocks.
///
/// Refer to [struct-level documentation] for more.
///
/// [struct-level documentation]: Update
pub introduce_older_blocks: bool,
}
/// This is a local implementation of [`ChainOracle`]. /// This is a local implementation of [`ChainOracle`].
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LocalChain { pub struct LocalChain {
@ -390,23 +355,16 @@ impl LocalChain {
/// the existing chain and invalidate the block after it (if it exists) by including a block at /// 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. /// the same height but with a different hash to explicitly exclude it as a connection point.
/// ///
/// Additionally, an empty chain can be updated with any chain, and a chain with a single block /// Additionally, a chain with a single block can have it's block invalidated by an update
/// can have it's block invalidated by an update chain with a block at the same height but /// chain with a block at the same height but different hash.
/// different hash.
/// ///
/// # Errors /// # Errors
/// ///
/// An error will occur if the update does not correctly connect with `self`. /// An error will occur if the update does not correctly connect with `self`.
/// ///
/// Refer to [`Update`] for more about the update struct.
///
/// [module-level documentation]: crate::local_chain /// [module-level documentation]: crate::local_chain
pub fn apply_update(&mut self, update: Update) -> Result<ChangeSet, CannotConnectError> { pub fn apply_update(&mut self, update: CheckPoint) -> Result<ChangeSet, CannotConnectError> {
let changeset = merge_chains( let changeset = merge_chains(self.tip.clone(), update.clone())?;
self.tip.clone(),
update.tip.clone(),
update.introduce_older_blocks,
)?;
// `._check_index_is_consistent_with_tip` and `._check_changeset_is_applied` is called in // `._check_index_is_consistent_with_tip` and `._check_changeset_is_applied` is called in
// `.apply_changeset` // `.apply_changeset`
self.apply_changeset(&changeset) self.apply_changeset(&changeset)
@ -464,11 +422,8 @@ impl LocalChain {
conn => Some(conn), conn => Some(conn),
}; };
let update = Update { let update = CheckPoint::from_block_ids([conn, prev, Some(this)].into_iter().flatten())
tip: CheckPoint::from_block_ids([conn, prev, Some(this)].into_iter().flatten()) .expect("block ids must be in order");
.expect("block ids must be in order"),
introduce_older_blocks: false,
};
self.apply_update(update) self.apply_update(update)
.map_err(ApplyHeaderError::CannotConnect) .map_err(ApplyHeaderError::CannotConnect)
@ -769,7 +724,6 @@ impl std::error::Error for ApplyHeaderError {}
fn merge_chains( fn merge_chains(
original_tip: CheckPoint, original_tip: CheckPoint,
update_tip: CheckPoint, update_tip: CheckPoint,
introduce_older_blocks: bool,
) -> Result<ChangeSet, CannotConnectError> { ) -> Result<ChangeSet, CannotConnectError> {
let mut changeset = ChangeSet::default(); let mut changeset = ChangeSet::default();
let mut orig = original_tip.into_iter(); let mut orig = original_tip.into_iter();
@ -829,11 +783,9 @@ fn merge_chains(
} }
point_of_agreement_found = true; point_of_agreement_found = true;
prev_orig_was_invalidated = false; prev_orig_was_invalidated = false;
// OPTIMIZATION 1 -- If we know that older blocks cannot be introduced without
// invalidation, we can break after finding the point of agreement.
// OPTIMIZATION 2 -- if we have the same underlying pointer at this point, we // OPTIMIZATION 2 -- if we have the same underlying pointer at this point, we
// can guarantee that no older blocks are introduced. // can guarantee that no older blocks are introduced.
if !introduce_older_blocks || Arc::as_ptr(&o.0) == Arc::as_ptr(&u.0) { if Arc::as_ptr(&o.0) == Arc::as_ptr(&u.0) {
return Ok(changeset); return Ok(changeset);
} }
} else { } else {

View File

@ -32,12 +32,9 @@ macro_rules! local_chain {
macro_rules! chain_update { macro_rules! chain_update {
[ $(($height:expr, $hash:expr)), * ] => {{ [ $(($height:expr, $hash:expr)), * ] => {{
#[allow(unused_mut)] #[allow(unused_mut)]
bdk_chain::local_chain::Update { bdk_chain::local_chain::LocalChain::from_blocks([$(($height, $hash).into()),*].into_iter().collect())
tip: bdk_chain::local_chain::LocalChain::from_blocks([$(($height, $hash).into()),*].into_iter().collect())
.expect("chain must have genesis block") .expect("chain must have genesis block")
.tip(), .tip()
introduce_older_blocks: true,
}
}}; }};
} }

View File

@ -3,7 +3,7 @@ use std::ops::{Bound, RangeBounds};
use bdk_chain::{ use bdk_chain::{
local_chain::{ local_chain::{
AlterCheckPointError, ApplyHeaderError, CannotConnectError, ChangeSet, CheckPoint, AlterCheckPointError, ApplyHeaderError, CannotConnectError, ChangeSet, CheckPoint,
LocalChain, MissingGenesisError, Update, LocalChain, MissingGenesisError,
}, },
BlockId, BlockId,
}; };
@ -17,7 +17,7 @@ mod common;
struct TestLocalChain<'a> { struct TestLocalChain<'a> {
name: &'static str, name: &'static str,
chain: LocalChain, chain: LocalChain,
update: Update, update: CheckPoint,
exp: ExpectedResult<'a>, exp: ExpectedResult<'a>,
} }

View File

@ -1,6 +1,6 @@
use bdk_chain::{ use bdk_chain::{
bitcoin::{OutPoint, ScriptBuf, Transaction, Txid}, bitcoin::{OutPoint, ScriptBuf, Transaction, Txid},
local_chain::{self, CheckPoint}, local_chain::CheckPoint,
tx_graph::{self, TxGraph}, tx_graph::{self, TxGraph},
Anchor, BlockId, ConfirmationHeightAnchor, ConfirmationTimeHeightAnchor, Anchor, BlockId, ConfirmationHeightAnchor, ConfirmationTimeHeightAnchor,
}; };
@ -124,7 +124,7 @@ impl RelevantTxids {
#[derive(Debug)] #[derive(Debug)]
pub struct ElectrumUpdate { pub struct ElectrumUpdate {
/// Chain update /// Chain update
pub chain_update: local_chain::Update, pub chain_update: CheckPoint,
/// Transaction updates from electrum /// Transaction updates from electrum
pub relevant_txids: RelevantTxids, pub relevant_txids: RelevantTxids,
} }
@ -232,10 +232,7 @@ impl<A: ElectrumApi> ElectrumExt for A {
continue; // reorg continue; // reorg
} }
let chain_update = local_chain::Update { let chain_update = tip;
tip,
introduce_older_blocks: true,
};
let keychain_update = request_spks let keychain_update = request_spks
.into_keys() .into_keys()

View File

@ -5,7 +5,7 @@ use bdk_chain::Anchor;
use bdk_chain::{ use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid}, bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
collections::BTreeMap, collections::BTreeMap,
local_chain::{self, CheckPoint}, local_chain::CheckPoint,
BlockId, ConfirmationTimeHeightAnchor, TxGraph, BlockId, ConfirmationTimeHeightAnchor, TxGraph,
}; };
use esplora_client::{Amount, TxStatus}; use esplora_client::{Amount, TxStatus};
@ -47,7 +47,7 @@ pub trait EsploraAsyncExt {
/// ///
/// A `stop_gap` of 0 will be treated as a `stop_gap` of 1. /// A `stop_gap` of 0 will be treated as a `stop_gap` of 1.
/// ///
/// [`LocalChain::tip`]: local_chain::LocalChain::tip /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
async fn full_scan<K: Ord + Clone + Send>( async fn full_scan<K: Ord + Clone + Send>(
&self, &self,
local_tip: CheckPoint, local_tip: CheckPoint,
@ -71,7 +71,7 @@ pub trait EsploraAsyncExt {
/// If the scripts to sync are unknown, such as when restoring or importing a keychain that /// If the scripts to sync are unknown, such as when restoring or importing a keychain that
/// may include scripts that have been used, use [`full_scan`] with the keychain. /// may include scripts that have been used, use [`full_scan`] with the keychain.
/// ///
/// [`LocalChain::tip`]: local_chain::LocalChain::tip /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
/// [`full_scan`]: EsploraAsyncExt::full_scan /// [`full_scan`]: EsploraAsyncExt::full_scan
async fn sync( async fn sync(
&self, &self,
@ -180,7 +180,7 @@ async fn chain_update<A: Anchor>(
latest_blocks: &BTreeMap<u32, BlockHash>, latest_blocks: &BTreeMap<u32, BlockHash>,
local_tip: &CheckPoint, local_tip: &CheckPoint,
anchors: &BTreeSet<(A, Txid)>, anchors: &BTreeSet<(A, Txid)>,
) -> Result<local_chain::Update, Error> { ) -> Result<CheckPoint, Error> {
let mut point_of_agreement = None; let mut point_of_agreement = None;
let mut conflicts = vec![]; let mut conflicts = vec![];
for local_cp in local_tip.iter() { for local_cp in local_tip.iter() {
@ -225,10 +225,7 @@ async fn chain_update<A: Anchor>(
tip = tip.insert(BlockId { height, hash }); tip = tip.insert(BlockId { height, hash });
} }
Ok(local_chain::Update { Ok(tip)
tip,
introduce_older_blocks: true,
})
} }
/// This performs a full scan to get an update for the [`TxGraph`] and /// This performs a full scan to get an update for the [`TxGraph`] and

View File

@ -6,7 +6,7 @@ use bdk_chain::collections::BTreeMap;
use bdk_chain::Anchor; use bdk_chain::Anchor;
use bdk_chain::{ use bdk_chain::{
bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid}, bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
local_chain::{self, CheckPoint}, local_chain::CheckPoint,
BlockId, ConfirmationTimeHeightAnchor, TxGraph, BlockId, ConfirmationTimeHeightAnchor, TxGraph,
}; };
use esplora_client::TxStatus; use esplora_client::TxStatus;
@ -47,7 +47,7 @@ pub trait EsploraExt {
/// ///
/// A `stop_gap` of 0 will be treated as a `stop_gap` of 1. /// A `stop_gap` of 0 will be treated as a `stop_gap` of 1.
/// ///
/// [`LocalChain::tip`]: local_chain::LocalChain::tip /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
fn full_scan<K: Ord + Clone>( fn full_scan<K: Ord + Clone>(
&self, &self,
local_tip: CheckPoint, local_tip: CheckPoint,
@ -68,7 +68,7 @@ pub trait EsploraExt {
/// If the scripts to sync are unknown, such as when restoring or importing a keychain that /// If the scripts to sync are unknown, such as when restoring or importing a keychain that
/// may include scripts that have been used, use [`full_scan`] with the keychain. /// may include scripts that have been used, use [`full_scan`] with the keychain.
/// ///
/// [`LocalChain::tip`]: local_chain::LocalChain::tip /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
/// [`full_scan`]: EsploraExt::full_scan /// [`full_scan`]: EsploraExt::full_scan
fn sync( fn sync(
&self, &self,
@ -178,7 +178,7 @@ fn chain_update<A: Anchor>(
latest_blocks: &BTreeMap<u32, BlockHash>, latest_blocks: &BTreeMap<u32, BlockHash>,
local_tip: &CheckPoint, local_tip: &CheckPoint,
anchors: &BTreeSet<(A, Txid)>, anchors: &BTreeSet<(A, Txid)>,
) -> Result<local_chain::Update, Error> { ) -> Result<CheckPoint, Error> {
let mut point_of_agreement = None; let mut point_of_agreement = None;
let mut conflicts = vec![]; let mut conflicts = vec![];
for local_cp in local_tip.iter() { for local_cp in local_tip.iter() {
@ -223,10 +223,7 @@ fn chain_update<A: Anchor>(
tip = tip.insert(BlockId { height, hash }); tip = tip.insert(BlockId { height, hash });
} }
Ok(local_chain::Update { Ok(tip)
tip,
introduce_older_blocks: true,
})
} }
/// This performs a full scan to get an update for the [`TxGraph`] and /// This performs a full scan to get an update for the [`TxGraph`] and
@ -752,7 +749,6 @@ mod test {
)?; )?;
let update_blocks = chain_update let update_blocks = chain_update
.tip
.iter() .iter()
.map(|cp| cp.block_id()) .map(|cp| cp.block_id())
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();

View File

@ -18,7 +18,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use bdk_chain::{local_chain, BlockId, ConfirmationTimeHeightAnchor, TxGraph}; use bdk_chain::{local_chain::CheckPoint, BlockId, ConfirmationTimeHeightAnchor, TxGraph};
use esplora_client::TxStatus; use esplora_client::TxStatus;
pub use esplora_client; pub use esplora_client;
@ -53,8 +53,8 @@ fn anchor_from_status(status: &TxStatus) -> Option<ConfirmationTimeHeightAnchor>
/// Update returns from a full scan. /// Update returns from a full scan.
pub struct FullScanUpdate<K> { pub struct FullScanUpdate<K> {
/// The update to apply to the receiving [`LocalChain`](local_chain::LocalChain). /// The update to apply to the receiving [`LocalChain`](bdk_chain::local_chain::LocalChain).
pub local_chain: local_chain::Update, pub local_chain: CheckPoint,
/// The update to apply to the receiving [`TxGraph`]. /// The update to apply to the receiving [`TxGraph`].
pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>, pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>,
/// Last active indices for the corresponding keychains (`K`). /// Last active indices for the corresponding keychains (`K`).
@ -63,8 +63,8 @@ pub struct FullScanUpdate<K> {
/// Update returned from a sync. /// Update returned from a sync.
pub struct SyncUpdate { pub struct SyncUpdate {
/// The update to apply to the receiving [`LocalChain`](local_chain::LocalChain). /// The update to apply to the receiving [`LocalChain`](bdk_chain::local_chain::LocalChain).
pub local_chain: local_chain::Update, pub local_chain: CheckPoint,
/// The update to apply to the receiving [`TxGraph`]. /// The update to apply to the receiving [`TxGraph`].
pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>, pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>,
} }

View File

@ -69,7 +69,6 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
{ {
let update_cps = sync_update let update_cps = sync_update
.local_chain .local_chain
.tip
.iter() .iter()
.map(|cp| cp.block_id()) .map(|cp| cp.block_id())
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();

View File

@ -67,7 +67,6 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
{ {
let update_cps = sync_update let update_cps = sync_update
.local_chain .local_chain
.tip
.iter() .iter()
.map(|cp| cp.block_id()) .map(|cp| cp.block_id())
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();

View File

@ -188,10 +188,7 @@ fn main() -> anyhow::Result<()> {
let mut db = db.lock().unwrap(); let mut db = db.lock().unwrap();
let chain_changeset = chain let chain_changeset = chain
.apply_update(local_chain::Update { .apply_update(emission.checkpoint)
tip: emission.checkpoint,
introduce_older_blocks: false,
})
.expect("must always apply as we receive blocks in order from emitter"); .expect("must always apply as we receive blocks in order from emitter");
let graph_changeset = graph.apply_block_relevant(&emission.block, height); let graph_changeset = graph.apply_block_relevant(&emission.block, height);
db.stage((chain_changeset, graph_changeset)); db.stage((chain_changeset, graph_changeset));
@ -301,12 +298,8 @@ fn main() -> anyhow::Result<()> {
let changeset = match emission { let changeset = match emission {
Emission::Block(block_emission) => { Emission::Block(block_emission) => {
let height = block_emission.block_height(); let height = block_emission.block_height();
let chain_update = local_chain::Update {
tip: block_emission.checkpoint,
introduce_older_blocks: false,
};
let chain_changeset = chain let chain_changeset = chain
.apply_update(chain_update) .apply_update(block_emission.checkpoint)
.expect("must always apply as we receive blocks in order from emitter"); .expect("must always apply as we receive blocks in order from emitter");
let graph_changeset = let graph_changeset =
graph.apply_block_relevant(&block_emission.block, height); graph.apply_block_relevant(&block_emission.block, height);