diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index f65454c8..a46c7f83 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -34,7 +34,7 @@ //! # use magical_bitcoin_wallet::blockchain::electrum::ElectrumBlockchain; //! let client = electrum_client::Client::new("ssl://electrum.blockstream.info:50002", None)?; //! let blockchain = ElectrumBlockchain::from(client); -//! # Ok::<(), magical_bitcoin_wallet::error::Error>(()) +//! # Ok::<(), magical_bitcoin_wallet::Error>(()) //! ``` use std::collections::HashSet; diff --git a/src/blockchain/esplora.rs b/src/blockchain/esplora.rs index 13f05f20..999a131e 100644 --- a/src/blockchain/esplora.rs +++ b/src/blockchain/esplora.rs @@ -32,7 +32,7 @@ //! ```no_run //! # use magical_bitcoin_wallet::blockchain::esplora::EsploraBlockchain; //! let blockchain = EsploraBlockchain::new("https://blockstream.info/testnet/"); -//! # Ok::<(), magical_bitcoin_wallet::error::Error>(()) +//! # Ok::<(), magical_bitcoin_wallet::Error>(()) //! ``` use std::collections::{HashMap, HashSet}; diff --git a/src/database/memory.rs b/src/database/memory.rs index fb25c7a3..d1ecd645 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -22,6 +22,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! In-memory ephemeral database +//! +//! This module defines an in-memory database type called [`MemoryDatabase`] that is based on a +//! [`BTreeMap`]. + use std::collections::BTreeMap; use std::ops::Bound::{Excluded, Included}; @@ -91,22 +96,39 @@ impl MapKey<'_> { fn after(key: &Vec) -> Vec { let mut key = key.clone(); - let len = key.len(); - if len > 0 { - // TODO i guess it could break if the value is 0xFF, but it's fine for now - key[len - 1] += 1; + let mut idx = key.len(); + while idx > 0 { + if key[idx - 1] == 0xFF { + idx -= 1; + continue; + } else { + key[idx - 1] += 1; + break; + } } key } -#[derive(Debug)] +/// In-memory ephemeral database +/// +/// This database can be used as a temporary storage for wallets that are not kept permanently on +/// a device, or on platforms that don't provide a filesystem, like `wasm32`. +/// +/// Once it's dropped its content will be lost. +/// +/// If you are looking for a permanent storage solution, you can try with the default key-value +/// database called [`sled`]. See the [`database`] module documentation for more defailts. +/// +/// [`database`]: crate::database +#[derive(Debug, Default)] pub struct MemoryDatabase { map: BTreeMap, Box>, deleted_keys: Vec>, } impl MemoryDatabase { + /// Create a new empty database pub fn new() -> Self { MemoryDatabase { map: BTreeMap::new(), diff --git a/src/database/mod.rs b/src/database/mod.rs index c91b385e..45cedcd1 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -22,6 +22,21 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! Database types +//! +//! This module provides the implementation of some defaults database types, along with traits that +//! can be implemented externally to let [`Wallet`]s use customized databases. +//! +//! It's important to note that the databases defined here only contains "blockchain-related" data. +//! They can be seen more as a cache than a critical piece of storage that contains secrets and +//! keys. +//! +//! The currently recommended database is [`sled`], which is a pretty simple key-value embedded +//! database written in Rust. If the `key-value-db` feature is enabled (which by default is), +//! this library automatically implements all the required traits for [`sled::Tree`]. +//! +//! [`Wallet`]: crate::wallet::Wallet + use bitcoin::hash_types::Txid; use bitcoin::{OutPoint, Script, Transaction, TxOut}; @@ -34,71 +49,113 @@ pub(crate) mod keyvalue; pub mod memory; pub use memory::MemoryDatabase; +/// Trait for operations that can be batched +/// +/// This trait defines the list of operations that must be implemented on the [`Database`] type and +/// the [`BatchDatabase::Batch`] type. pub trait BatchOperations { + /// Store a script_pubkey along with its script type and child number fn set_script_pubkey( &mut self, script: &Script, script_type: ScriptType, child: u32, ) -> Result<(), Error>; + /// Store a [`UTXO`] fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>; + /// Store a raw transaction fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>; + /// Store the metadata of a transaction fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>; + /// Store the last derivation index for a given script type fn set_last_index(&mut self, script_type: ScriptType, value: u32) -> Result<(), Error>; + /// Delete a script_pubkey given the script type and its child number fn del_script_pubkey_from_path( &mut self, script_type: ScriptType, child: u32, ) -> Result, Error>; + /// Delete the data related to a specific script_pubkey, meaning the script type and the child + /// number fn del_path_from_script_pubkey( &mut self, script: &Script, ) -> Result, Error>; + /// Delete a [`UTXO`] given its [`OutPoint`] fn del_utxo(&mut self, outpoint: &OutPoint) -> Result, Error>; + /// Delete a raw transaction given its [`Txid`] fn del_raw_tx(&mut self, txid: &Txid) -> Result, Error>; + /// Delete the metadata of a transaction and optionally the raw transaction itself fn del_tx( &mut self, txid: &Txid, include_raw: bool, ) -> Result, Error>; + /// Delete the last derivation index for a script type fn del_last_index(&mut self, script_type: ScriptType) -> Result, Error>; } +/// Trait for reading data from a database +/// +/// This traits defines the operations that can be used to read data out of a database pub trait Database: BatchOperations { + /// Read and checks the descriptor checksum for a given script type + /// + /// Should return [`Error::ChecksumMismatch`](crate::error::Error::ChecksumMismatch) if the + /// checksum doesn't match. If there's no checksum in the database, simply store it for the + /// next time. fn check_descriptor_checksum>( &mut self, script_type: ScriptType, bytes: B, ) -> Result<(), Error>; + /// Return the list of script_pubkeys fn iter_script_pubkeys(&self, script_type: Option) -> Result, Error>; + /// Return the list of [`UTXO`]s fn iter_utxos(&self) -> Result, Error>; + /// Return the list of raw transactions fn iter_raw_txs(&self) -> Result, Error>; + /// Return the list of transactions metadata fn iter_txs(&self, include_raw: bool) -> Result, Error>; + /// Fetch a script_pubkey given the script type and child number fn get_script_pubkey_from_path( &self, script_type: ScriptType, child: u32, ) -> Result, Error>; + /// Fetch the script type and child number of a given script_pubkey fn get_path_from_script_pubkey( &self, script: &Script, ) -> Result, Error>; + /// Fetch a [`UTXO`] given its [`OutPoint`] fn get_utxo(&self, outpoint: &OutPoint) -> Result, Error>; + /// Fetch a raw transaction given its [`Txid`] fn get_raw_tx(&self, txid: &Txid) -> Result, Error>; + /// Fetch the transaction metadata and optionally also the raw transaction fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result, Error>; + /// Return the last defivation index for a script type fn get_last_index(&self, script_type: ScriptType) -> Result, Error>; - // inserts 0 if not present + /// Increment the last derivation index for a script type and returns it + /// + /// It should insert and return `0` if not present in the database fn increment_last_index(&mut self, script_type: ScriptType) -> Result; } +/// Trait for a database that supports batch operations +/// +/// This trait defines the methods to start and apply a batch of operations. pub trait BatchDatabase: Database { + /// Container for the operations type Batch: BatchOperations; + /// Create a new batch container fn begin_batch(&self) -> Self::Batch; + /// Consume and apply a batch of operations fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>; } diff --git a/src/descriptor/checksum.rs b/src/descriptor/checksum.rs index 63a55443..15f80055 100644 --- a/src/descriptor/checksum.rs +++ b/src/descriptor/checksum.rs @@ -22,6 +22,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! Descriptor checksum +//! +//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the +//! checksum of a descriptor + use std::iter::FromIterator; use crate::descriptor::Error; @@ -51,6 +56,7 @@ fn poly_mod(mut c: u64, val: u64) -> u64 { c } +/// Compute the checksum of a descriptor pub fn get_checksum(desc: &str) -> Result { let mut c = 1; let mut cls = 0; diff --git a/src/descriptor/error.rs b/src/descriptor/error.rs index 08bc2baa..47a2473d 100644 --- a/src/descriptor/error.rs +++ b/src/descriptor/error.rs @@ -22,6 +22,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! Descriptor errors + +/// Errors related to the parsing and usage of descriptors #[derive(Debug)] pub enum Error { InternalError, diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 7ab1896f..d75cdbfd 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -22,6 +22,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! Descriptors +//! +//! This module contains generic utilities to work with descriptors, plus some re-exported types +//! from [`miniscript`]. + use std::collections::{BTreeMap, HashMap}; use std::fmt; use std::sync::Arc; @@ -46,9 +51,17 @@ use self::error::Error; pub use self::policy::Policy; use crate::wallet::signer::SignersContainer; +/// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`] pub type ExtendedDescriptor = Descriptor; + +/// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or +/// [`psbt::Output`] +/// +/// [`psbt::Input`]: bitcoin::util::psbt::Input +/// [`psbt::Output`]: bitcoin::util::psbt::Output pub type HDKeyPaths = BTreeMap; +/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`] pub trait ExtractPolicy { fn extract_policy( &self, @@ -56,7 +69,7 @@ pub trait ExtractPolicy { ) -> Result, Error>; } -pub trait XKeyUtils { +pub(crate) trait XKeyUtils { fn full_path(&self, append: &[ChildNumber]) -> DerivationPath; fn root_fingerprint(&self) -> Fingerprint; } @@ -91,7 +104,7 @@ impl XKeyUtils for DescriptorXKey { } } -pub trait DescriptorMeta: Sized { +pub(crate) trait DescriptorMeta: Sized { fn is_witness(&self) -> bool; fn get_hd_keypaths(&self, index: u32) -> Result; fn is_fixed(&self) -> bool; @@ -100,7 +113,7 @@ pub trait DescriptorMeta: Sized { -> Option; } -pub trait DescriptorScripts { +pub(crate) trait DescriptorScripts { fn psbt_redeem_script(&self) -> Option