From: 志宇 Date: Fri, 23 Aug 2024 15:08:55 +0000 (+0000) Subject: feat!: introduce `bdk_core` X-Git-Tag: v1.0.0-beta.2~2^2~8 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/struct.EncoderStringWriter.html?a=commitdiff_plain;h=6f7626ad0bb6ca7e45428af11dbf930d746d9cd0;p=bdk 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. --- diff --git a/Cargo.toml b/Cargo.toml index 1c29bbaf..ef6d55e9 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 7261bdfa..42a77441 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 8ce6e31a..ce6076c5 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 8077e211..c71b6cfe 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 029eedc2..c1c55596 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 d8ef65c4..2cc4481c 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 e31b431d..75fb6698 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 8a324f6a..d3d562bf 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 e953580c..206aaf11 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 df5e6a62..4ce6772b 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 00000000..d74cf906 --- /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 00000000..2e64c9cb --- /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 00000000..1c535840 --- /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 393f9d3f..6a97252f 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},