From 6f7626ad0bb6ca7e45428af11dbf930d746d9cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 15:08:55 +0000 Subject: [PATCH 1/9] feat!: introduce `bdk_core` This is an initial version with `chain_data` types ported over. Types ported over include `BlockId`, `ConfirmationBlockTime`. The impls for `Anchor` and `AnchorFromBlockPosition` of these types are moved to where the traits are defined. --- Cargo.toml | 1 + crates/chain/Cargo.toml | 9 ++- crates/chain/src/chain_data.rs | 93 ++------------------------- crates/chain/src/example_utils.rs | 3 +- crates/chain/src/lib.rs | 60 ++++++++--------- crates/chain/src/rusqlite_impl.rs | 12 ++-- crates/chain/src/spk_client.rs | 3 +- crates/chain/src/tx_data_traits.rs | 36 ++++++++++- crates/chain/src/tx_graph.rs | 6 +- crates/chain/tests/test_tx_graph.rs | 6 +- crates/core/Cargo.toml | 21 ++++++ crates/core/src/chain_data.rs | 51 +++++++++++++++ crates/core/src/lib.rs | 58 +++++++++++++++++ example-crates/example_cli/src/lib.rs | 2 +- 14 files changed, 217 insertions(+), 144 deletions(-) create mode 100644 crates/core/Cargo.toml create mode 100644 crates/core/src/chain_data.rs create mode 100644 crates/core/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 1c29bbaf5..ef6d55e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "crates/wallet", "crates/chain", + "crates/core", "crates/file_store", "crates/electrum", "crates/esplora", diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index 7261bdfa2..42a77441f 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -14,10 +14,8 @@ readme = "README.md" [dependencies] bitcoin = { version = "0.32.0", default-features = false } +bdk_core = { path = "../core", version = "0.1", default-features = false } serde = { version = "1", optional = true, features = ["derive", "rc"] } - -# Use hashbrown as a feature flag to have HashSet and HashMap from it. -hashbrown = { version = "0.9.1", optional = true, features = ["serde"] } miniscript = { version = "12.0.0", optional = true, default-features = false } # Feature dependencies @@ -30,6 +28,7 @@ proptest = "1.2.0" [features] default = ["std", "miniscript"] -std = ["bitcoin/std", "miniscript?/std"] -serde = ["dep:serde", "bitcoin/serde", "miniscript?/serde"] +std = ["bitcoin/std", "miniscript?/std", "bdk_core/std"] +serde = ["dep:serde", "bitcoin/serde", "miniscript?/serde", "bdk_core/serde"] +hashbrown = ["bdk_core/hashbrown"] rusqlite = ["std", "dep:rusqlite", "serde", "serde_json"] diff --git a/crates/chain/src/chain_data.rs b/crates/chain/src/chain_data.rs index 8ce6e31a4..ce6076c51 100644 --- a/crates/chain/src/chain_data.rs +++ b/crates/chain/src/chain_data.rs @@ -1,6 +1,7 @@ -use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid}; +use crate::ConfirmationBlockTime; +use bitcoin::{OutPoint, TxOut, Txid}; -use crate::{Anchor, AnchorFromBlockPosition, COINBASE_MATURITY}; +use crate::{Anchor, COINBASE_MATURITY}; /// Represents the observed position of some chain data. /// @@ -82,92 +83,6 @@ impl From> for ConfirmationTime { } } -/// A reference to a block in the canonical chain. -/// -/// `BlockId` implements [`Anchor`]. When a transaction is anchored to `BlockId`, the confirmation -/// block and anchor block are the same block. -#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct BlockId { - /// The height of the block. - pub height: u32, - /// The hash of the block. - pub hash: BlockHash, -} - -impl Anchor for BlockId { - fn anchor_block(&self) -> Self { - *self - } -} - -impl AnchorFromBlockPosition for BlockId { - fn from_block_position(_block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self { - block_id - } -} - -impl Default for BlockId { - fn default() -> Self { - Self { - height: Default::default(), - hash: BlockHash::all_zeros(), - } - } -} - -impl From<(u32, BlockHash)> for BlockId { - fn from((height, hash): (u32, BlockHash)) -> Self { - Self { height, hash } - } -} - -impl From for (u32, BlockHash) { - fn from(block_id: BlockId) -> Self { - (block_id.height, block_id.hash) - } -} - -impl From<(&u32, &BlockHash)> for BlockId { - fn from((height, hash): (&u32, &BlockHash)) -> Self { - Self { - height: *height, - hash: *hash, - } - } -} - -/// An [`Anchor`] implementation that also records the exact confirmation time of the transaction. -/// -/// Refer to [`Anchor`] for more details. -#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct ConfirmationBlockTime { - /// The anchor block. - pub block_id: BlockId, - /// The confirmation time of the transaction being anchored. - pub confirmation_time: u64, -} - -impl Anchor for ConfirmationBlockTime { - fn anchor_block(&self) -> BlockId { - self.block_id - } - - fn confirmation_height_upper_bound(&self) -> u32 { - self.block_id.height - } -} - -impl AnchorFromBlockPosition for ConfirmationBlockTime { - fn from_block_position(block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self { - Self { - block_id, - confirmation_time: block.header.time as _, - } - } -} - /// A `TxOut` with as much data as we can retrieve about it #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct FullTxOut { @@ -244,6 +159,8 @@ impl FullTxOut { #[cfg(test)] mod test { + use crate::BlockId; + use super::*; #[test] diff --git a/crates/chain/src/example_utils.rs b/crates/chain/src/example_utils.rs index 8077e2118..c71b6cfef 100644 --- a/crates/chain/src/example_utils.rs +++ b/crates/chain/src/example_utils.rs @@ -1,4 +1,5 @@ #![allow(unused)] +use crate::BlockId; use alloc::vec::Vec; use bitcoin::{ consensus, @@ -6,8 +7,6 @@ use bitcoin::{ Transaction, }; -use crate::BlockId; - pub const RAW_TX_1: &str = "0200000000010116d6174da7183d70d0a7d4dc314d517a7d135db79ad63515028b293a76f4f9d10000000000feffffff023a21fc8350060000160014531c405e1881ef192294b8813631e258bf98ea7a1027000000000000225120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c024730440220591b1a172a122da49ba79a3e79f98aaa03fd7a372f9760da18890b6a327e6010022013e82319231da6c99abf8123d7c07e13cf9bd8d76e113e18dc452e5024db156d012102318a2d558b2936c52e320decd6d92a88d7f530be91b6fe0af5caf41661e77da3ef2e0100"; pub const RAW_TX_2: &str = "02000000000101a688607020cfae91a61e7c516b5ef1264d5d77f17200c3866826c6c808ebf1620000000000feffffff021027000000000000225120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c20fd48ff530600001600146886c525e41d4522042bd0b159dfbade2504a6bb024730440220740ff7e665cd20565d4296b549df8d26b941be3f1e3af89a0b60e50c0dbeb69a02206213ab7030cf6edc6c90d4ccf33010644261e029950a688dc0b1a9ebe6ddcc5a012102f2ac6b396a97853cb6cd62242c8ae4842024742074475023532a51e9c53194253e760100"; pub const RAW_TX_3: &str = "0200000000010135d67ee47b557e68b8c6223958f597381965ed719f1207ee2b9e20432a24a5dc0100000000feffffff021027000000000000225120a82f29944d65b86ae6b5e5cc75e294ead6c59391a1edc5e016e3498c67fc7bbb62215a5055060000160014070df7671dea67a50c4799a744b5c9be8f4bac690247304402207ebf8d29f71fd03e7e6977b3ea78ca5fcc5c49a42ae822348fc401862fdd766c02201d7e4ff0684ecb008b6142f36ead1b0b4d615524c4f58c261113d361f4427e25012103e6a75e2fab85e5ecad641afc4ffba7222f998649d9f18cac92f0fcc8618883b3ee760100"; diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 029eedc28..c1c555961 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -38,8 +38,8 @@ pub use indexer::spk_txout; pub use indexer::Indexer; pub mod local_chain; mod tx_data_traits; -pub mod tx_graph; pub use tx_data_traits::*; +pub mod tx_graph; pub use tx_graph::TxGraph; mod chain_oracle; pub use chain_oracle::*; @@ -63,6 +63,9 @@ pub use spk_iter::*; pub mod rusqlite_impl; pub mod spk_client; +pub extern crate bdk_core; +pub use bdk_core::*; + #[allow(unused_imports)] #[macro_use] extern crate alloc; @@ -75,37 +78,6 @@ pub extern crate serde; #[macro_use] extern crate std; -#[cfg(all(not(feature = "std"), feature = "hashbrown"))] -extern crate hashbrown; - -// When no-std use `alloc`'s Hash collections. This is activated by default -#[cfg(all(not(feature = "std"), not(feature = "hashbrown")))] -#[doc(hidden)] -pub mod collections { - #![allow(dead_code)] - pub type HashSet = alloc::collections::BTreeSet; - pub type HashMap = alloc::collections::BTreeMap; - pub use alloc::collections::{btree_map as hash_map, *}; -} - -// When we have std, use `std`'s all collections -#[cfg(all(feature = "std", not(feature = "hashbrown")))] -#[doc(hidden)] -pub mod collections { - pub use std::collections::{hash_map, *}; -} - -// With this special feature `hashbrown`, use `hashbrown`'s hash collections, and else from `alloc`. -#[cfg(feature = "hashbrown")] -#[doc(hidden)] -pub mod collections { - #![allow(dead_code)] - pub type HashSet = hashbrown::HashSet; - pub type HashMap = hashbrown::HashMap; - pub use alloc::collections::*; - pub use hashbrown::hash_map; -} - /// How many confirmations are needed f or a coinbase output to be spent. pub const COINBASE_MATURITY: u32 = 100; @@ -137,3 +109,27 @@ impl core::ops::Deref for Impl { &self.0 } } + +/// A wrapper that we use to impl remote traits for types in our crate or dependency crates that impl [`Anchor`]. +pub struct AnchorImpl(pub T); + +impl AnchorImpl { + /// Returns the inner `T`. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl From for AnchorImpl { + fn from(value: T) -> Self { + Self(value) + } +} + +impl core::ops::Deref for AnchorImpl { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/chain/src/rusqlite_impl.rs b/crates/chain/src/rusqlite_impl.rs index d8ef65c42..2cc4481c9 100644 --- a/crates/chain/src/rusqlite_impl.rs +++ b/crates/chain/src/rusqlite_impl.rs @@ -157,15 +157,15 @@ impl ToSql for Impl { } } -impl FromSql for Impl { +impl FromSql for AnchorImpl { fn column_result(value: ValueRef<'_>) -> FromSqlResult { serde_json::from_str(value.as_str()?) - .map(Impl) + .map(AnchorImpl) .map_err(from_sql_error) } } -impl ToSql for Impl { +impl ToSql for AnchorImpl { fn to_sql(&self) -> rusqlite::Result> { serde_json::to_string(&self.0) .map(Into::into) @@ -319,12 +319,12 @@ where ))?; let row_iter = statement.query_map([], |row| { Ok(( - row.get::<_, Impl>("json(anchor)")?, + row.get::<_, AnchorImpl>("json(anchor)")?, row.get::<_, Impl>("txid")?, )) })?; for row in row_iter { - let (Impl(anchor), Impl(txid)) = row?; + let (AnchorImpl(anchor), Impl(txid)) = row?; changeset.anchors.insert((anchor, txid)); } @@ -381,7 +381,7 @@ where ":txid": Impl(*txid), ":block_height": anchor_block.height, ":block_hash": Impl(anchor_block.hash), - ":anchor": Impl(anchor.clone()), + ":anchor": AnchorImpl(anchor.clone()), })?; } diff --git a/crates/chain/src/spk_client.rs b/crates/chain/src/spk_client.rs index e31b431dd..75fb66984 100644 --- a/crates/chain/src/spk_client.rs +++ b/crates/chain/src/spk_client.rs @@ -3,8 +3,9 @@ use crate::{ alloc::{boxed::Box, collections::VecDeque, vec::Vec}, collections::BTreeMap, local_chain::CheckPoint, - ConfirmationBlockTime, Indexed, + Indexed, }; +use bdk_core::ConfirmationBlockTime; use bitcoin::{OutPoint, Script, ScriptBuf, Txid}; type InspectSync = dyn FnMut(SyncItem, SyncProgress) + Send + 'static; diff --git a/crates/chain/src/tx_data_traits.rs b/crates/chain/src/tx_data_traits.rs index 8a324f6a5..d3d562bf3 100644 --- a/crates/chain/src/tx_data_traits.rs +++ b/crates/chain/src/tx_data_traits.rs @@ -1,6 +1,5 @@ -use crate::collections::BTreeMap; -use crate::collections::BTreeSet; -use crate::BlockId; +use crate::collections::{BTreeMap, BTreeSet}; +use crate::{BlockId, ConfirmationBlockTime}; use alloc::vec::Vec; /// Trait that "anchors" blockchain data to a specific block of height and hash. @@ -85,6 +84,22 @@ impl<'a, A: Anchor> Anchor for &'a A { } } +impl Anchor for BlockId { + fn anchor_block(&self) -> Self { + *self + } +} + +impl Anchor for ConfirmationBlockTime { + fn anchor_block(&self) -> BlockId { + self.block_id + } + + fn confirmation_height_upper_bound(&self) -> u32 { + self.block_id.height + } +} + /// An [`Anchor`] that can be constructed from a given block, block height and transaction position /// within the block. pub trait AnchorFromBlockPosition: Anchor { @@ -92,6 +107,21 @@ pub trait AnchorFromBlockPosition: Anchor { fn from_block_position(block: &bitcoin::Block, block_id: BlockId, tx_pos: usize) -> Self; } +impl AnchorFromBlockPosition for BlockId { + fn from_block_position(_block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self { + block_id + } +} + +impl AnchorFromBlockPosition for ConfirmationBlockTime { + fn from_block_position(block: &bitcoin::Block, block_id: BlockId, _tx_pos: usize) -> Self { + Self { + block_id, + confirmation_time: block.header.time as _, + } + } +} + /// Trait that makes an object mergeable. pub trait Merge: Default { /// Merge another object of the same type onto `self`. diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index e953580ce..206aaf11e 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -92,9 +92,9 @@ //! [`try_get_chain_position`]: TxGraph::try_get_chain_position //! [`insert_txout`]: TxGraph::insert_txout -use crate::{ - collections::*, Anchor, Balance, BlockId, ChainOracle, ChainPosition, FullTxOut, Merge, -}; +use crate::collections::*; +use crate::BlockId; +use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge}; use alloc::collections::vec_deque::VecDeque; use alloc::sync::Arc; use alloc::vec::Vec; diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index df5e6a621..4ce6772bb 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -2,12 +2,12 @@ #[macro_use] mod common; -use bdk_chain::tx_graph::{self, CalculateFeeError}; +use bdk_chain::{collections::*, BlockId, ConfirmationBlockTime}; use bdk_chain::{ - collections::*, local_chain::LocalChain, + tx_graph::{self, CalculateFeeError}, tx_graph::{ChangeSet, TxGraph}, - Anchor, BlockId, ChainOracle, ChainPosition, ConfirmationBlockTime, Merge, + Anchor, ChainOracle, ChainPosition, Merge, }; use bitcoin::{ absolute, hashes::Hash, transaction, Amount, BlockHash, OutPoint, ScriptBuf, SignedAmount, diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 000000000..d74cf906c --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bdk_core" +version = "0.1.0" +edition = "2021" +rust-version = "1.63" +homepage = "https://bitcoindevkit.org" +repository = "https://github.com/bitcoindevkit/bdk" +documentation = "https://docs.rs/bdk_core" +description = "Collection of core structures for Bitcoin Dev Kit." +license = "MIT OR Apache-2.0" +readme = "README.md" + +[dependencies] +bitcoin = { version = "0.32", default-features = false } +serde = { version = "1", optional = true, features = ["derive", "rc"] } +hashbrown = { version = "0.9.1", optional = true } + +[features] +default = ["std"] +std = ["bitcoin/std"] +serde = ["dep:serde", "bitcoin/serde", "hashbrown?/serde"] diff --git a/crates/core/src/chain_data.rs b/crates/core/src/chain_data.rs new file mode 100644 index 000000000..2e64c9cb2 --- /dev/null +++ b/crates/core/src/chain_data.rs @@ -0,0 +1,51 @@ +use bitcoin::{hashes::Hash, BlockHash}; + +/// A reference to a block in the canonical chain. +#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct BlockId { + /// The height of the block. + pub height: u32, + /// The hash of the block. + pub hash: BlockHash, +} + +impl Default for BlockId { + fn default() -> Self { + Self { + height: Default::default(), + hash: BlockHash::all_zeros(), + } + } +} + +impl From<(u32, BlockHash)> for BlockId { + fn from((height, hash): (u32, BlockHash)) -> Self { + Self { height, hash } + } +} + +impl From for (u32, BlockHash) { + fn from(block_id: BlockId) -> Self { + (block_id.height, block_id.hash) + } +} + +impl From<(&u32, &BlockHash)> for BlockId { + fn from((height, hash): (&u32, &BlockHash)) -> Self { + Self { + height: *height, + hash: *hash, + } + } +} + +/// Represents the confirmation block and time of a transaction. +#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ConfirmationBlockTime { + /// The anchor block. + pub block_id: BlockId, + /// The confirmation time of the transaction being anchored. + pub confirmation_time: u64, +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 000000000..1c5358401 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,58 @@ +//! This crate is a collection of core structures for [Bitcoin Dev Kit]. + +// only enables the `doc_cfg` feature when the `docsrs` configuration attribute is defined +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr( + docsrs, + doc(html_logo_url = "https://github.com/bitcoindevkit/bdk/raw/master/static/bdk.png") +)] +#![no_std] +#![warn(missing_docs)] + +pub use bitcoin; + +#[allow(unused_imports)] +#[macro_use] +extern crate alloc; + +#[allow(unused_imports)] +#[cfg(feature = "std")] +#[macro_use] +extern crate std; + +#[cfg(feature = "serde")] +pub extern crate serde; + +#[cfg(all(not(feature = "std"), feature = "hashbrown"))] +extern crate hashbrown; + +// When no-std use `alloc`'s Hash collections. This is activated by default +#[cfg(all(not(feature = "std"), not(feature = "hashbrown")))] +#[doc(hidden)] +pub mod collections { + #![allow(dead_code)] + pub type HashSet = alloc::collections::BTreeSet; + pub type HashMap = alloc::collections::BTreeMap; + pub use alloc::collections::{btree_map as hash_map, *}; +} + +// When we have std, use `std`'s all collections +#[cfg(all(feature = "std", not(feature = "hashbrown")))] +#[doc(hidden)] +pub mod collections { + pub use std::collections::{hash_map, *}; +} + +// With this special feature `hashbrown`, use `hashbrown`'s hash collections, and else from `alloc`. +#[cfg(feature = "hashbrown")] +#[doc(hidden)] +pub mod collections { + #![allow(dead_code)] + pub type HashSet = hashbrown::HashSet; + pub type HashMap = hashbrown::HashMap; + pub use alloc::collections::*; + pub use hashbrown::hash_map; +} + +mod chain_data; +pub use chain_data::*; diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 393f9d3fb..6a97252fc 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -1,4 +1,3 @@ -use bdk_chain::ConfirmationBlockTime; use serde_json::json; use std::cmp; use std::collections::HashMap; @@ -20,6 +19,7 @@ use bdk_chain::miniscript::{ psbt::PsbtExt, Descriptor, DescriptorPublicKey, }; +use bdk_chain::ConfirmationBlockTime; use bdk_chain::{ indexed_tx_graph, indexer::keychain_txout::{self, KeychainTxOutIndex}, From a86c878a60f40ce30ab00c0b47fa6cddf30cfe30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 15:18:25 +0000 Subject: [PATCH 2/9] refactor(chain): change `CheckPoint::apply_changeset` to be a separate function: `apply_changeset_to_checkpoint`. --- crates/chain/src/local_chain.rs | 103 ++++++++++++++++---------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/crates/chain/src/local_chain.rs b/crates/chain/src/local_chain.rs index 3a822ab0b..4a2505f70 100644 --- a/crates/chain/src/local_chain.rs +++ b/crates/chain/src/local_chain.rs @@ -207,46 +207,6 @@ impl CheckPoint { base.extend(core::iter::once(block_id).chain(tail.into_iter().rev())) .expect("tail is in order") } - - /// Apply `changeset` to the checkpoint. - fn apply_changeset(mut self, changeset: &ChangeSet) -> Result { - 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 = None; - - for cp in self.iter() { - if cp.height() >= start_height { - extension.insert(cp.height(), cp.hash()); - } else { - base = Some(cp); - break; - } - } - - for (&height, &hash) in &changeset.blocks { - match hash { - Some(hash) => { - extension.insert(height, hash); - } - None => { - extension.remove(&height); - } - }; - } - - let new_tip = match base { - Some(base) => base - .extend(extension.into_iter().map(BlockId::from)) - .expect("extension is strictly greater than base"), - None => LocalChain::from_blocks(extension)?.tip(), - }; - self = new_tip; - } - - Ok(self) - } } /// Iterates over checkpoints backwards. @@ -275,6 +235,49 @@ impl IntoIterator for CheckPoint { } } +/// Apply `changeset` to the checkpoint. +fn apply_changeset_to_checkpoint( + mut init_cp: CheckPoint, + changeset: &ChangeSet, +) -> Result { + 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 = None; + + for cp in init_cp.iter() { + if cp.height() >= start_height { + extension.insert(cp.height(), cp.hash()); + } else { + base = Some(cp); + break; + } + } + + for (&height, &hash) in &changeset.blocks { + match hash { + Some(hash) => { + extension.insert(height, hash); + } + None => { + extension.remove(&height); + } + }; + } + + let new_tip = match base { + Some(base) => base + .extend(extension.into_iter().map(BlockId::from)) + .expect("extension is strictly greater than base"), + None => LocalChain::from_blocks(extension)?.tip(), + }; + init_cp = new_tip; + } + + Ok(init_cp) +} + /// This is a local implementation of [`ChainOracle`]. #[derive(Debug, Clone, PartialEq)] pub struct LocalChain { @@ -490,7 +493,7 @@ impl LocalChain { /// Apply the given `changeset`. pub fn apply_changeset(&mut self, changeset: &ChangeSet) -> Result<(), MissingGenesisError> { let old_tip = self.tip.clone(); - let new_tip = old_tip.apply_changeset(changeset)?; + let new_tip = apply_changeset_to_checkpoint(old_tip, changeset)?; self.tip = new_tip; debug_assert!(self._check_changeset_is_applied(changeset)); Ok(()) @@ -848,12 +851,10 @@ fn merge_chains( if is_update_height_superset_of_original { return Ok((update_tip, changeset)); } else { - let new_tip = - original_tip.apply_changeset(&changeset).map_err(|_| { - CannotConnectError { - try_include_height: 0, - } - })?; + let new_tip = apply_changeset_to_checkpoint(original_tip, &changeset) + .map_err(|_| CannotConnectError { + try_include_height: 0, + })?; return Ok((new_tip, changeset)); } } @@ -889,10 +890,10 @@ fn merge_chains( } } - let new_tip = original_tip - .apply_changeset(&changeset) - .map_err(|_| CannotConnectError { + let new_tip = apply_changeset_to_checkpoint(original_tip, &changeset).map_err(|_| { + CannotConnectError { try_include_height: 0, - })?; + } + })?; Ok((new_tip, changeset)) } From 77e31c71a5edc011df42daece1920903bc2b866f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 15:38:10 +0000 Subject: [PATCH 3/9] feat!: move `CheckPoint` to `bdk_core` Also add `CheckPoint::eq_ptr` which compares the internal pointers of two `CheckPoint`s. This is used as an optimisation when comparing two chains. --- crates/chain/src/local_chain.rs | 234 +------------------------------- crates/chain/src/spk_client.rs | 3 +- crates/core/src/checkpoint.rs | 233 +++++++++++++++++++++++++++++++ crates/core/src/lib.rs | 3 + crates/testenv/src/lib.rs | 3 +- 5 files changed, 241 insertions(+), 235 deletions(-) create mode 100644 crates/core/src/checkpoint.rs diff --git a/crates/chain/src/local_chain.rs b/crates/chain/src/local_chain.rs index 4a2505f70..e16131db3 100644 --- a/crates/chain/src/local_chain.rs +++ b/crates/chain/src/local_chain.rs @@ -5,236 +5,10 @@ use core::ops::RangeBounds; use crate::collections::BTreeMap; use crate::{BlockId, ChainOracle, Merge}; -use alloc::sync::Arc; +pub use bdk_core::{CheckPoint, CheckPointIter}; use bitcoin::block::Header; use bitcoin::BlockHash; -/// A [`LocalChain`] checkpoint is used to find the agreement point between two chains and as a -/// transaction anchor. -/// -/// Each checkpoint contains the height and hash of a block ([`BlockId`]). -/// -/// Internally, checkpoints are nodes of a reference-counted linked-list. This allows the caller to -/// cheaply clone a [`CheckPoint`] without copying the whole list and to view the entire chain -/// without holding a lock on [`LocalChain`]. -#[derive(Debug, Clone)] -pub struct CheckPoint(Arc); - -/// The internal contents of [`CheckPoint`]. -#[derive(Debug, Clone)] -struct CPInner { - /// Block id (hash and height). - block: BlockId, - /// Previous checkpoint (if any). - prev: Option>, -} - -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) - } -} - -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 })) - } - - /// 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, - ) -> Result> { - let mut blocks = block_ids.into_iter(); - let mut acc = CheckPoint::new(blocks.next().ok_or(None)?); - for id in blocks { - acc = acc.push(id).map_err(Some)?; - } - Ok(acc) - } - - /// 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") - } - - /// 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 { - if self.height() < block.height { - Ok(Self(Arc::new(CPInner { - block, - 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 - } - - /// 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 - } - - /// Get the previous checkpoint in the chain - pub fn prev(&self) -> Option { - self.0.prev.clone().map(CheckPoint) - } - - /// Iterate from this checkpoint in descending height. - pub fn iter(&self) -> CheckPointIter { - self.clone().into_iter() - } - - /// Get checkpoint at `height`. - /// - /// Returns `None` if checkpoint at `height` does not exist`. - pub fn get(&self, height: u32) -> Option { - self.range(height..=height).next() - } - - /// Iterate checkpoints over a height range. - /// - /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip - /// height). - pub fn range(&self, range: R) -> impl Iterator - where - R: RangeBounds, - { - let start_bound = range.start_bound().cloned(); - let end_bound = range.end_bound().cloned(); - self.iter() - .skip_while(move |cp| match end_bound { - core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound, - core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound, - core::ops::Bound::Unbounded => false, - }) - .take_while(move |cp| match start_bound { - core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound, - core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound, - core::ops::Bound::Unbounded => true, - }) - } - - /// 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") - } -} - -/// Iterates over checkpoints backwards. -pub struct CheckPointIter { - current: Option>, -} - -impl Iterator for CheckPointIter { - type Item = CheckPoint; - - fn next(&mut self) -> Option { - let current = self.current.clone()?; - self.current.clone_from(¤t.prev); - Some(CheckPoint(current)) - } -} - -impl IntoIterator for CheckPoint { - type Item = CheckPoint; - type IntoIter = CheckPointIter; - - fn into_iter(self) -> Self::IntoIter { - CheckPointIter { - current: Some(self.0), - } - } -} - /// Apply `changeset` to the checkpoint. fn apply_changeset_to_checkpoint( mut init_cp: CheckPoint, @@ -582,9 +356,7 @@ impl LocalChain { /// Iterate over checkpoints in descending height order. pub fn iter_checkpoints(&self) -> CheckPointIter { - CheckPointIter { - current: Some(self.tip.0.clone()), - } + self.tip.iter() } fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool { @@ -847,7 +619,7 @@ fn merge_chains( prev_orig_was_invalidated = false; // OPTIMIZATION 2 -- if we have the same underlying pointer at this point, we // can guarantee that no older blocks are introduced. - if Arc::as_ptr(&o.0) == Arc::as_ptr(&u.0) { + if o.eq_ptr(u) { if is_update_height_superset_of_original { return Ok((update_tip, changeset)); } else { diff --git a/crates/chain/src/spk_client.rs b/crates/chain/src/spk_client.rs index 75fb66984..e31b431dd 100644 --- a/crates/chain/src/spk_client.rs +++ b/crates/chain/src/spk_client.rs @@ -3,9 +3,8 @@ use crate::{ alloc::{boxed::Box, collections::VecDeque, vec::Vec}, collections::BTreeMap, local_chain::CheckPoint, - Indexed, + ConfirmationBlockTime, Indexed, }; -use bdk_core::ConfirmationBlockTime; use bitcoin::{OutPoint, Script, ScriptBuf, Txid}; type InspectSync = dyn FnMut(SyncItem, SyncProgress) + Send + 'static; diff --git a/crates/core/src/checkpoint.rs b/crates/core/src/checkpoint.rs new file mode 100644 index 000000000..0abadda1d --- /dev/null +++ b/crates/core/src/checkpoint.rs @@ -0,0 +1,233 @@ +use core::ops::RangeBounds; + +use alloc::sync::Arc; +use bitcoin::BlockHash; + +use crate::BlockId; + +/// A checkpoint is a node of a reference-counted linked list of [`BlockId`]s. +/// +/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse +/// block chains. +#[derive(Debug, Clone)] +pub struct CheckPoint(Arc); + +/// The internal contents of [`CheckPoint`]. +#[derive(Debug, Clone)] +struct CPInner { + /// Block id (hash and height). + block: BlockId, + /// Previous checkpoint (if any). + prev: Option>, +} + +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) + } +} + +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 })) + } + + /// 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, + ) -> Result> { + let mut blocks = block_ids.into_iter(); + let mut acc = CheckPoint::new(blocks.next().ok_or(None)?); + for id in blocks { + acc = acc.push(id).map_err(Some)?; + } + Ok(acc) + } + + /// 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") + } + + /// 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 { + if self.height() < block.height { + Ok(Self(Arc::new(CPInner { + block, + 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 + } + + /// 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 + } + + /// Get the previous checkpoint in the chain + pub fn prev(&self) -> Option { + self.0.prev.clone().map(CheckPoint) + } + + /// Iterate from this checkpoint in descending height. + pub fn iter(&self) -> CheckPointIter { + self.clone().into_iter() + } + + /// Get checkpoint at `height`. + /// + /// Returns `None` if checkpoint at `height` does not exist`. + pub fn get(&self, height: u32) -> Option { + self.range(height..=height).next() + } + + /// Iterate checkpoints over a height range. + /// + /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip + /// height). + pub fn range(&self, range: R) -> impl Iterator + where + R: RangeBounds, + { + let start_bound = range.start_bound().cloned(); + let end_bound = range.end_bound().cloned(); + self.iter() + .skip_while(move |cp| match end_bound { + core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound, + core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound, + core::ops::Bound::Unbounded => false, + }) + .take_while(move |cp| match start_bound { + core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound, + core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound, + core::ops::Bound::Unbounded => true, + }) + } + + /// 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) + } +} + +/// Iterates over checkpoints backwards. +pub struct CheckPointIter { + current: Option>, +} + +impl Iterator for CheckPointIter { + type Item = CheckPoint; + + fn next(&mut self) -> Option { + let current = self.current.clone()?; + self.current.clone_from(¤t.prev); + Some(CheckPoint(current)) + } +} + +impl IntoIterator for CheckPoint { + type Item = CheckPoint; + type IntoIter = CheckPointIter; + + fn into_iter(self) -> Self::IntoIter { + CheckPointIter { + current: Some(self.0), + } + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 1c5358401..f8ac5e328 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -56,3 +56,6 @@ pub mod collections { mod chain_data; pub use chain_data::*; + +mod checkpoint; +pub use checkpoint::*; diff --git a/crates/testenv/src/lib.rs b/crates/testenv/src/lib.rs index 6d169bdce..747acc448 100644 --- a/crates/testenv/src/lib.rs +++ b/crates/testenv/src/lib.rs @@ -4,8 +4,7 @@ use bdk_chain::{ secp256k1::rand::random, transaction, Address, Amount, Block, BlockHash, CompactTarget, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, }, - local_chain::CheckPoint, - BlockId, + BlockId, local_chain::CheckPoint, }; use bitcoincore_rpc::{ bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules}, From bdea871a0d0d337772f226fdcf5cd734f3d857ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 16:57:59 +0000 Subject: [PATCH 4/9] feat!: move `tx_graph::Update` to `bdk_core` --- crates/chain/src/tx_graph.rs | 35 +--------------------------- crates/core/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 206aaf11e..085418fc3 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -98,6 +98,7 @@ use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge}; use alloc::collections::vec_deque::VecDeque; use alloc::sync::Arc; use alloc::vec::Vec; +pub use bdk_core::tx_graph::Update; use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid}; use core::fmt::{self, Formatter}; use core::{ @@ -105,30 +106,6 @@ use core::{ ops::{Deref, RangeInclusive}, }; -/// Data object used to update the [`TxGraph`] with. -#[derive(Debug, Clone)] -pub struct Update { - /// Full transactions. - pub txs: Vec>, - /// Floating txouts. - pub txouts: BTreeMap, - /// Transaction anchors. - pub anchors: BTreeSet<(A, Txid)>, - /// Seen at times for transactions. - pub seen_ats: HashMap, -} - -impl Default for Update { - fn default() -> Self { - Self { - txs: Default::default(), - txouts: Default::default(), - anchors: Default::default(), - seen_ats: Default::default(), - } - } -} - impl From> for Update { fn from(graph: TxGraph) -> Self { Self { @@ -151,16 +128,6 @@ impl From> for TxGraph { } } -impl Update { - /// Extend this update with `other`. - pub fn extend(&mut self, other: Update) { - self.txs.extend(other.txs); - self.txouts.extend(other.txouts); - self.anchors.extend(other.anchors); - self.seen_ats.extend(other.seen_ats); - } -} - /// A graph of transactions and spends. /// /// See the [module-level documentation] for more. diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index f8ac5e328..bed9da4a1 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -59,3 +59,48 @@ pub use chain_data::*; mod checkpoint; pub use checkpoint::*; + +/// Core structures for [`TxGraph`]. +/// +/// [`TxGraph`]: https://docs.rs/bdk_chain/latest/bdk_chain/tx_graph/struct.TxGraph.html +pub mod tx_graph { + use crate::collections::{BTreeMap, BTreeSet, HashMap}; + use alloc::{sync::Arc, vec::Vec}; + use bitcoin::{OutPoint, Transaction, TxOut, Txid}; + + /// Data object used to update a [`TxGraph`]. + /// + /// [`TxGraph`]: https://docs.rs/bdk_chain/latest/bdk_chain/tx_graph/struct.TxGraph.html + #[derive(Debug, Clone)] + pub struct Update { + /// Full transactions. + pub txs: Vec>, + /// Floating txouts. + pub txouts: BTreeMap, + /// Transaction anchors. + pub anchors: BTreeSet<(A, Txid)>, + /// Seen at times for transactions. + pub seen_ats: HashMap, + } + + impl Default for Update { + fn default() -> Self { + Self { + txs: Default::default(), + txouts: Default::default(), + anchors: Default::default(), + seen_ats: Default::default(), + } + } + } + + impl Update { + /// Extend this update with `other`. + pub fn extend(&mut self, other: Update) { + self.txs.extend(other.txs); + self.txouts.extend(other.txouts); + self.anchors.extend(other.anchors); + self.seen_ats.extend(other.seen_ats); + } + } +} From ab0315d14fa741a691ee0deef4567ea66cb44a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 18:12:46 +0000 Subject: [PATCH 5/9] feat!: move `spk_client`s to `bdk_core` Also introduced extension trait for builder methods that take in a `KeychainTxOutIndex`. `Indexed` and `KeychainIndexed` are also moved to `bdk_core`. --- crates/chain/src/indexer/keychain_txout.rs | 41 ++++++++++++++++ crates/chain/src/lib.rs | 6 --- crates/core/Cargo.toml | 3 ++ crates/core/src/lib.rs | 7 +++ crates/{chain => core}/src/spk_client.rs | 57 +++++----------------- crates/testenv/src/lib.rs | 3 +- crates/wallet/src/wallet/mod.rs | 2 + example-crates/example_esplora/src/main.rs | 1 + 8 files changed, 68 insertions(+), 52 deletions(-) rename crates/{chain => core}/src/spk_client.rs (90%) diff --git a/crates/chain/src/indexer/keychain_txout.rs b/crates/chain/src/indexer/keychain_txout.rs index ce9707c7b..c43208095 100644 --- a/crates/chain/src/indexer/keychain_txout.rs +++ b/crates/chain/src/indexer/keychain_txout.rs @@ -4,6 +4,7 @@ use crate::{ collections::*, miniscript::{Descriptor, DescriptorPublicKey}, + spk_client::{FullScanRequestBuilder, SyncRequestBuilder}, spk_iter::BIP32_MAX_INDEX, spk_txout::SpkTxOutIndex, DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator, @@ -875,3 +876,43 @@ impl Merge for ChangeSet { self.last_revealed.is_empty() } } + +/// Trait to extend [`SyncRequestBuilder`]. +pub trait SyncRequestBuilderExt { + /// Add [`Script`](bitcoin::Script)s that are revealed by the `indexer` of the given `spk_range` + /// that will be synced against. + fn revealed_spks_from_indexer(self, indexer: &KeychainTxOutIndex, spk_range: R) -> Self + where + R: core::ops::RangeBounds; + + /// Add [`Script`](bitcoin::Script)s that are revealed by the `indexer` but currently unused. + fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex) -> Self; +} + +impl SyncRequestBuilderExt for SyncRequestBuilder<(K, u32)> { + fn revealed_spks_from_indexer(self, indexer: &KeychainTxOutIndex, spk_range: R) -> Self + where + R: core::ops::RangeBounds, + { + self.spks_with_indexes(indexer.revealed_spks(spk_range)) + } + + fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex) -> Self { + self.spks_with_indexes(indexer.unused_spks()) + } +} + +/// Trait to extend [`FullScanRequestBuilder`]. +pub trait FullScanRequestBuilderExt { + /// Add spk iterators for each keychain tracked in `indexer`. + fn spks_from_indexer(self, indexer: &KeychainTxOutIndex) -> Self; +} + +impl FullScanRequestBuilderExt for FullScanRequestBuilder { + fn spks_from_indexer(mut self, indexer: &KeychainTxOutIndex) -> Self { + for (keychain, spks) in indexer.all_unbounded_spk_iters() { + self = self.spks_for_keychain(keychain, spks); + } + self + } +} diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index c1c555961..9667bb549 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -61,7 +61,6 @@ pub use indexer::keychain_txout; pub use spk_iter::*; #[cfg(feature = "rusqlite")] pub mod rusqlite_impl; -pub mod spk_client; pub extern crate bdk_core; pub use bdk_core::*; @@ -81,11 +80,6 @@ extern crate std; /// How many confirmations are needed f or a coinbase output to be spent. pub const COINBASE_MATURITY: u32 = 100; -/// A tuple of keychain index and `T` representing the indexed value. -pub type Indexed = (u32, T); -/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them. -pub type KeychainIndexed = ((K, u32), T); - /// A wrapper that we use to impl remote traits for types in our crate or dependency crates. pub struct Impl(pub T); diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index d74cf906c..80741b074 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -19,3 +19,6 @@ hashbrown = { version = "0.9.1", optional = true } default = ["std"] std = ["bitcoin/std"] serde = ["dep:serde", "bitcoin/serde", "hashbrown?/serde"] + +[dev-dependencies] +bdk_chain = { version = "0.17.0", path = "../chain" } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index bed9da4a1..038bee621 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -54,12 +54,19 @@ pub mod collections { pub use hashbrown::hash_map; } +/// A tuple of keychain index and `T` representing the indexed value. +pub type Indexed = (u32, T); +/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them. +pub type KeychainIndexed = ((K, u32), T); + mod chain_data; pub use chain_data::*; mod checkpoint; pub use checkpoint::*; +pub mod spk_client; + /// Core structures for [`TxGraph`]. /// /// [`TxGraph`]: https://docs.rs/bdk_chain/latest/bdk_chain/tx_graph/struct.TxGraph.html diff --git a/crates/chain/src/spk_client.rs b/crates/core/src/spk_client.rs similarity index 90% rename from crates/chain/src/spk_client.rs rename to crates/core/src/spk_client.rs index e31b431dd..e54849fbb 100644 --- a/crates/chain/src/spk_client.rs +++ b/crates/core/src/spk_client.rs @@ -2,8 +2,7 @@ use crate::{ alloc::{boxed::Box, collections::VecDeque, vec::Vec}, collections::BTreeMap, - local_chain::CheckPoint, - ConfirmationBlockTime, Indexed, + CheckPoint, ConfirmationBlockTime, Indexed, }; use bitcoin::{OutPoint, Script, ScriptBuf, Txid}; @@ -101,27 +100,6 @@ impl Default for SyncRequestBuilder { } } -#[cfg(feature = "miniscript")] -impl SyncRequestBuilder<(K, u32)> { - /// Add [`Script`]s that are revealed by the `indexer` of the given `spk_range` that will be - /// synced against. - pub fn revealed_spks_from_indexer( - self, - indexer: &crate::indexer::keychain_txout::KeychainTxOutIndex, - spk_range: impl core::ops::RangeBounds, - ) -> Self { - self.spks_with_indexes(indexer.revealed_spks(spk_range)) - } - - /// Add [`Script`]s that are revealed by the `indexer` but currently unused. - pub fn unused_spks_from_indexer( - self, - indexer: &crate::indexer::keychain_txout::KeychainTxOutIndex, - ) -> Self { - self.spks_with_indexes(indexer.unused_spks()) - } -} - impl SyncRequestBuilder<()> { /// Add [`Script`]s that will be synced against. pub fn spks(self, spks: impl IntoIterator) -> Self { @@ -132,7 +110,7 @@ impl SyncRequestBuilder<()> { impl SyncRequestBuilder { /// Set the initial chain tip for the sync request. /// - /// This is used to update [`LocalChain`](crate::local_chain::LocalChain). + /// This is used to update [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html). pub fn chain_tip(mut self, cp: CheckPoint) -> Self { self.inner.chain_tip = Some(cp); self @@ -143,7 +121,7 @@ impl SyncRequestBuilder { /// # Example /// /// Sync revealed script pubkeys obtained from a - /// [`KeychainTxOutIndex`](crate::keychain_txout::KeychainTxOutIndex). + /// [`KeychainTxOutIndex`](../../bdk_chain/indexer/keychain_txout/struct.KeychainTxOutIndex.html). /// /// ```rust /// # use bdk_chain::spk_client::SyncRequest; @@ -216,9 +194,9 @@ impl SyncRequestBuilder { /// /// ```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 scripts = [ScriptBuf::default(), ScriptBuf::default()]; -/// # use bdk_chain::spk_client::SyncRequest; /// // Construct a sync request. /// let sync_request = SyncRequest::builder() /// // Provide chain tip of the local wallet. @@ -345,9 +323,11 @@ impl SyncRequest { #[must_use] #[derive(Debug)] pub struct SyncResult { - /// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph). + /// The update to apply to the receiving + /// [`TxGraph`](../../bdk_chain/tx_graph/struct.TxGraph.html). pub graph_update: crate::tx_graph::Update, - /// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain). + /// The update to apply to the receiving + /// [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html). pub chain_update: Option, } @@ -374,24 +354,10 @@ impl Default for FullScanRequestBuilder { } } -#[cfg(feature = "miniscript")] -impl FullScanRequestBuilder { - /// Add spk iterators for each keychain tracked in `indexer`. - pub fn spks_from_indexer( - mut self, - indexer: &crate::indexer::keychain_txout::KeychainTxOutIndex, - ) -> Self { - for (keychain, spks) in indexer.all_unbounded_spk_iters() { - self = self.spks_for_keychain(keychain, spks); - } - self - } -} - impl FullScanRequestBuilder { /// Set the initial chain tip for the full scan request. /// - /// This is used to update [`LocalChain`](crate::local_chain::LocalChain). + /// This is used to update [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html). pub fn chain_tip(mut self, tip: CheckPoint) -> Self { self.inner.chain_tip = Some(tip); self @@ -496,9 +462,10 @@ impl FullScanRequest { #[must_use] #[derive(Debug)] pub struct FullScanResult { - /// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain). + /// The update to apply to the receiving + /// [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html). pub graph_update: crate::tx_graph::Update, - /// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph). + /// The update to apply to the receiving [`TxGraph`](../../bdk_chain/tx_graph/struct.TxGraph.html). pub chain_update: Option, /// Last active indices for the corresponding keychains (`K`). pub last_active_indices: BTreeMap, diff --git a/crates/testenv/src/lib.rs b/crates/testenv/src/lib.rs index 747acc448..6d169bdce 100644 --- a/crates/testenv/src/lib.rs +++ b/crates/testenv/src/lib.rs @@ -4,7 +4,8 @@ use bdk_chain::{ secp256k1::rand::random, transaction, Address, Amount, Block, BlockHash, CompactTarget, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, }, - BlockId, local_chain::CheckPoint, + local_chain::CheckPoint, + BlockId, }; use bitcoincore_rpc::{ bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules}, diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 638bb5757..72c42ddfe 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -2472,6 +2472,7 @@ impl Wallet { /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to /// start a blockchain sync with a spk based blockchain client. pub fn start_sync_with_revealed_spks(&self) -> SyncRequestBuilder<(KeychainKind, u32)> { + use bdk_chain::keychain_txout::SyncRequestBuilderExt; SyncRequest::builder() .chain_tip(self.chain.tip()) .revealed_spks_from_indexer(&self.indexed_graph.index, ..) @@ -2486,6 +2487,7 @@ impl Wallet { /// This operation is generally only used when importing or restoring a previously used wallet /// in which the list of used scripts is not known. pub fn start_full_scan(&self) -> FullScanRequestBuilder { + use bdk_chain::keychain_txout::FullScanRequestBuilderExt; FullScanRequest::builder() .chain_tip(self.chain.tip()) .spks_from_indexer(&self.indexed_graph.index) diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index d4692e35c..7a4005878 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -6,6 +6,7 @@ use std::{ use bdk_chain::{ bitcoin::Network, + keychain_txout::FullScanRequestBuilderExt, spk_client::{FullScanRequest, SyncRequest}, Merge, }; From 0d302f5f204eeac8902a4b5943b9b820c6b575ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 18:26:28 +0000 Subject: [PATCH 6/9] feat(electrum)!: depend on `bdk_core` instead of `bdk_chain` `.populate_tx_cache` has been changed to take in a collection of `Arc`. --- crates/electrum/Cargo.toml | 3 +- crates/electrum/src/bdk_electrum_client.rs | 41 ++++++++++----------- crates/electrum/src/lib.rs | 24 +++++++----- example-crates/example_electrum/src/main.rs | 9 ++++- example-crates/wallet_electrum/src/main.rs | 2 +- 5 files changed, 44 insertions(+), 35 deletions(-) diff --git a/crates/electrum/Cargo.toml b/crates/electrum/Cargo.toml index eff11daac..b91c13662 100644 --- a/crates/electrum/Cargo.toml +++ b/crates/electrum/Cargo.toml @@ -10,11 +10,12 @@ license = "MIT OR Apache-2.0" readme = "README.md" [dependencies] -bdk_chain = { path = "../chain", version = "0.17.0" } +bdk_core = { path = "../core", version = "0.1" } electrum-client = { version = "0.21", features = ["proxy"], default-features = false } [dev-dependencies] bdk_testenv = { path = "../testenv", default-features = false } +bdk_chain = { path = "../chain", version = "0.17.0" } [features] default = ["use-rustls"] diff --git a/crates/electrum/src/bdk_electrum_client.rs b/crates/electrum/src/bdk_electrum_client.rs index 571660756..d78d7f64c 100644 --- a/crates/electrum/src/bdk_electrum_client.rs +++ b/crates/electrum/src/bdk_electrum_client.rs @@ -1,10 +1,8 @@ -use bdk_chain::{ +use bdk_core::{ bitcoin::{block::Header, BlockHash, OutPoint, ScriptBuf, Transaction, Txid}, collections::{BTreeMap, HashMap}, - local_chain::CheckPoint, spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}, - tx_graph::{self, TxGraph}, - Anchor, BlockId, ConfirmationBlockTime, + tx_graph, BlockId, CheckPoint, ConfirmationBlockTime, }; use electrum_client::{ElectrumApi, Error, HeaderNotification}; use std::{ @@ -39,14 +37,11 @@ impl BdkElectrumClient { /// Inserts transactions into the transaction cache so that the client will not fetch these /// transactions. - pub fn populate_tx_cache(&self, tx_graph: impl AsRef>) { - let txs = tx_graph - .as_ref() - .full_txs() - .map(|tx_node| (tx_node.txid, tx_node.tx)); - + pub fn populate_tx_cache(&self, txs: impl IntoIterator>>) { let mut tx_cache = self.tx_cache.lock().unwrap(); - for (txid, tx) in txs { + for tx in txs { + let tx = tx.into(); + let txid = tx.compute_txid(); tx_cache.insert(txid, tx); } } @@ -121,9 +116,10 @@ impl BdkElectrumClient { /// [`CalculateFeeError::MissingTxOut`] error if those `TxOut`s are not /// present in the transaction graph. /// - /// [`CalculateFeeError::MissingTxOut`]: bdk_chain::tx_graph::CalculateFeeError::MissingTxOut - /// [`Wallet.calculate_fee`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee - /// [`Wallet.calculate_fee_rate`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee_rate + /// [`bdk_chain`]: ../bdk_chain/index.html + /// [`CalculateFeeError::MissingTxOut`]: ../bdk_chain/tx_graph/enum.CalculateFeeError.html#variant.MissingTxOut + /// [`Wallet.calculate_fee`]: ../bdk_wallet/struct.Wallet.html#method.calculate_fee + /// [`Wallet.calculate_fee_rate`]: ../bdk_wallet/struct.Wallet.html#method.calculate_fee_rate pub fn full_scan( &self, request: impl Into>, @@ -189,9 +185,10 @@ impl BdkElectrumClient { /// may include scripts that have been used, use [`full_scan`] with the keychain. /// /// [`full_scan`]: Self::full_scan - /// [`CalculateFeeError::MissingTxOut`]: bdk_chain::tx_graph::CalculateFeeError::MissingTxOut - /// [`Wallet.calculate_fee`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee - /// [`Wallet.calculate_fee_rate`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee_rate + /// [`bdk_chain`]: ../bdk_chain/index.html + /// [`CalculateFeeError::MissingTxOut`]: ../bdk_chain/tx_graph/enum.CalculateFeeError.html#variant.MissingTxOut + /// [`Wallet.calculate_fee`]: ../bdk_wallet/struct.Wallet.html#method.calculate_fee + /// [`Wallet.calculate_fee_rate`]: ../bdk_wallet/struct.Wallet.html#method.calculate_fee_rate pub fn sync( &self, request: impl Into>, @@ -514,20 +511,20 @@ fn fetch_tip_and_latest_blocks( // Add a corresponding checkpoint per anchor height if it does not yet exist. Checkpoints should not // surpass `latest_blocks`. -fn chain_update( +fn chain_update( mut tip: CheckPoint, latest_blocks: &BTreeMap, - anchors: impl Iterator, + anchors: impl Iterator, ) -> Result { - for anchor in anchors { - let height = anchor.0.anchor_block().height; + for (anchor, _txid) in anchors { + let height = anchor.block_id.height; // Checkpoint uses the `BlockHash` from `latest_blocks` so that the hash will be consistent // in case of a re-org. if tip.get(height).is_none() && height <= tip.height() { let hash = match latest_blocks.get(&height) { Some(&hash) => hash, - None => anchor.0.anchor_block().hash, + None => anchor.block_id.hash, }; tip = tip.insert(BlockId { hash, height }); } diff --git a/crates/electrum/src/lib.rs b/crates/electrum/src/lib.rs index d303ee403..914b4f19a 100644 --- a/crates/electrum/src/lib.rs +++ b/crates/electrum/src/lib.rs @@ -1,22 +1,26 @@ -//! This crate is used for updating structures of [`bdk_chain`] with data from an Electrum server. +//! This crate is used for returning updates from Electrum servers. //! -//! The two primary methods are [`BdkElectrumClient::sync`] and [`BdkElectrumClient::full_scan`]. In most cases -//! [`BdkElectrumClient::sync`] is used to sync the transaction histories of scripts that the application -//! cares about, for example the scripts for all the receive addresses of a Wallet's keychain that it -//! has shown a user. [`BdkElectrumClient::full_scan`] is meant to be used when importing or restoring a -//! keychain where the range of possibly used scripts is not known. In this case it is necessary to -//! scan all keychain scripts until a number (the "stop gap") of unused scripts is discovered. For a -//! sync or full scan the user receives relevant blockchain data and output updates for -//! [`bdk_chain`]. +//! Updates are returned as either a [`SyncResult`] (if [`BdkElectrumClient::sync()`] is called), +//! or a [`FullScanResult`] (if [`BdkElectrumClient::full_scan()`] is called). +//! +//! In most cases [`BdkElectrumClient::sync()`] is used to sync the transaction histories of scripts +//! that the application cares about, for example the scripts for all the receive addresses of a +//! Wallet's keychain that it has shown a user. +//! +//! [`BdkElectrumClient::full_scan`] is meant to be used when importing or restoring a keychain +//! where the range of possibly used scripts is not known. In this case it is necessary to scan all +//! keychain scripts until a number (the "stop gap") of unused scripts is discovered. //! //! Refer to [`example_electrum`] for a complete example. //! //! [`example_electrum`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_electrum +//! [`SyncResult`]: bdk_core::spk_client::SyncResult +//! [`FullScanResult`]: bdk_core::spk_client::FullScanResult #![warn(missing_docs)] mod bdk_electrum_client; pub use bdk_electrum_client::*; -pub use bdk_chain; +pub use bdk_core; pub use electrum_client; diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 662bc4237..21910cf66 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -127,7 +127,14 @@ fn main() -> anyhow::Result<()> { let client = BdkElectrumClient::new(electrum_cmd.electrum_args().client(network)?); // Tell the electrum client about the txs we've already got locally so it doesn't re-download them - client.populate_tx_cache(&*graph.lock().unwrap()); + client.populate_tx_cache( + graph + .lock() + .unwrap() + .graph() + .full_txs() + .map(|tx_node| tx_node.tx), + ); let (chain_update, graph_update, keychain_update) = match electrum_cmd.clone() { ElectrumCommands::Scan { diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index c05184052..47cbfa15d 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -50,7 +50,7 @@ fn main() -> Result<(), anyhow::Error> { // Populate the electrum client's transaction cache so it doesn't redownload transaction we // already have. - client.populate_tx_cache(wallet.tx_graph()); + client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); let request = wallet.start_full_scan().inspect({ let mut stdout = std::io::stdout(); From fea8eede760130db32c2cfaecc272f6c1ed979db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 18:36:07 +0000 Subject: [PATCH 7/9] feat(esplora)!: depend on `bdk_core` instead of `bdk_chain` Helper methods have been changed to use concrete types (instead of `A: Anchor` generic) - since `Anchor` is still part of `bdk_chain`. Also fix esplora docs. --- crates/esplora/Cargo.toml | 3 +- crates/esplora/README.md | 4 +-- crates/esplora/src/async_ext.rs | 39 ++++++++++++----------- crates/esplora/src/blocking_ext.rs | 50 +++++++++++++++++------------- crates/esplora/src/lib.rs | 9 ++---- 5 files changed, 55 insertions(+), 50 deletions(-) diff --git a/crates/esplora/Cargo.toml b/crates/esplora/Cargo.toml index 9148a0f86..8e0cf0464 100644 --- a/crates/esplora/Cargo.toml +++ b/crates/esplora/Cargo.toml @@ -12,13 +12,14 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bdk_chain = { path = "../chain", version = "0.17.0", default-features = false } +bdk_core = { path = "../core", version = "0.1", default-features = false } esplora-client = { version = "0.9.0", default-features = false } async-trait = { version = "0.1.66", optional = true } futures = { version = "0.3.26", optional = true } miniscript = { version = "12.0.0", optional = true, default-features = false } [dev-dependencies] +bdk_chain = { path = "../chain", version = "0.17.0" } bdk_testenv = { path = "../testenv", default-features = false } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/crates/esplora/README.md b/crates/esplora/README.md index ef2e65176..0535b9a38 100644 --- a/crates/esplora/README.md +++ b/crates/esplora/README.md @@ -37,7 +37,7 @@ For full examples, refer to [`example-crates/wallet_esplora_blocking`](https://g [`bdk_chain`]: https://docs.rs/bdk-chain/ [`EsploraExt`]: crate::EsploraExt [`EsploraAsyncExt`]: crate::EsploraAsyncExt -[`SyncRequest`]: bdk_chain::spk_client::SyncRequest -[`FullScanRequest`]: bdk_chain::spk_client::FullScanRequest +[`SyncRequest`]: bdk_core::spk_client::SyncRequest +[`FullScanRequest`]: bdk_core::spk_client::FullScanRequest [`sync`]: crate::EsploraExt::sync [`full_scan`]: crate::EsploraExt::full_scan diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index f3c8e966a..041205c5e 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -1,14 +1,10 @@ -use std::collections::{BTreeSet, HashSet}; - use async_trait::async_trait; -use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; -use bdk_chain::{ +use bdk_core::collections::{BTreeMap, BTreeSet, HashSet}; +use bdk_core::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; +use bdk_core::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, - collections::BTreeMap, - local_chain::CheckPoint, - BlockId, ConfirmationBlockTime, + tx_graph, BlockId, CheckPoint, ConfirmationBlockTime, Indexed, }; -use bdk_chain::{tx_graph, Anchor, Indexed}; use futures::{stream::FuturesOrdered, TryStreamExt}; use crate::{insert_anchor_from_status, insert_prevouts}; @@ -209,11 +205,11 @@ async fn fetch_block( /// /// We want to have a corresponding checkpoint per anchor height. However, checkpoints fetched /// should not surpass `latest_blocks`. -async fn chain_update( +async fn chain_update( client: &esplora_client::AsyncClient, latest_blocks: &BTreeMap, local_tip: &CheckPoint, - anchors: &BTreeSet<(A, Txid)>, + anchors: &BTreeSet<(ConfirmationBlockTime, Txid)>, ) -> Result { let mut point_of_agreement = None; let mut conflicts = vec![]; @@ -242,8 +238,8 @@ async fn chain_update( .extend(conflicts.into_iter().rev()) .expect("evicted are in order"); - for anchor in anchors { - let height = anchor.0.anchor_block().height; + for (anchor, _txid) in anchors { + let height = anchor.block_id.height; if tip.get(height).is_none() { let hash = match fetch_block(client, latest_blocks, height).await? { Some(hash) => hash, @@ -494,6 +490,7 @@ mod test { local_chain::LocalChain, BlockId, }; + use bdk_core::ConfirmationBlockTime; use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv}; use esplora_client::Builder; @@ -572,9 +569,12 @@ mod test { .iter() .map(|&height| -> anyhow::Result<_> { Ok(( - BlockId { - height, - hash: env.bitcoind.client.get_block_hash(height as _)?, + ConfirmationBlockTime { + block_id: BlockId { + height, + hash: env.bitcoind.client.get_block_hash(height as _)?, + }, + confirmation_time: height as _, }, Txid::all_zeros(), )) @@ -610,9 +610,12 @@ mod test { .iter() .map(|&(height, txid)| -> anyhow::Result<_> { Ok(( - BlockId { - height, - hash: env.bitcoind.client.get_block_hash(height as _)?, + ConfirmationBlockTime { + block_id: BlockId { + height, + hash: env.bitcoind.client.get_block_hash(height as _)?, + }, + confirmation_time: height as _, }, txid, )) diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs index 62f0d351e..d0744b4ed 100644 --- a/crates/esplora/src/blocking_ext.rs +++ b/crates/esplora/src/blocking_ext.rs @@ -1,15 +1,11 @@ -use std::collections::{BTreeSet, HashSet}; -use std::thread::JoinHandle; - -use bdk_chain::collections::BTreeMap; -use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; -use bdk_chain::{ +use bdk_core::collections::{BTreeMap, BTreeSet, HashSet}; +use bdk_core::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; +use bdk_core::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, - local_chain::CheckPoint, - BlockId, ConfirmationBlockTime, + tx_graph, BlockId, CheckPoint, ConfirmationBlockTime, Indexed, }; -use bdk_chain::{tx_graph, Anchor, Indexed}; use esplora_client::{OutputStatus, Tx}; +use std::thread::JoinHandle; use crate::{insert_anchor_from_status, insert_prevouts}; @@ -199,11 +195,11 @@ fn fetch_block( /// /// We want to have a corresponding checkpoint per anchor height. However, checkpoints fetched /// should not surpass `latest_blocks`. -fn chain_update( +fn chain_update( client: &esplora_client::BlockingClient, latest_blocks: &BTreeMap, local_tip: &CheckPoint, - anchors: &BTreeSet<(A, Txid)>, + anchors: &BTreeSet<(ConfirmationBlockTime, Txid)>, ) -> Result { let mut point_of_agreement = None; let mut conflicts = vec![]; @@ -232,8 +228,8 @@ fn chain_update( .extend(conflicts.into_iter().rev()) .expect("evicted are in order"); - for anchor in anchors { - let height = anchor.0.anchor_block().height; + for (anchor, _) in anchors { + let height = anchor.block_id.height; if tip.get(height).is_none() { let hash = match fetch_block(client, latest_blocks, height)? { Some(hash) => hash, @@ -475,6 +471,7 @@ mod test { use bdk_chain::bitcoin::Txid; use bdk_chain::local_chain::LocalChain; use bdk_chain::BlockId; + use bdk_core::ConfirmationBlockTime; use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv}; use esplora_client::{BlockHash, Builder}; use std::collections::{BTreeMap, BTreeSet}; @@ -561,9 +558,12 @@ mod test { .iter() .map(|&height| -> anyhow::Result<_> { Ok(( - BlockId { - height, - hash: env.bitcoind.client.get_block_hash(height as _)?, + ConfirmationBlockTime { + block_id: BlockId { + height, + hash: env.bitcoind.client.get_block_hash(height as _)?, + }, + confirmation_time: height as _, }, Txid::all_zeros(), )) @@ -598,9 +598,12 @@ mod test { .iter() .map(|&(height, txid)| -> anyhow::Result<_> { Ok(( - BlockId { - height, - hash: env.bitcoind.client.get_block_hash(height as _)?, + ConfirmationBlockTime { + block_id: BlockId { + height, + hash: env.bitcoind.client.get_block_hash(height as _)?, + }, + confirmation_time: height as _, }, txid, )) @@ -794,9 +797,12 @@ mod test { let txid: Txid = bdk_chain::bitcoin::hashes::Hash::hash( &format!("txid_at_height_{}", h).into_bytes(), ); - let anchor = BlockId { - height: h, - hash: anchor_blockhash, + let anchor = ConfirmationBlockTime { + block_id: BlockId { + height: h, + hash: anchor_blockhash, + }, + confirmation_time: h as _, }; (anchor, txid) }) diff --git a/crates/esplora/src/lib.rs b/crates/esplora/src/lib.rs index 9a6e8f1df..7aad133b4 100644 --- a/crates/esplora/src/lib.rs +++ b/crates/esplora/src/lib.rs @@ -19,14 +19,9 @@ //! Just like how [`EsploraExt`] extends the functionality of an //! [`esplora_client::BlockingClient`], [`EsploraAsyncExt`] is the async version which extends //! [`esplora_client::AsyncClient`]. -//! -//! [`TxGraph`]: bdk_chain::tx_graph::TxGraph -//! [`LocalChain`]: bdk_chain::local_chain::LocalChain -//! [`ChainOracle`]: bdk_chain::ChainOracle -//! [`example_esplora`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_esplora -use bdk_chain::bitcoin::{Amount, OutPoint, TxOut, Txid}; -use bdk_chain::{tx_graph, BlockId, ConfirmationBlockTime}; +use bdk_core::bitcoin::{Amount, OutPoint, TxOut, Txid}; +use bdk_core::{tx_graph, BlockId, ConfirmationBlockTime}; use esplora_client::TxStatus; pub use esplora_client; From dafb9aaef7d8d4bc83c8ba0845323b92b82fbb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 23 Aug 2024 18:38:55 +0000 Subject: [PATCH 8/9] feat(bitcoind_rpc)!: depend on `bdk_core` instead of `bdk_chain` --- crates/bitcoind_rpc/Cargo.toml | 7 ++++--- crates/bitcoind_rpc/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bitcoind_rpc/Cargo.toml b/crates/bitcoind_rpc/Cargo.toml index bee58efa1..e601d6fe2 100644 --- a/crates/bitcoind_rpc/Cargo.toml +++ b/crates/bitcoind_rpc/Cargo.toml @@ -15,12 +15,13 @@ readme = "README.md" [dependencies] bitcoin = { version = "0.32.0", default-features = false } bitcoincore-rpc = { version = "0.19.0" } -bdk_chain = { path = "../chain", version = "0.17", default-features = false } +bdk_core = { path = "../core", version = "0.1", default-features = false } [dev-dependencies] bdk_testenv = { path = "../testenv", default-features = false } +bdk_chain = { path = "../chain", version = "0.17" } [features] default = ["std"] -std = ["bitcoin/std", "bdk_chain/std"] -serde = ["bitcoin/serde", "bdk_chain/serde"] +std = ["bitcoin/std", "bdk_core/std"] +serde = ["bitcoin/serde", "bdk_core/serde"] diff --git a/crates/bitcoind_rpc/src/lib.rs b/crates/bitcoind_rpc/src/lib.rs index ce5e863bb..49121cead 100644 --- a/crates/bitcoind_rpc/src/lib.rs +++ b/crates/bitcoind_rpc/src/lib.rs @@ -9,7 +9,7 @@ //! mempool. #![warn(missing_docs)] -use bdk_chain::{local_chain::CheckPoint, BlockId}; +use bdk_core::{BlockId, CheckPoint}; use bitcoin::{block::Header, Block, BlockHash, Transaction}; pub use bitcoincore_rpc; use bitcoincore_rpc::bitcoincore_rpc_json; From a5d076f215cd91173f55bda0d1cc59b9dde75511 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Sun, 25 Aug 2024 15:35:18 +1000 Subject: [PATCH 9/9] chore(core)!: s/tx_graph::Update/TxUpdate/ We shouldn't refer to `tx_graph` in `bdk_core`. TxGraph happens to consume `Update` but it doesn't conceptually own the definition of an update to transactions. I tried to also remove a lot of references to "graph" where they shouldn't be. "graph" is a very general kind of data structure so we should avoid referring to it where it's not relevant. `tx_update` is much better than `graph_update`. --- crates/chain/src/indexed_tx_graph.rs | 6 +- crates/chain/src/tx_graph.rs | 14 ++-- crates/chain/tests/test_tx_graph.rs | 22 +++---- .../core/src/{chain_data.rs => block_id.rs} | 0 crates/core/src/lib.rs | 52 ++------------- crates/core/src/spk_client.rs | 24 ++++--- crates/core/src/tx_update.rs | 43 ++++++++++++ crates/electrum/src/bdk_electrum_client.rs | 66 +++++++++---------- crates/electrum/tests/test_electrum.rs | 22 +++---- crates/esplora/src/async_ext.rs | 36 +++++----- crates/esplora/src/blocking_ext.rs | 36 +++++----- crates/esplora/src/lib.rs | 6 +- crates/esplora/tests/async_ext.rs | 16 ++--- crates/esplora/tests/blocking_ext.rs | 16 ++--- crates/wallet/src/wallet/export.rs | 2 +- crates/wallet/src/wallet/mod.rs | 12 ++-- crates/wallet/tests/common.rs | 2 +- crates/wallet/tests/wallet.rs | 2 +- example-crates/example_electrum/src/main.rs | 8 +-- example-crates/example_esplora/src/main.rs | 6 +- 20 files changed, 195 insertions(+), 196 deletions(-) rename crates/core/src/{chain_data.rs => block_id.rs} (100%) create mode 100644 crates/core/src/tx_update.rs diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 73ae458ff..ed2a1f0ce 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -90,10 +90,10 @@ where /// Apply an `update` directly. /// - /// `update` is a [`tx_graph::Update`] and the resultant changes is returned as [`ChangeSet`]. + /// `update` is a [`tx_graph::TxUpdate`] and the resultant changes is returned as [`ChangeSet`]. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn apply_update(&mut self, update: tx_graph::Update) -> ChangeSet { + pub fn apply_update(&mut self, update: tx_graph::TxUpdate) -> ChangeSet { let tx_graph = self.graph.apply_update(update); let indexer = self.index_tx_graph_changeset(&tx_graph); ChangeSet { tx_graph, indexer } @@ -114,7 +114,7 @@ where /// set to the current time. pub fn apply_update_at( &mut self, - update: tx_graph::Update, + update: tx_graph::TxUpdate, seen_at: Option, ) -> ChangeSet { let tx_graph = self.graph.apply_update_at(update, seen_at); diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 085418fc3..127b47cf2 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -78,7 +78,7 @@ //! # let tx_b = tx_from_hex(RAW_TX_2); //! let mut graph: TxGraph = TxGraph::default(); //! -//! let mut update = tx_graph::Update::default(); +//! let mut update = tx_graph::TxUpdate::default(); //! update.txs.push(Arc::new(tx_a)); //! update.txs.push(Arc::new(tx_b)); //! @@ -98,7 +98,7 @@ use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge}; use alloc::collections::vec_deque::VecDeque; use alloc::sync::Arc; use alloc::vec::Vec; -pub use bdk_core::tx_graph::Update; +pub use bdk_core::TxUpdate; use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid}; use core::fmt::{self, Formatter}; use core::{ @@ -106,7 +106,7 @@ use core::{ ops::{Deref, RangeInclusive}, }; -impl From> for Update { +impl From> for TxUpdate { fn from(graph: TxGraph) -> Self { Self { txs: graph.full_txs().map(|tx_node| tx_node.tx).collect(), @@ -120,8 +120,8 @@ impl From> for Update { } } -impl From> for TxGraph { - fn from(update: Update) -> Self { +impl From> for TxGraph { + fn from(update: TxUpdate) -> Self { let mut graph = TxGraph::::default(); let _ = graph.apply_update_at(update, None); graph @@ -655,7 +655,7 @@ impl TxGraph { /// exist in `update` but not in `self`). #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn apply_update(&mut self, update: Update) -> ChangeSet { + pub fn apply_update(&mut self, update: TxUpdate) -> ChangeSet { use std::time::*; let now = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -676,7 +676,7 @@ impl TxGraph { /// /// Use [`apply_update`](TxGraph::apply_update) to have the `seen_at` value automatically set /// to the current time. - pub fn apply_update_at(&mut self, update: Update, seen_at: Option) -> ChangeSet { + pub fn apply_update_at(&mut self, update: TxUpdate, seen_at: Option) -> ChangeSet { let mut changeset = ChangeSet::::default(); let mut unanchored_txs = HashSet::::new(); for tx in update.txs { diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 4ce6772bb..a49c9e5f5 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -88,7 +88,7 @@ fn insert_txouts() { // Make the update graph let update = { - let mut update = tx_graph::Update::default(); + let mut update = tx_graph::TxUpdate::default(); for (outpoint, txout) in &update_ops { // Insert partials transactions. update.txouts.insert(*outpoint, txout.clone()); @@ -1137,12 +1137,12 @@ fn call_map_anchors_with_non_deterministic_anchor() { ); } -/// Tests `From` impls for conversion between [`TxGraph`] and [`tx_graph::Update`]. +/// Tests `From` impls for conversion between [`TxGraph`] and [`tx_graph::TxUpdate`]. #[test] fn tx_graph_update_conversion() { - use tx_graph::Update; + use tx_graph::TxUpdate; - type TestCase = (&'static str, Update); + type TestCase = (&'static str, TxUpdate); fn make_tx(v: i32) -> Transaction { Transaction { @@ -1161,24 +1161,24 @@ fn tx_graph_update_conversion() { } let test_cases: &[TestCase] = &[ - ("empty_update", Update::default()), + ("empty_update", TxUpdate::default()), ( "single_tx", - Update { + TxUpdate { txs: vec![make_tx(0).into()], ..Default::default() }, ), ( "two_txs", - Update { + TxUpdate { txs: vec![make_tx(0).into(), make_tx(1).into()], ..Default::default() }, ), ( "with_floating_txouts", - Update { + TxUpdate { txs: vec![make_tx(0).into(), make_tx(1).into()], txouts: [ (OutPoint::new(h!("a"), 0), make_txout(0)), @@ -1191,7 +1191,7 @@ fn tx_graph_update_conversion() { ), ( "with_anchors", - Update { + TxUpdate { txs: vec![make_tx(0).into(), make_tx(1).into()], txouts: [ (OutPoint::new(h!("a"), 0), make_txout(0)), @@ -1209,7 +1209,7 @@ fn tx_graph_update_conversion() { ), ( "with_seen_ats", - Update { + TxUpdate { txs: vec![make_tx(0).into(), make_tx(1).into()], txouts: [ (OutPoint::new(h!("a"), 0), make_txout(0)), @@ -1230,7 +1230,7 @@ fn tx_graph_update_conversion() { for (test_name, update) in test_cases { let mut tx_graph = TxGraph::::default(); let _ = tx_graph.apply_update_at(update.clone(), None); - let update_from_tx_graph: Update = tx_graph.into(); + let update_from_tx_graph: TxUpdate = tx_graph.into(); assert_eq!( update diff --git a/crates/core/src/chain_data.rs b/crates/core/src/block_id.rs similarity index 100% rename from crates/core/src/chain_data.rs rename to crates/core/src/block_id.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 038bee621..aeb34dca3 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -59,55 +59,13 @@ pub type Indexed = (u32, T); /// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them. pub type KeychainIndexed = ((K, u32), T); -mod chain_data; -pub use chain_data::*; +mod block_id; +pub use block_id::*; mod checkpoint; pub use checkpoint::*; -pub mod spk_client; - -/// Core structures for [`TxGraph`]. -/// -/// [`TxGraph`]: https://docs.rs/bdk_chain/latest/bdk_chain/tx_graph/struct.TxGraph.html -pub mod tx_graph { - use crate::collections::{BTreeMap, BTreeSet, HashMap}; - use alloc::{sync::Arc, vec::Vec}; - use bitcoin::{OutPoint, Transaction, TxOut, Txid}; - - /// Data object used to update a [`TxGraph`]. - /// - /// [`TxGraph`]: https://docs.rs/bdk_chain/latest/bdk_chain/tx_graph/struct.TxGraph.html - #[derive(Debug, Clone)] - pub struct Update { - /// Full transactions. - pub txs: Vec>, - /// Floating txouts. - pub txouts: BTreeMap, - /// Transaction anchors. - pub anchors: BTreeSet<(A, Txid)>, - /// Seen at times for transactions. - pub seen_ats: HashMap, - } +mod tx_update; +pub use tx_update::*; - impl Default for Update { - fn default() -> Self { - Self { - txs: Default::default(), - txouts: Default::default(), - anchors: Default::default(), - seen_ats: Default::default(), - } - } - } - - impl Update { - /// Extend this update with `other`. - pub fn extend(&mut self, other: Update) { - self.txs.extend(other.txs); - self.txouts.extend(other.txouts); - self.anchors.extend(other.anchors); - self.seen_ats.extend(other.seen_ats); - } - } -} +pub mod spk_client; diff --git a/crates/core/src/spk_client.rs b/crates/core/src/spk_client.rs index e54849fbb..1aceae5bb 100644 --- a/crates/core/src/spk_client.rs +++ b/crates/core/src/spk_client.rs @@ -323,18 +323,16 @@ impl SyncRequest { #[must_use] #[derive(Debug)] pub struct SyncResult { - /// The update to apply to the receiving - /// [`TxGraph`](../../bdk_chain/tx_graph/struct.TxGraph.html). - pub graph_update: crate::tx_graph::Update, - /// The update to apply to the receiving - /// [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html). + /// Relevant transaction data discovered during the scan. + pub tx_update: crate::TxUpdate, + /// Changes to the chain discovered during the scan. pub chain_update: Option, } impl Default for SyncResult { fn default() -> Self { Self { - graph_update: Default::default(), + tx_update: Default::default(), chain_update: Default::default(), } } @@ -462,19 +460,19 @@ impl FullScanRequest { #[must_use] #[derive(Debug)] pub struct FullScanResult { - /// The update to apply to the receiving - /// [`LocalChain`](../../bdk_chain/local_chain/struct.LocalChain.html). - pub graph_update: crate::tx_graph::Update, - /// The update to apply to the receiving [`TxGraph`](../../bdk_chain/tx_graph/struct.TxGraph.html). - pub chain_update: Option, - /// Last active indices for the corresponding keychains (`K`). + /// Relevant transaction data discovered during the scan. + pub tx_update: crate::TxUpdate, + /// 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, + /// Changes to the chain discovered during the scan. + pub chain_update: Option, } impl Default for FullScanResult { fn default() -> Self { Self { - graph_update: Default::default(), + tx_update: Default::default(), chain_update: Default::default(), last_active_indices: Default::default(), } diff --git a/crates/core/src/tx_update.rs b/crates/core/src/tx_update.rs new file mode 100644 index 000000000..a1ff75de7 --- /dev/null +++ b/crates/core/src/tx_update.rs @@ -0,0 +1,43 @@ +use crate::collections::{BTreeMap, BTreeSet, HashMap}; +use alloc::{sync::Arc, vec::Vec}; +use bitcoin::{OutPoint, Transaction, TxOut, Txid}; + +/// Data object used to communicate updates about relevant transactions from some chain data soruce +/// to the core model (usually a `bdk_chain::TxGraph`). +#[derive(Debug, Clone)] +pub struct TxUpdate { + /// Full transactions. These are transactions that were determined to be relevant to the wallet + /// given the request. + pub txs: Vec>, + /// Floating txouts. These are `TxOut`s that exist but the whole transaction wasn't included in + /// `txs` since only knowing about the output is important. These are often used to help determine + /// the fee of a wallet transaction. + pub txouts: BTreeMap, + /// Transaction anchors. Anchors tells us a position in the chain where a transaction was + /// confirmed. + pub anchors: BTreeSet<(A, Txid)>, + /// Seen at times for transactions. This records when a transaction was most recently seen in + /// the user's mempool for the sake of tie-breaking other conflicting transactions. + pub seen_ats: HashMap, +} + +impl Default for TxUpdate { + fn default() -> Self { + Self { + txs: Default::default(), + txouts: Default::default(), + anchors: Default::default(), + seen_ats: Default::default(), + } + } +} + +impl TxUpdate { + /// Extend this update with `other`. + pub fn extend(&mut self, other: TxUpdate) { + self.txs.extend(other.txs); + self.txouts.extend(other.txouts); + self.anchors.extend(other.anchors); + self.seen_ats.extend(other.seen_ats); + } +} diff --git a/crates/electrum/src/bdk_electrum_client.rs b/crates/electrum/src/bdk_electrum_client.rs index d78d7f64c..a98063336 100644 --- a/crates/electrum/src/bdk_electrum_client.rs +++ b/crates/electrum/src/bdk_electrum_client.rs @@ -2,7 +2,7 @@ use bdk_core::{ bitcoin::{block::Header, BlockHash, OutPoint, ScriptBuf, Transaction, Txid}, collections::{BTreeMap, HashMap}, spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}, - tx_graph, BlockId, CheckPoint, ConfirmationBlockTime, + BlockId, CheckPoint, ConfirmationBlockTime, TxUpdate, }; use electrum_client::{ElectrumApi, Error, HeaderNotification}; use std::{ @@ -134,12 +134,12 @@ impl BdkElectrumClient { None => None, }; - let mut graph_update = tx_graph::Update::::default(); + let mut tx_update = TxUpdate::::default(); let mut last_active_indices = BTreeMap::::default(); for keychain in request.keychains() { let spks = request.iter_spks(keychain.clone()); if let Some(last_active_index) = - self.populate_with_spks(&mut graph_update, spks, stop_gap, batch_size)? + self.populate_with_spks(&mut tx_update, spks, stop_gap, batch_size)? { last_active_indices.insert(keychain, last_active_index); } @@ -147,20 +147,20 @@ impl BdkElectrumClient { // Fetch previous `TxOut`s for fee calculation if flag is enabled. if fetch_prev_txouts { - self.fetch_prev_txout(&mut graph_update)?; + self.fetch_prev_txout(&mut tx_update)?; } let chain_update = match tip_and_latest_blocks { Some((chain_tip, latest_blocks)) => Some(chain_update( chain_tip, &latest_blocks, - graph_update.anchors.iter().cloned(), + tx_update.anchors.iter().cloned(), )?), _ => None, }; Ok(FullScanResult { - graph_update, + tx_update, chain_update, last_active_indices, }) @@ -202,9 +202,9 @@ impl BdkElectrumClient { None => None, }; - let mut graph_update = tx_graph::Update::::default(); + let mut tx_update = TxUpdate::::default(); self.populate_with_spks( - &mut graph_update, + &mut tx_update, request .iter_spks() .enumerate() @@ -212,37 +212,37 @@ impl BdkElectrumClient { usize::MAX, batch_size, )?; - self.populate_with_txids(&mut graph_update, request.iter_txids())?; - self.populate_with_outpoints(&mut graph_update, request.iter_outpoints())?; + self.populate_with_txids(&mut tx_update, request.iter_txids())?; + self.populate_with_outpoints(&mut tx_update, request.iter_outpoints())?; // Fetch previous `TxOut`s for fee calculation if flag is enabled. if fetch_prev_txouts { - self.fetch_prev_txout(&mut graph_update)?; + self.fetch_prev_txout(&mut tx_update)?; } let chain_update = match tip_and_latest_blocks { Some((chain_tip, latest_blocks)) => Some(chain_update( chain_tip, &latest_blocks, - graph_update.anchors.iter().cloned(), + tx_update.anchors.iter().cloned(), )?), None => None, }; Ok(SyncResult { - graph_update, + tx_update, chain_update, }) } - /// Populate the `graph_update` with transactions/anchors associated with the given `spks`. + /// Populate the `tx_update` with transactions/anchors associated with the given `spks`. /// /// Transactions that contains an output with requested spk, or spends form an output with - /// requested spk will be added to `graph_update`. Anchors of the aforementioned transactions are + /// requested spk will be added to `tx_update`. Anchors of the aforementioned transactions are /// also included. fn populate_with_spks( &self, - graph_update: &mut tx_graph::Update, + tx_update: &mut TxUpdate, mut spks: impl Iterator, stop_gap: usize, batch_size: usize, @@ -275,20 +275,20 @@ impl BdkElectrumClient { } for tx_res in spk_history { - graph_update.txs.push(self.fetch_tx(tx_res.tx_hash)?); - self.validate_merkle_for_anchor(graph_update, tx_res.tx_hash, tx_res.height)?; + tx_update.txs.push(self.fetch_tx(tx_res.tx_hash)?); + self.validate_merkle_for_anchor(tx_update, tx_res.tx_hash, tx_res.height)?; } } } } - /// Populate the `graph_update` with associated transactions/anchors of `outpoints`. + /// Populate the `tx_update` with associated transactions/anchors of `outpoints`. /// /// Transactions in which the outpoint resides, and transactions that spend from the outpoint are /// included. Anchors of the aforementioned transactions are included. fn populate_with_outpoints( &self, - graph_update: &mut tx_graph::Update, + tx_update: &mut TxUpdate, outpoints: impl IntoIterator, ) -> Result<(), Error> { for outpoint in outpoints { @@ -311,8 +311,8 @@ impl BdkElectrumClient { if !has_residing && res.tx_hash == op_txid { has_residing = true; - graph_update.txs.push(Arc::clone(&op_tx)); - self.validate_merkle_for_anchor(graph_update, res.tx_hash, res.height)?; + tx_update.txs.push(Arc::clone(&op_tx)); + self.validate_merkle_for_anchor(tx_update, res.tx_hash, res.height)?; } if !has_spending && res.tx_hash != op_txid { @@ -325,18 +325,18 @@ impl BdkElectrumClient { if !has_spending { continue; } - graph_update.txs.push(Arc::clone(&res_tx)); - self.validate_merkle_for_anchor(graph_update, res.tx_hash, res.height)?; + tx_update.txs.push(Arc::clone(&res_tx)); + self.validate_merkle_for_anchor(tx_update, res.tx_hash, res.height)?; } } } Ok(()) } - /// Populate the `graph_update` with transactions/anchors of the provided `txids`. + /// Populate the `tx_update` with transactions/anchors of the provided `txids`. fn populate_with_txids( &self, - graph_update: &mut tx_graph::Update, + tx_update: &mut TxUpdate, txids: impl IntoIterator, ) -> Result<(), Error> { for txid in txids { @@ -360,10 +360,10 @@ impl BdkElectrumClient { .into_iter() .find(|r| r.tx_hash == txid) { - self.validate_merkle_for_anchor(graph_update, txid, r.height)?; + self.validate_merkle_for_anchor(tx_update, txid, r.height)?; } - graph_update.txs.push(tx); + tx_update.txs.push(tx); } Ok(()) } @@ -372,7 +372,7 @@ impl BdkElectrumClient { // An anchor is inserted if the transaction is validated to be in a confirmed block. fn validate_merkle_for_anchor( &self, - graph_update: &mut tx_graph::Update, + tx_update: &mut TxUpdate, txid: Txid, confirmation_height: i32, ) -> Result<(), Error> { @@ -399,7 +399,7 @@ impl BdkElectrumClient { } if is_confirmed_tx { - graph_update.anchors.insert(( + tx_update.anchors.insert(( ConfirmationBlockTime { confirmation_time: header.time as u64, block_id: BlockId { @@ -418,17 +418,17 @@ impl BdkElectrumClient { // which we do not have by default. This data is needed to calculate the transaction fee. fn fetch_prev_txout( &self, - graph_update: &mut tx_graph::Update, + tx_update: &mut TxUpdate, ) -> Result<(), Error> { let mut no_dup = HashSet::::new(); - for tx in &graph_update.txs { + for tx in &tx_update.txs { if no_dup.insert(tx.compute_txid()) { for vin in &tx.input { let outpoint = vin.previous_output; let vout = outpoint.vout; let prev_tx = self.fetch_tx(outpoint.txid)?; let txout = prev_tx.output[vout as usize].clone(); - let _ = graph_update.txouts.insert(outpoint, txout); + let _ = tx_update.txouts.insert(outpoint, txout); } } } diff --git a/crates/electrum/tests/test_electrum.rs b/crates/electrum/tests/test_electrum.rs index d5e4a1596..5f032ba6c 100644 --- a/crates/electrum/tests/test_electrum.rs +++ b/crates/electrum/tests/test_electrum.rs @@ -49,7 +49,7 @@ where .apply_update(chain_update) .map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?; } - let _ = graph.apply_update(update.graph_update.clone()); + let _ = graph.apply_update(update.tx_update.clone()); Ok(update) } @@ -120,15 +120,15 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { "update should not alter original checkpoint tip since we already started with all checkpoints", ); - let graph_update = sync_update.graph_update; + let tx_update = sync_update.tx_update; let updated_graph = { let mut graph = TxGraph::::default(); - let _ = graph.apply_update(graph_update.clone()); + let _ = graph.apply_update(tx_update.clone()); graph }; // Check to see if we have the floating txouts available from our two created transactions' // previous outputs in order to calculate transaction fees. - for tx in &graph_update.txs { + for tx in &tx_update.txs { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transactions' previous outputs. let fee = updated_graph.calculate_fee(tx).expect("Fee must exist"); @@ -150,7 +150,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { } assert_eq!( - graph_update + tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -217,7 +217,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { .spks_for_keychain(0, spks.clone()); client.full_scan(request, 3, 1, false)? }; - assert!(full_scan_update.graph_update.txs.is_empty()); + assert!(full_scan_update.tx_update.txs.is_empty()); assert!(full_scan_update.last_active_indices.is_empty()); let full_scan_update = { let request = FullScanRequest::builder() @@ -227,7 +227,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { }; assert_eq!( full_scan_update - .graph_update + .tx_update .txs .first() .unwrap() @@ -259,7 +259,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { client.full_scan(request, 5, 1, false)? }; let txs: HashSet<_> = full_scan_update - .graph_update + .tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -274,7 +274,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { client.full_scan(request, 6, 1, false)? }; let txs: HashSet<_> = full_scan_update - .graph_update + .tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -478,7 +478,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { )?; // Retain a snapshot of all anchors before reorg process. - let initial_anchors = update.graph_update.anchors.clone(); + let initial_anchors = update.tx_update.anchors.clone(); assert_eq!(initial_anchors.len(), REORG_COUNT); for i in 0..REORG_COUNT { let (anchor, txid) = initial_anchors.iter().nth(i).unwrap(); @@ -509,7 +509,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { )?; // Check that no new anchors are added during current reorg. - assert!(initial_anchors.is_superset(&update.graph_update.anchors)); + assert!(initial_anchors.is_superset(&update.tx_update.anchors)); assert_eq!( get_balance(&recv_chain, &recv_graph)?, diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index 041205c5e..04e07c7c4 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -3,7 +3,7 @@ use bdk_core::collections::{BTreeMap, BTreeSet, HashSet}; use bdk_core::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; use bdk_core::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, - tx_graph, BlockId, CheckPoint, ConfirmationBlockTime, Indexed, + BlockId, CheckPoint, ConfirmationBlockTime, Indexed, TxUpdate, }; use futures::{stream::FuturesOrdered, TryStreamExt}; @@ -67,7 +67,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { None }; - let mut graph_update = tx_graph::Update::::default(); + let mut tx_update = TxUpdate::::default(); let mut inserted_txs = HashSet::::new(); let mut last_active_indices = BTreeMap::::new(); for keychain in keychains { @@ -80,7 +80,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { parallel_requests, ) .await?; - graph_update.extend(update); + tx_update.extend(update); if let Some(last_active_index) = last_active_index { last_active_indices.insert(keychain, last_active_index); } @@ -88,14 +88,14 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { let chain_update = match (chain_tip, latest_blocks) { (Some(chain_tip), Some(latest_blocks)) => { - Some(chain_update(self, &latest_blocks, &chain_tip, &graph_update.anchors).await?) + Some(chain_update(self, &latest_blocks, &chain_tip, &tx_update.anchors).await?) } _ => None, }; Ok(FullScanResult { chain_update, - graph_update, + tx_update, last_active_indices, }) } @@ -114,9 +114,9 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { None }; - let mut graph_update = tx_graph::Update::::default(); + let mut tx_update = TxUpdate::::default(); let mut inserted_txs = HashSet::::new(); - graph_update.extend( + tx_update.extend( fetch_txs_with_spks( self, &mut inserted_txs, @@ -125,7 +125,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { ) .await?, ); - graph_update.extend( + tx_update.extend( fetch_txs_with_txids( self, &mut inserted_txs, @@ -134,7 +134,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { ) .await?, ); - graph_update.extend( + tx_update.extend( fetch_txs_with_outpoints( self, &mut inserted_txs, @@ -146,14 +146,14 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { let chain_update = match (chain_tip, latest_blocks) { (Some(chain_tip), Some(latest_blocks)) => { - Some(chain_update(self, &latest_blocks, &chain_tip, &graph_update.anchors).await?) + Some(chain_update(self, &latest_blocks, &chain_tip, &tx_update.anchors).await?) } _ => None, }; Ok(SyncResult { chain_update, - graph_update, + tx_update, }) } } @@ -277,10 +277,10 @@ async fn fetch_txs_with_keychain_spks> + S mut keychain_spks: I, stop_gap: usize, parallel_requests: usize, -) -> Result<(tx_graph::Update, Option), Error> { +) -> Result<(TxUpdate, Option), Error> { type TxsOfSpkIndex = (u32, Vec); - let mut update = tx_graph::Update::::default(); + let mut update = TxUpdate::::default(); let mut last_index = Option::::None; let mut last_active_index = Option::::None; @@ -351,7 +351,7 @@ async fn fetch_txs_with_spks + Send>( inserted_txs: &mut HashSet, spks: I, parallel_requests: usize, -) -> Result, Error> +) -> Result, Error> where I::IntoIter: Send, { @@ -377,11 +377,11 @@ async fn fetch_txs_with_txids + Send>( inserted_txs: &mut HashSet, txids: I, parallel_requests: usize, -) -> Result, Error> +) -> Result, Error> where I::IntoIter: Send, { - let mut update = tx_graph::Update::::default(); + let mut update = TxUpdate::::default(); // Only fetch for non-inserted txs. let mut txids = txids .into_iter() @@ -426,12 +426,12 @@ async fn fetch_txs_with_outpoints + Send>( inserted_txs: &mut HashSet, outpoints: I, parallel_requests: usize, -) -> Result, Error> +) -> Result, Error> where I::IntoIter: Send, { let outpoints = outpoints.into_iter().collect::>(); - let mut update = tx_graph::Update::::default(); + let mut update = TxUpdate::::default(); // make sure txs exists in graph and tx statuses are updated // TODO: We should maintain a tx cache (like we do with Electrum). diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs index d0744b4ed..2552e6e83 100644 --- a/crates/esplora/src/blocking_ext.rs +++ b/crates/esplora/src/blocking_ext.rs @@ -2,7 +2,7 @@ use bdk_core::collections::{BTreeMap, BTreeSet, HashSet}; use bdk_core::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; use bdk_core::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, - tx_graph, BlockId, CheckPoint, ConfirmationBlockTime, Indexed, + BlockId, CheckPoint, ConfirmationBlockTime, Indexed, TxUpdate, }; use esplora_client::{OutputStatus, Tx}; use std::thread::JoinHandle; @@ -62,7 +62,7 @@ impl EsploraExt for esplora_client::BlockingClient { None }; - let mut graph_update = tx_graph::Update::default(); + let mut tx_update = TxUpdate::default(); let mut inserted_txs = HashSet::::new(); let mut last_active_indices = BTreeMap::::new(); for keychain in request.keychains() { @@ -74,7 +74,7 @@ impl EsploraExt for esplora_client::BlockingClient { stop_gap, parallel_requests, )?; - graph_update.extend(update); + tx_update.extend(update); if let Some(last_active_index) = last_active_index { last_active_indices.insert(keychain, last_active_index); } @@ -85,14 +85,14 @@ impl EsploraExt for esplora_client::BlockingClient { self, &latest_blocks, &chain_tip, - &graph_update.anchors, + &tx_update.anchors, )?), _ => None, }; Ok(FullScanResult { chain_update, - graph_update, + tx_update, last_active_indices, }) } @@ -111,21 +111,21 @@ impl EsploraExt for esplora_client::BlockingClient { None }; - let mut graph_update = tx_graph::Update::::default(); + let mut tx_update = TxUpdate::::default(); let mut inserted_txs = HashSet::::new(); - graph_update.extend(fetch_txs_with_spks( + tx_update.extend(fetch_txs_with_spks( self, &mut inserted_txs, request.iter_spks(), parallel_requests, )?); - graph_update.extend(fetch_txs_with_txids( + tx_update.extend(fetch_txs_with_txids( self, &mut inserted_txs, request.iter_txids(), parallel_requests, )?); - graph_update.extend(fetch_txs_with_outpoints( + tx_update.extend(fetch_txs_with_outpoints( self, &mut inserted_txs, request.iter_outpoints(), @@ -137,14 +137,14 @@ impl EsploraExt for esplora_client::BlockingClient { self, &latest_blocks, &chain_tip, - &graph_update.anchors, + &tx_update.anchors, )?), _ => None, }; Ok(SyncResult { chain_update, - graph_update, + tx_update, }) } } @@ -254,10 +254,10 @@ fn fetch_txs_with_keychain_spks>>( mut keychain_spks: I, stop_gap: usize, parallel_requests: usize, -) -> Result<(tx_graph::Update, Option), Error> { +) -> Result<(TxUpdate, Option), Error> { type TxsOfSpkIndex = (u32, Vec); - let mut update = tx_graph::Update::::default(); + let mut update = TxUpdate::::default(); let mut last_index = Option::::None; let mut last_active_index = Option::::None; @@ -331,7 +331,7 @@ fn fetch_txs_with_spks>( inserted_txs: &mut HashSet, spks: I, parallel_requests: usize, -) -> Result, Error> { +) -> Result, Error> { fetch_txs_with_keychain_spks( client, inserted_txs, @@ -353,8 +353,8 @@ fn fetch_txs_with_txids>( inserted_txs: &mut HashSet, txids: I, parallel_requests: usize, -) -> Result, Error> { - let mut update = tx_graph::Update::::default(); +) -> Result, Error> { + let mut update = TxUpdate::::default(); // Only fetch for non-inserted txs. let mut txids = txids .into_iter() @@ -405,9 +405,9 @@ fn fetch_txs_with_outpoints>( inserted_txs: &mut HashSet, outpoints: I, parallel_requests: usize, -) -> Result, Error> { +) -> Result, Error> { let outpoints = outpoints.into_iter().collect::>(); - let mut update = tx_graph::Update::::default(); + let mut update = TxUpdate::::default(); // make sure txs exists in graph and tx statuses are updated // TODO: We should maintain a tx cache (like we do with Electrum). diff --git a/crates/esplora/src/lib.rs b/crates/esplora/src/lib.rs index 7aad133b4..a166b6f9f 100644 --- a/crates/esplora/src/lib.rs +++ b/crates/esplora/src/lib.rs @@ -21,7 +21,7 @@ //! [`esplora_client::AsyncClient`]. use bdk_core::bitcoin::{Amount, OutPoint, TxOut, Txid}; -use bdk_core::{tx_graph, BlockId, ConfirmationBlockTime}; +use bdk_core::{BlockId, ConfirmationBlockTime, TxUpdate}; use esplora_client::TxStatus; pub use esplora_client; @@ -37,7 +37,7 @@ mod async_ext; pub use async_ext::*; fn insert_anchor_from_status( - update: &mut tx_graph::Update, + update: &mut TxUpdate, txid: Txid, status: TxStatus, ) { @@ -59,7 +59,7 @@ fn insert_anchor_from_status( /// Inserts floating txouts into `tx_graph` using [`Vin`](esplora_client::api::Vin)s returned by /// Esplora. fn insert_prevouts( - update: &mut tx_graph::Update, + update: &mut TxUpdate, esplora_inputs: impl IntoIterator, ) { let prevouts = esplora_inputs diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index 7b0ef7fa6..b535d2bfa 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -78,15 +78,15 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { "update should not alter original checkpoint tip since we already started with all checkpoints", ); - let graph_update = sync_update.graph_update; + let tx_update = sync_update.tx_update; let updated_graph = { let mut graph = TxGraph::::default(); - let _ = graph.apply_update(graph_update.clone()); + let _ = graph.apply_update(tx_update.clone()); graph }; // Check to see if we have the floating txouts available from our two created transactions' // previous outputs in order to calculate transaction fees. - for tx in &graph_update.txs { + for tx in &tx_update.txs { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transactions' previous outputs. let fee = updated_graph.calculate_fee(tx).expect("Fee must exist"); @@ -108,7 +108,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { } assert_eq!( - graph_update + tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -177,7 +177,7 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> { .spks_for_keychain(0, spks.clone()); client.full_scan(request, 3, 1).await? }; - assert!(full_scan_update.graph_update.txs.is_empty()); + assert!(full_scan_update.tx_update.txs.is_empty()); assert!(full_scan_update.last_active_indices.is_empty()); let full_scan_update = { let request = FullScanRequest::builder() @@ -187,7 +187,7 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> { }; assert_eq!( full_scan_update - .graph_update + .tx_update .txs .first() .unwrap() @@ -221,7 +221,7 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> { client.full_scan(request, 5, 1).await? }; let txs: HashSet<_> = full_scan_update - .graph_update + .tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -236,7 +236,7 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> { client.full_scan(request, 6, 1).await? }; let txs: HashSet<_> = full_scan_update - .graph_update + .tx_update .txs .iter() .map(|tx| tx.compute_txid()) diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index b3833b899..d4191ceb0 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -78,15 +78,15 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { "update should not alter original checkpoint tip since we already started with all checkpoints", ); - let graph_update = sync_update.graph_update; + let tx_update = sync_update.tx_update; let updated_graph = { let mut graph = TxGraph::::default(); - let _ = graph.apply_update(graph_update.clone()); + let _ = graph.apply_update(tx_update.clone()); graph }; // Check to see if we have the floating txouts available from our two created transactions' // previous outputs in order to calculate transaction fees. - for tx in &graph_update.txs { + for tx in &tx_update.txs { // Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the // floating txouts available from the transactions' previous outputs. let fee = updated_graph.calculate_fee(tx).expect("Fee must exist"); @@ -108,7 +108,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { } assert_eq!( - graph_update + tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -177,7 +177,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { .spks_for_keychain(0, spks.clone()); client.full_scan(request, 3, 1)? }; - assert!(full_scan_update.graph_update.txs.is_empty()); + assert!(full_scan_update.tx_update.txs.is_empty()); assert!(full_scan_update.last_active_indices.is_empty()); let full_scan_update = { let request = FullScanRequest::builder() @@ -187,7 +187,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { }; assert_eq!( full_scan_update - .graph_update + .tx_update .txs .first() .unwrap() @@ -221,7 +221,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { client.full_scan(request, 5, 1)? }; let txs: HashSet<_> = full_scan_update - .graph_update + .tx_update .txs .iter() .map(|tx| tx.compute_txid()) @@ -236,7 +236,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> { client.full_scan(request, 6, 1)? }; let txs: HashSet<_> = full_scan_update - .graph_update + .tx_update .txs .iter() .map(|tx| tx.compute_txid()) diff --git a/crates/wallet/src/wallet/export.rs b/crates/wallet/src/wallet/export.rs index 386d9d4e3..ad5a6b2a8 100644 --- a/crates/wallet/src/wallet/export.rs +++ b/crates/wallet/src/wallet/export.rs @@ -255,7 +255,7 @@ mod test { }; wallet .apply_update(Update { - graph: tx_graph::Update { + tx_update: tx_graph::TxUpdate { anchors: [(anchor, txid)].into_iter().collect(), ..Default::default() }, diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 72c42ddfe..5cad6bb86 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -33,7 +33,7 @@ use bdk_chain::{ FullScanRequest, FullScanRequestBuilder, FullScanResult, SyncRequest, SyncRequestBuilder, SyncResult, }, - tx_graph::{CanonicalTx, TxGraph, TxNode}, + tx_graph::{CanonicalTx, TxGraph, TxNode, TxUpdate}, BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, DescriptorExt, FullTxOut, Indexed, IndexedTxGraph, Merge, }; @@ -132,7 +132,7 @@ pub struct Update { pub last_active_indices: BTreeMap, /// Update for the wallet's internal [`TxGraph`]. - pub graph: chain::tx_graph::Update, + pub tx_update: TxUpdate, /// Update for the wallet's internal [`LocalChain`]. /// @@ -144,7 +144,7 @@ impl From> for Update { fn from(value: FullScanResult) -> Self { Self { last_active_indices: value.last_active_indices, - graph: value.graph_update, + tx_update: value.tx_update, chain: value.chain_update, } } @@ -154,7 +154,7 @@ impl From for Update { fn from(value: SyncResult) -> Self { Self { last_active_indices: BTreeMap::new(), - graph: value.graph_update, + tx_update: value.tx_update, chain: value.chain_update, } } @@ -2318,7 +2318,7 @@ impl Wallet { changeset.merge(index_changeset.into()); changeset.merge( self.indexed_graph - .apply_update_at(update.graph, seen_at) + .apply_update_at(update.tx_update, seen_at) .into(), ); self.stage.merge(changeset); @@ -2624,7 +2624,7 @@ macro_rules! doctest_wallet { block_id, }; let update = Update { - graph: tx_graph::Update { + tx_update: tx_graph::TxUpdate { anchors: [(anchor, txid)].into_iter().collect(), ..Default::default() }, diff --git a/crates/wallet/tests/common.rs b/crates/wallet/tests/common.rs index 561a9a5fb..375d680d1 100644 --- a/crates/wallet/tests/common.rs +++ b/crates/wallet/tests/common.rs @@ -220,7 +220,7 @@ pub fn insert_anchor_from_conf(wallet: &mut Wallet, txid: Txid, position: Confir wallet .apply_update(Update { - graph: tx_graph::Update { + tx_update: tx_graph::TxUpdate { anchors: [(anchor, txid)].into(), ..Default::default() }, diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 243161658..2da25cd28 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -83,7 +83,7 @@ fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) { use bdk_wallet::Update; wallet .apply_update(Update { - graph: tx_graph::Update { + tx_update: tx_graph::TxUpdate { seen_ats: [(txid, seen_at)].into_iter().collect(), ..Default::default() }, diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 21910cf66..9c705a3df 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -136,7 +136,7 @@ fn main() -> anyhow::Result<()> { .map(|tx_node| tx_node.tx), ); - let (chain_update, graph_update, keychain_update) = match electrum_cmd.clone() { + let (chain_update, tx_update, keychain_update) = match electrum_cmd.clone() { ElectrumCommands::Scan { stop_gap, scan_options, @@ -182,7 +182,7 @@ fn main() -> anyhow::Result<()> { .context("scanning the blockchain")?; ( res.chain_update, - res.graph_update, + res.tx_update, Some(res.last_active_indices), ) } @@ -251,7 +251,7 @@ fn main() -> anyhow::Result<()> { // drop lock on graph and chain drop((graph, chain)); - (res.chain_update, res.graph_update, None) + (res.chain_update, res.tx_update, None) } }; @@ -267,7 +267,7 @@ fn main() -> anyhow::Result<()> { let keychain_changeset = graph.index.reveal_to_target_multi(&keychain_update); indexed_tx_graph_changeset.merge(keychain_changeset.into()); } - indexed_tx_graph_changeset.merge(graph.apply_update(graph_update)); + indexed_tx_graph_changeset.merge(graph.apply_update(tx_update)); ChangeSet { local_chain: chain_changeset, diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index 7a4005878..cba86b862 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -164,7 +164,7 @@ fn main() -> anyhow::Result<()> { }; // The client scans keychain spks for transaction histories, stopping after `stop_gap` - // is reached. It returns a `TxGraph` update (`graph_update`) and a structure that + // is reached. It returns a `TxGraph` update (`tx_update`) and a structure that // represents the last active spk derivation indices of keychains // (`keychain_indices_update`). let update = client @@ -183,7 +183,7 @@ fn main() -> anyhow::Result<()> { let index_changeset = graph .index .reveal_to_target_multi(&update.last_active_indices); - let mut indexed_tx_graph_changeset = graph.apply_update(update.graph_update); + let mut indexed_tx_graph_changeset = graph.apply_update(update.tx_update); indexed_tx_graph_changeset.merge(index_changeset.into()); indexed_tx_graph_changeset }, @@ -269,7 +269,7 @@ fn main() -> anyhow::Result<()> { .lock() .unwrap() .apply_update(update.chain_update.expect("request has chain tip"))?, - graph.lock().unwrap().apply_update(update.graph_update), + graph.lock().unwrap().apply_update(update.tx_update), ) } };