diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 4db035fb6..1a97500b2 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -19,7 +19,7 @@ use alloc::{ sync::Arc, vec::Vec, }; -pub use bdk_chain::keychain::Balance; +pub use bdk_chain::Balance; use bdk_chain::{ indexed_tx_graph, keychain::{self, KeychainTxOutIndex}, diff --git a/crates/bitcoind_rpc/tests/test_emitter.rs b/crates/bitcoind_rpc/tests/test_emitter.rs index 52d709301..f07e9050d 100644 --- a/crates/bitcoind_rpc/tests/test_emitter.rs +++ b/crates/bitcoind_rpc/tests/test_emitter.rs @@ -3,9 +3,8 @@ use std::collections::{BTreeMap, BTreeSet}; use bdk_bitcoind_rpc::Emitter; use bdk_chain::{ bitcoin::{Address, Amount, BlockHash, Txid}, - keychain::Balance, local_chain::{self, CheckPoint, LocalChain}, - Append, BlockId, IndexedTxGraph, SpkTxOutIndex, + Append, Balance, BlockId, IndexedTxGraph, SpkTxOutIndex, }; use bitcoin::{ address::NetworkChecked, block::Header, hash_types::TxMerkleNode, hashes::Hash, diff --git a/crates/chain/src/balance.rs b/crates/chain/src/balance.rs new file mode 100644 index 000000000..2400a905e --- /dev/null +++ b/crates/chain/src/balance.rs @@ -0,0 +1,55 @@ +/// Balance, differentiated into various categories. +#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(crate = "serde_crate",) +)] +pub struct Balance { + /// All coinbase outputs not yet matured + pub immature: u64, + /// Unconfirmed UTXOs generated by a wallet tx + pub trusted_pending: u64, + /// Unconfirmed UTXOs received from an external wallet + pub untrusted_pending: u64, + /// Confirmed and immediately spendable balance + pub confirmed: u64, +} + +impl Balance { + /// Get sum of trusted_pending and confirmed coins. + /// + /// This is the balance you can spend right now that shouldn't get cancelled via another party + /// double spending it. + pub fn trusted_spendable(&self) -> u64 { + self.confirmed + self.trusted_pending + } + + /// Get the whole balance visible to the wallet. + pub fn total(&self) -> u64 { + self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature + } +} + +impl core::fmt::Display for Balance { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}", + self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed + ) + } +} + +impl core::ops::Add for Balance { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + immature: self.immature + other.immature, + trusted_pending: self.trusted_pending + other.trusted_pending, + untrusted_pending: self.untrusted_pending + other.untrusted_pending, + confirmed: self.confirmed + other.confirmed, + } + } +} diff --git a/crates/chain/src/chain_data.rs b/crates/chain/src/chain_data.rs index ae0976de5..9ee22fd7b 100644 --- a/crates/chain/src/chain_data.rs +++ b/crates/chain/src/chain_data.rs @@ -1,7 +1,8 @@ +use crate::collections::{BTreeMap, BTreeSet}; +use crate::COINBASE_MATURITY; +use alloc::vec::Vec; use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid}; -use crate::{Anchor, AnchorFromBlockPosition, COINBASE_MATURITY}; - /// Represents the observed position of some chain data. /// /// The generic `A` should be a [`Anchor`] implementation. @@ -299,6 +300,183 @@ impl FullTxOut { } } +/// Trait that "anchors" blockchain data to a specific block of height and hash. +/// +/// If transaction A is anchored in block B, and block B is in the best chain, we can +/// assume that transaction A is also confirmed in the best chain. This does not necessarily mean +/// that transaction A is confirmed in block B. It could also mean transaction A is confirmed in a +/// parent block of B. +/// +/// Every [`Anchor`] implementation must contain a [`BlockId`] parameter, and must implement +/// [`Ord`]. When implementing [`Ord`], the anchors' [`BlockId`]s should take precedence +/// over other elements inside the [`Anchor`]s for comparison purposes, i.e., you should first +/// compare the anchors' [`BlockId`]s and then care about the rest. +/// +/// The example shows different types of anchors: +/// ``` +/// # use bdk_chain::local_chain::LocalChain; +/// # use bdk_chain::tx_graph::TxGraph; +/// # use bdk_chain::BlockId; +/// # use bdk_chain::ConfirmationHeightAnchor; +/// # use bdk_chain::ConfirmationTimeHeightAnchor; +/// # use bdk_chain::example_utils::*; +/// # use bitcoin::hashes::Hash; +/// // Initialize the local chain with two blocks. +/// let chain = LocalChain::from_blocks( +/// [ +/// (1, Hash::hash("first".as_bytes())), +/// (2, Hash::hash("second".as_bytes())), +/// ] +/// .into_iter() +/// .collect(), +/// ); +/// +/// // Transaction to be inserted into `TxGraph`s with different anchor types. +/// let tx = tx_from_hex(RAW_TX_1); +/// +/// // Insert `tx` into a `TxGraph` that uses `BlockId` as the anchor type. +/// // When a transaction is anchored with `BlockId`, the anchor block and the confirmation block of +/// // the transaction is the same block. +/// let mut graph_a = TxGraph::::default(); +/// let _ = graph_a.insert_tx(tx.clone()); +/// graph_a.insert_anchor( +/// tx.txid(), +/// BlockId { +/// height: 1, +/// hash: Hash::hash("first".as_bytes()), +/// }, +/// ); +/// +/// // Insert `tx` into a `TxGraph` that uses `ConfirmationHeightAnchor` as the anchor type. +/// // This anchor records the anchor block and the confirmation height of the transaction. +/// // When a transaction is anchored with `ConfirmationHeightAnchor`, the anchor block and +/// // confirmation block can be different. However, the confirmation block cannot be higher than +/// // the anchor block and both blocks must be in the same chain for the anchor to be valid. +/// let mut graph_b = TxGraph::::default(); +/// let _ = graph_b.insert_tx(tx.clone()); +/// graph_b.insert_anchor( +/// tx.txid(), +/// ConfirmationHeightAnchor { +/// anchor_block: BlockId { +/// height: 2, +/// hash: Hash::hash("second".as_bytes()), +/// }, +/// confirmation_height: 1, +/// }, +/// ); +/// +/// // Insert `tx` into a `TxGraph` that uses `ConfirmationTimeHeightAnchor` as the anchor type. +/// // This anchor records the anchor block, the confirmation height and time of the transaction. +/// // When a transaction is anchored with `ConfirmationTimeHeightAnchor`, the anchor block and +/// // confirmation block can be different. However, the confirmation block cannot be higher than +/// // the anchor block and both blocks must be in the same chain for the anchor to be valid. +/// let mut graph_c = TxGraph::::default(); +/// let _ = graph_c.insert_tx(tx.clone()); +/// graph_c.insert_anchor( +/// tx.txid(), +/// ConfirmationTimeHeightAnchor { +/// anchor_block: BlockId { +/// height: 2, +/// hash: Hash::hash("third".as_bytes()), +/// }, +/// confirmation_height: 1, +/// confirmation_time: 123, +/// }, +/// ); +/// ``` +pub trait Anchor: core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash { + /// Returns the [`BlockId`] that the associated blockchain data is "anchored" in. + fn anchor_block(&self) -> BlockId; + + /// Get the upper bound of the chain data's confirmation height. + /// + /// The default definition gives a pessimistic answer. This can be overridden by the `Anchor` + /// implementation for a more accurate value. + fn confirmation_height_upper_bound(&self) -> u32 { + self.anchor_block().height + } +} + +impl<'a, A: Anchor> Anchor for &'a A { + fn anchor_block(&self) -> BlockId { + ::anchor_block(self) + } +} + +/// An [`Anchor`] that can be constructed from a given block, block height and transaction position +/// within the block. +pub trait AnchorFromBlockPosition: Anchor { + /// Construct the anchor from a given `block`, block height and `tx_pos` within the block. + fn from_block_position(block: &bitcoin::Block, block_id: BlockId, tx_pos: usize) -> Self; +} + +/// Trait that makes an object appendable. +pub trait Append { + /// Append another object of the same type onto `self`. + fn append(&mut self, other: Self); + + /// Returns whether the structure is considered empty. + fn is_empty(&self) -> bool; +} + +impl Append for BTreeMap { + fn append(&mut self, mut other: Self) { + BTreeMap::append(self, &mut other) + } + + fn is_empty(&self) -> bool { + BTreeMap::is_empty(self) + } +} + +impl Append for BTreeSet { + fn append(&mut self, mut other: Self) { + BTreeSet::append(self, &mut other) + } + + fn is_empty(&self) -> bool { + BTreeSet::is_empty(self) + } +} + +impl Append for Vec { + fn append(&mut self, mut other: Self) { + Vec::append(self, &mut other) + } + + fn is_empty(&self) -> bool { + Vec::is_empty(self) + } +} + +macro_rules! impl_append_for_tuple { + ($($a:ident $b:tt)*) => { + impl<$($a),*> Append for ($($a,)*) where $($a: Append),* { + + fn append(&mut self, _other: Self) { + $(Append::append(&mut self.$b, _other.$b) );* + } + + fn is_empty(&self) -> bool { + $(Append::is_empty(&self.$b) && )* true + } + } + } +} + +impl_append_for_tuple!(); +impl_append_for_tuple!(T0 0); +impl_append_for_tuple!(T0 0 T1 1); +impl_append_for_tuple!(T0 0 T1 1 T2 2); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9 T10 10); + #[cfg(test)] mod test { use super::*; diff --git a/crates/chain/src/keychain.rs b/crates/chain/src/keychain.rs index c20d1f6cd..e8d37438d 100644 --- a/crates/chain/src/keychain.rs +++ b/crates/chain/src/keychain.rs @@ -81,62 +81,6 @@ impl AsRef> for ChangeSet { } } -/// Balance, differentiated into various categories. -#[derive(Debug, PartialEq, Eq, Clone, Default)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(crate = "serde_crate",) -)] -pub struct Balance { - /// All coinbase outputs not yet matured - pub immature: u64, - /// Unconfirmed UTXOs generated by a wallet tx - pub trusted_pending: u64, - /// Unconfirmed UTXOs received from an external wallet - pub untrusted_pending: u64, - /// Confirmed and immediately spendable balance - pub confirmed: u64, -} - -impl Balance { - /// Get sum of trusted_pending and confirmed coins. - /// - /// This is the balance you can spend right now that shouldn't get cancelled via another party - /// double spending it. - pub fn trusted_spendable(&self) -> u64 { - self.confirmed + self.trusted_pending - } - - /// Get the whole balance visible to the wallet. - pub fn total(&self) -> u64 { - self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature - } -} - -impl core::fmt::Display for Balance { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}", - self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed - ) - } -} - -impl core::ops::Add for Balance { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { - immature: self.immature + other.immature, - trusted_pending: self.trusted_pending + other.trusted_pending, - untrusted_pending: self.untrusted_pending + other.untrusted_pending, - confirmed: self.confirmed + other.confirmed, - } - } -} - #[cfg(test)] mod test { use super::*; diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 206566971..846dcd23f 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -23,15 +23,15 @@ pub use bitcoin; mod spk_txout_index; pub use spk_txout_index::*; +mod balance; mod chain_data; pub use chain_data::*; pub mod indexed_tx_graph; +pub use balance::*; pub use indexed_tx_graph::IndexedTxGraph; pub mod keychain; pub mod local_chain; -mod tx_data_traits; pub mod tx_graph; -pub use tx_data_traits::*; pub use tx_graph::TxGraph; mod chain_oracle; pub use chain_oracle::*; diff --git a/crates/chain/src/tx_data_traits.rs b/crates/chain/src/tx_data_traits.rs deleted file mode 100644 index 8fa17ff90..000000000 --- a/crates/chain/src/tx_data_traits.rs +++ /dev/null @@ -1,185 +0,0 @@ -use crate::collections::BTreeMap; -use crate::collections::BTreeSet; -use crate::BlockId; -use alloc::vec::Vec; - -/// Trait that "anchors" blockchain data to a specific block of height and hash. -/// -/// If transaction A is anchored in block B, and block B is in the best chain, we can -/// assume that transaction A is also confirmed in the best chain. This does not necessarily mean -/// that transaction A is confirmed in block B. It could also mean transaction A is confirmed in a -/// parent block of B. -/// -/// Every [`Anchor`] implementation must contain a [`BlockId`] parameter, and must implement -/// [`Ord`]. When implementing [`Ord`], the anchors' [`BlockId`]s should take precedence -/// over other elements inside the [`Anchor`]s for comparison purposes, i.e., you should first -/// compare the anchors' [`BlockId`]s and then care about the rest. -/// -/// The example shows different types of anchors: -/// ``` -/// # use bdk_chain::local_chain::LocalChain; -/// # use bdk_chain::tx_graph::TxGraph; -/// # use bdk_chain::BlockId; -/// # use bdk_chain::ConfirmationHeightAnchor; -/// # use bdk_chain::ConfirmationTimeHeightAnchor; -/// # use bdk_chain::example_utils::*; -/// # use bitcoin::hashes::Hash; -/// // Initialize the local chain with two blocks. -/// let chain = LocalChain::from_blocks( -/// [ -/// (1, Hash::hash("first".as_bytes())), -/// (2, Hash::hash("second".as_bytes())), -/// ] -/// .into_iter() -/// .collect(), -/// ); -/// -/// // Transaction to be inserted into `TxGraph`s with different anchor types. -/// let tx = tx_from_hex(RAW_TX_1); -/// -/// // Insert `tx` into a `TxGraph` that uses `BlockId` as the anchor type. -/// // When a transaction is anchored with `BlockId`, the anchor block and the confirmation block of -/// // the transaction is the same block. -/// let mut graph_a = TxGraph::::default(); -/// let _ = graph_a.insert_tx(tx.clone()); -/// graph_a.insert_anchor( -/// tx.txid(), -/// BlockId { -/// height: 1, -/// hash: Hash::hash("first".as_bytes()), -/// }, -/// ); -/// -/// // Insert `tx` into a `TxGraph` that uses `ConfirmationHeightAnchor` as the anchor type. -/// // This anchor records the anchor block and the confirmation height of the transaction. -/// // When a transaction is anchored with `ConfirmationHeightAnchor`, the anchor block and -/// // confirmation block can be different. However, the confirmation block cannot be higher than -/// // the anchor block and both blocks must be in the same chain for the anchor to be valid. -/// let mut graph_b = TxGraph::::default(); -/// let _ = graph_b.insert_tx(tx.clone()); -/// graph_b.insert_anchor( -/// tx.txid(), -/// ConfirmationHeightAnchor { -/// anchor_block: BlockId { -/// height: 2, -/// hash: Hash::hash("second".as_bytes()), -/// }, -/// confirmation_height: 1, -/// }, -/// ); -/// -/// // Insert `tx` into a `TxGraph` that uses `ConfirmationTimeHeightAnchor` as the anchor type. -/// // This anchor records the anchor block, the confirmation height and time of the transaction. -/// // When a transaction is anchored with `ConfirmationTimeHeightAnchor`, the anchor block and -/// // confirmation block can be different. However, the confirmation block cannot be higher than -/// // the anchor block and both blocks must be in the same chain for the anchor to be valid. -/// let mut graph_c = TxGraph::::default(); -/// let _ = graph_c.insert_tx(tx.clone()); -/// graph_c.insert_anchor( -/// tx.txid(), -/// ConfirmationTimeHeightAnchor { -/// anchor_block: BlockId { -/// height: 2, -/// hash: Hash::hash("third".as_bytes()), -/// }, -/// confirmation_height: 1, -/// confirmation_time: 123, -/// }, -/// ); -/// ``` -pub trait Anchor: core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash { - /// Returns the [`BlockId`] that the associated blockchain data is "anchored" in. - fn anchor_block(&self) -> BlockId; - - /// Get the upper bound of the chain data's confirmation height. - /// - /// The default definition gives a pessimistic answer. This can be overridden by the `Anchor` - /// implementation for a more accurate value. - fn confirmation_height_upper_bound(&self) -> u32 { - self.anchor_block().height - } -} - -impl<'a, A: Anchor> Anchor for &'a A { - fn anchor_block(&self) -> BlockId { - ::anchor_block(self) - } -} - -/// An [`Anchor`] that can be constructed from a given block, block height and transaction position -/// within the block. -pub trait AnchorFromBlockPosition: Anchor { - /// Construct the anchor from a given `block`, block height and `tx_pos` within the block. - fn from_block_position(block: &bitcoin::Block, block_id: BlockId, tx_pos: usize) -> Self; -} - -/// Trait that makes an object appendable. -pub trait Append { - /// Append another object of the same type onto `self`. - fn append(&mut self, other: Self); - - /// Returns whether the structure is considered empty. - fn is_empty(&self) -> bool; -} - -impl Append for BTreeMap { - fn append(&mut self, other: Self) { - // We use `extend` instead of `BTreeMap::append` due to performance issues with `append`. - // Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420 - BTreeMap::extend(self, other) - } - - fn is_empty(&self) -> bool { - BTreeMap::is_empty(self) - } -} - -impl Append for BTreeSet { - fn append(&mut self, other: Self) { - // We use `extend` instead of `BTreeMap::append` due to performance issues with `append`. - // Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420 - BTreeSet::extend(self, other) - } - - fn is_empty(&self) -> bool { - BTreeSet::is_empty(self) - } -} - -impl Append for Vec { - fn append(&mut self, mut other: Self) { - Vec::append(self, &mut other) - } - - fn is_empty(&self) -> bool { - Vec::is_empty(self) - } -} - -macro_rules! impl_append_for_tuple { - ($($a:ident $b:tt)*) => { - impl<$($a),*> Append for ($($a,)*) where $($a: Append),* { - - fn append(&mut self, _other: Self) { - $(Append::append(&mut self.$b, _other.$b) );* - } - - fn is_empty(&self) -> bool { - $(Append::is_empty(&self.$b) && )* true - } - } - } -} - -impl_append_for_tuple!(); -impl_append_for_tuple!(T0 0); -impl_append_for_tuple!(T0 0 T1 1); -impl_append_for_tuple!(T0 0 T1 1 T2 2); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9); -impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9 T10 10); diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 34cbccf5c..cba76bdb3 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -76,8 +76,8 @@ //! [`insert_txout`]: TxGraph::insert_txout use crate::{ - collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId, - ChainOracle, ChainPosition, FullTxOut, + collections::*, local_chain::LocalChain, Anchor, Append, Balance, BlockId, ChainOracle, + ChainPosition, FullTxOut, }; use alloc::collections::vec_deque::VecDeque; use alloc::vec::Vec; diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 41b1d4d3e..ca43a39b5 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -5,9 +5,9 @@ use std::collections::BTreeSet; use bdk_chain::{ indexed_tx_graph::{self, IndexedTxGraph}, - keychain::{self, Balance, KeychainTxOutIndex}, + keychain::{self, KeychainTxOutIndex}, local_chain::LocalChain, - tx_graph, BlockId, ChainPosition, ConfirmationHeightAnchor, + tx_graph, Balance, BlockId, ChainPosition, ConfirmationHeightAnchor, }; use bitcoin::{secp256k1::Secp256k1, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut}; use miniscript::Descriptor; diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index 8ac440f3e..287dd1814 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -3,7 +3,7 @@ mod common; use std::collections::{BTreeSet, HashSet}; -use bdk_chain::{keychain::Balance, BlockId}; +use bdk_chain::{Balance, BlockId}; use bitcoin::{OutPoint, Script}; use common::*;