Write more docs

This commit is contained in:
Alekos Filini 2020-09-04 11:44:49 +02:00
parent 6b9c363937
commit 7065c1fed6
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
14 changed files with 341 additions and 17 deletions

View File

@ -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;

View File

@ -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};

View File

@ -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<u8>) -> Vec<u8> {
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<Vec<u8>, Box<dyn std::any::Any>>,
deleted_keys: Vec<Vec<u8>>,
}
impl MemoryDatabase {
/// Create a new empty database
pub fn new() -> Self {
MemoryDatabase {
map: BTreeMap::new(),

View File

@ -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<Option<Script>, 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<Option<(ScriptType, u32)>, Error>;
/// Delete a [`UTXO`] given its [`OutPoint`]
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
/// Delete a raw transaction given its [`Txid`]
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Delete the metadata of a transaction and optionally the raw transaction itself
fn del_tx(
&mut self,
txid: &Txid,
include_raw: bool,
) -> Result<Option<TransactionDetails>, Error>;
/// Delete the last derivation index for a script type
fn del_last_index(&mut self, script_type: ScriptType) -> Result<Option<u32>, 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<B: AsRef<[u8]>>(
&mut self,
script_type: ScriptType,
bytes: B,
) -> Result<(), Error>;
/// Return the list of script_pubkeys
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Result<Vec<Script>, Error>;
/// Return the list of [`UTXO`]s
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>;
/// Return the list of raw transactions
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
/// Return the list of transactions metadata
fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, 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<Option<Script>, Error>;
/// Fetch the script type and child number of a given script_pubkey
fn get_path_from_script_pubkey(
&self,
script: &Script,
) -> Result<Option<(ScriptType, u32)>, Error>;
/// Fetch a [`UTXO`] given its [`OutPoint`]
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
/// Fetch a raw transaction given its [`Txid`]
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Fetch the transaction metadata and optionally also the raw transaction
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
/// Return the last defivation index for a script type
fn get_last_index(&self, script_type: ScriptType) -> Result<Option<u32>, 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<u32, Error>;
}
/// 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>;
}

View File

@ -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<String, Error> {
let mut c = 1;
let mut cls = 0;

View File

@ -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,

View File

@ -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<DescriptorPublicKey>;
/// 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<PublicKey, (Fingerprint, DerivationPath)>;
/// 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<Option<Policy>, 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<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
}
}
pub trait DescriptorMeta: Sized {
pub(crate) trait DescriptorMeta: Sized {
fn is_witness(&self) -> bool;
fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error>;
fn is_fixed(&self) -> bool;
@ -100,7 +113,7 @@ pub trait DescriptorMeta: Sized {
-> Option<Self>;
}
pub trait DescriptorScripts {
pub(crate) trait DescriptorScripts {
fn psbt_redeem_script(&self) -> Option<Script>;
fn psbt_witness_script(&self) -> Option<Script>;
}

View File

@ -22,6 +22,27 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptor policy
//!
//! This module implements the logic to extract and represent the spending policies of a descriptor
//! in a more human-readable format.
//!
//! ## Example
//!
//! ```
//! # use std::sync::Arc;
//! # use magical_bitcoin_wallet::descriptor::*;
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
//!
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_secret(desc)?;
//! println!("{:?}", extended_desc);
//!
//! let signers = Arc::new(key_map.into());
//! let policy = extended_desc.extract_policy(signers)?;
//! println!("policy: {}", serde_json::to_string(&policy)?);
//! # Ok::<(), magical_bitcoin_wallet::Error>(())
//! ```
use std::cmp::max;
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::fmt;
@ -47,6 +68,7 @@ use super::checksum::get_checksum;
use super::error::Error;
use super::XKeyUtils;
/// Raw public key or extended key fingerprint
#[derive(Debug, Clone, Default, Serialize)]
pub struct PKOrF {
#[serde(skip_serializing_if = "Option::is_none")]
@ -79,6 +101,7 @@ impl PKOrF {
}
}
/// An item that need to be satisfied
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
@ -208,30 +231,44 @@ where
map.end()
}
/// Represent if and how much a policy item is satisfied by the wallet's descriptor
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum Satisfaction {
/// Only a partial satisfaction of some kind of threshold policy
Partial {
/// Total number of items
n: usize,
/// Threshold
m: usize,
/// The items that can be satisfied by the descriptor
items: Vec<usize>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
/// Extra conditions that also need to be satisfied
conditions: ConditionMap,
},
/// Can reach the threshold of some kind of threshold policy
PartialComplete {
/// Total number of items
n: usize,
/// Threshold
m: usize,
/// The items that can be satisfied by the descriptor
items: Vec<usize>,
#[serde(
serialize_with = "serialize_folded_cond_map",
skip_serializing_if = "BTreeMap::is_empty"
)]
/// Extra conditions that also need to be satisfied
conditions: FoldedConditionMap,
},
/// Can satisfy the policy item
Complete {
/// Extra conditions that also need to be satisfied
condition: Condition,
},
/// Cannot satisfy or contribute to the policy item
None,
}
@ -363,6 +400,7 @@ impl From<bool> for Satisfaction {
}
}
/// Descriptor spending policy
#[derive(Debug, Clone, Serialize)]
pub struct Policy {
id: String,
@ -373,6 +411,7 @@ pub struct Policy {
contribution: Satisfaction,
}
/// An extra condition that must be satisfied but that is out of control of the user
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
pub struct Condition {
#[serde(skip_serializing_if = "Option::is_none")]
@ -413,6 +452,7 @@ impl Condition {
}
}
/// Errors that can happen while extracting and manipulating policies
#[derive(Debug)]
pub enum PolicyError {
NotEnoughItemsSelected(String),
@ -519,11 +559,19 @@ impl Policy {
Ok(Some(policy))
}
/// Return whether or not a specific path in the policy tree is required to unambiguously
/// create a transaction
///
/// What this means is that for some spending policies the user should select which paths in
/// the tree it intends to satisfy while signing, because the transaction must be created differently based
/// on that.
pub fn requires_path(&self) -> bool {
self.get_requirements(&BTreeMap::new()).is_err()
self.get_condition(&BTreeMap::new()).is_err()
}
pub fn get_requirements(
/// Return the conditions that are set by the spending policy for a given path in the
/// policy tree
pub fn get_condition(
&self,
path: &BTreeMap<String, Vec<usize>>,
) -> Result<Condition, PolicyError> {
@ -544,7 +592,7 @@ impl Policy {
SatisfiableItem::Thresh { items, threshold } => {
let mapped_req = items
.iter()
.map(|i| i.get_requirements(path))
.map(|i| i.get_condition(path))
.collect::<Result<Vec<_>, _>>()?;
// if all the requirements are null we don't care about `selected` because there

View File

@ -26,6 +26,7 @@ use std::fmt;
use bitcoin::{Address, OutPoint};
/// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet)
#[derive(Debug)]
pub enum Error {
KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),

View File

@ -66,7 +66,7 @@ extern crate testutils_macros;
extern crate serial_test;
#[macro_use]
pub mod error;
pub(crate) mod error;
pub mod blockchain;
pub mod database;
pub mod descriptor;
@ -74,6 +74,7 @@ pub(crate) mod psbt;
pub(crate) mod types;
pub mod wallet;
pub use descriptor::HDKeyPaths;
pub use error::Error;
pub use types::*;
pub use wallet::address_validator;

View File

@ -29,7 +29,7 @@ use bitcoin::hash_types::Txid;
use serde::{Deserialize, Serialize};
// TODO serde flatten?
/// Types of script
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScriptType {
External = 0,
@ -58,23 +58,28 @@ impl AsRef<[u8]> for ScriptType {
}
}
/// Fee rate
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
// Internally stored as satoshi/vbyte
pub struct FeeRate(f32);
impl FeeRate {
/// Create a new instance of [`FeeRate`] given a float fee rate in btc/kvbytes
pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self {
FeeRate(btc_per_kvb * 1e5)
}
/// Create a new instance of [`FeeRate`] given a float fee rate in satoshi/vbyte
pub fn from_sat_per_vb(sat_per_vb: f32) -> Self {
FeeRate(sat_per_vb)
}
/// Create a new [`FeeRate`] with the default min relay fee value
pub fn default_min_relay_fee() -> Self {
FeeRate(1.0)
}
/// Return the value as satoshi/vbyte
pub fn as_sat_vb(&self) -> f32 {
self.0
}
@ -86,6 +91,7 @@ impl std::default::Default for FeeRate {
}
}
/// A wallet unspent output
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct UTXO {
pub outpoint: OutPoint,
@ -93,6 +99,7 @@ pub struct UTXO {
pub is_internal: bool,
}
/// A wallet transaction
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct TransactionDetails {
pub transaction: Option<Transaction>,

View File

@ -22,6 +22,58 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Address validation callbacks
//!
//! The typical usage of those callbacks is for displaying the newly-generated address on a
//! hardware wallet, so that the user can cross-check its correctness.
//!
//! More generally speaking though, these callbacks can also be used to "do something" every time
//! an address is generated, without necessarily checking or validating it.
//!
//! An address validator can be attached to a [`Wallet`](super::Wallet) by using the
//! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and
//! whenever a new address is generated (either explicitly by the user with
//! [`Wallet::get_new_address`](super::Wallet::get_new_address) or internally to create a change
//! address) all the attached validators will be polled, in sequence. All of them must complete
//! successfully to continue.
//!
//! ## Example
//!
//! ```
//! # use std::sync::Arc;
//! # use bitcoin::*;
//! # use magical_bitcoin_wallet::address_validator::*;
//! # use magical_bitcoin_wallet::database::*;
//! # use magical_bitcoin_wallet::*;
//! struct PrintAddressAndContinue;
//!
//! impl AddressValidator for PrintAddressAndContinue {
//! fn validate(
//! &self,
//! script_type: ScriptType,
//! hd_keypaths: &HDKeyPaths,
//! script: &Script
//! ) -> Result<(), AddressValidatorError> {
//! let address = Address::from_script(script, Network::Testnet)
//! .as_ref()
//! .map(Address::to_string)
//! .unwrap_or(script.to_string());
//! println!("New address of type {:?}: {}", script_type, address);
//! println!("HD keypaths: {:#?}", hd_keypaths);
//!
//! Ok(())
//! }
//! }
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_address_validator(Arc::new(Box::new(PrintAddressAndContinue)));
//!
//! let address = wallet.get_new_address()?;
//! println!("Address: {}", address);
//! # Ok::<(), magical_bitcoin_wallet::Error>(())
//! ```
use std::fmt;
use bitcoin::Script;
@ -29,12 +81,14 @@ use bitcoin::Script;
use crate::descriptor::HDKeyPaths;
use crate::types::ScriptType;
/// Errors that can be returned to fail the validation of an address
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddressValidatorError {
UserRejected,
ConnectionError,
TimeoutError,
InvalidScript,
Message(String),
}
impl fmt::Display for AddressValidatorError {
@ -45,7 +99,15 @@ impl fmt::Display for AddressValidatorError {
impl std::error::Error for AddressValidatorError {}
/// Trait to build address validators
///
/// All the address validators attached to a wallet with [`Wallet::add_address_validator`](super::Wallet::add_address_validator) will be polled
/// every time an address (external or internal) is generated by the wallet. Errors returned in the
/// validator will be propagated up to the original caller that triggered the address generation.
///
/// For a usage example see [this module](crate::address_validator)'s documentation.
pub trait AddressValidator {
/// Validate or inspect an address
fn validate(
&self,
script_type: ScriptType,

View File

@ -22,22 +22,122 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Coin selection
//!
//! This module provides the trait [`CoinSelectionAlgorithm`] that can be implemented to
//! define custom coin selection algorithms.
//!
//! The coin selection algorithm is not globally part of a [`Wallet`](super::Wallet), instead it
//! is selected whenever a [`Wallet::create_tx`](super::Wallet::create_tx) call is made, through
//! the use of the [`TxBuilder`] structure, specifically with
//! [`TxBuilder::coin_selection`](super::tx_builder::TxBuilder::coin_selection) method.
//!
//! The [`DefaultCoinSelectionAlgorithm`] selects the default coin selection algorithm that
//! [`TxBuilder`] uses, if it's not explicitly overridden.
//!
//! [`TxBuilder`]: super::tx_builder::TxBuilder
//!
//! ## Example
//!
//! ```no_run
//! # use std::str::FromStr;
//! # use bitcoin::*;
//! # use bitcoin::consensus::serialize;
//! # use magical_bitcoin_wallet::wallet::coin_selection::*;
//! # use magical_bitcoin_wallet::*;
//! #[derive(Debug)]
//! struct AlwaysSpendEverything;
//!
//! impl CoinSelectionAlgorithm for AlwaysSpendEverything {
//! fn coin_select(
//! &self,
//! utxos: Vec<UTXO>,
//! _use_all_utxos: bool,
//! fee_rate: FeeRate,
//! amount_needed: u64,
//! input_witness_weight: usize,
//! fee_amount: f32,
//! ) -> Result<CoinSelectionResult, magical_bitcoin_wallet::Error> {
//! let selected_amount = utxos.iter().fold(0, |acc, utxo| acc + utxo.txout.value);
//! let all_utxos_selected = utxos
//! .into_iter()
//! .map(|utxo| {
//! (
//! TxIn {
//! previous_output: utxo.outpoint,
//! ..Default::default()
//! },
//! utxo.txout.script_pubkey,
//! )
//! })
//! .collect::<Vec<_>>();
//! let additional_weight = all_utxos_selected.iter().fold(0, |acc, (txin, _)| {
//! acc + serialize(txin).len() * 4 + input_witness_weight
//! });
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
//!
//! if (fee_amount + additional_fees).ceil() as u64 + amount_needed > selected_amount {
//! return Err(magical_bitcoin_wallet::Error::InsufficientFunds);
//! }
//!
//! Ok(CoinSelectionResult {
//! txin: all_utxos_selected,
//! selected_amount,
//! fee_amount: fee_amount + additional_fees,
//! })
//! }
//! }
//!
//! # let wallet: OfflineWallet<_> = Wallet::new_offline("", None, Network::Testnet, magical_bitcoin_wallet::database::MemoryDatabase::default())?;
//! // create wallet, sync, ...
//!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! let (psbt, details) = wallet.create_tx(
//! TxBuilder::with_recipients(vec![(to_address, 50_000)])
//! .coin_selection(AlwaysSpendEverything),
//! )?;
//!
//! // inspect, sign, broadcast, ...
//!
//! # Ok::<(), magical_bitcoin_wallet::Error>(())
//! ```
use bitcoin::consensus::encode::serialize;
use bitcoin::{Script, TxIn};
use crate::error::Error;
use crate::types::{FeeRate, UTXO};
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
/// overridden
pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection;
/// Result of a successful coin selection
#[derive(Debug)]
pub struct CoinSelectionResult {
/// List of inputs to use, with the respective previous script_pubkey
pub txin: Vec<(TxIn, Script)>,
/// Sum of the selected inputs' value
pub selected_amount: u64,
/// Total fee amount in satoshi
pub fee_amount: f32,
}
/// Trait for generalized coin selection algorithms
///
/// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin
/// selection algorithm when it creates transactions.
///
/// For an example see [this module](crate::wallet::coin_selection)'s documentation.
pub trait CoinSelectionAlgorithm: std::fmt::Debug {
/// Perform the coin selection
///
/// - `utxos`: the list of spendable UTXOs
/// - `use_all_utxos`: if true all utxos should be spent unconditionally
/// - `fee_rate`: fee rate to use
/// - `amount_needed`: the amount in satoshi to select
/// - `input_witness_weight`: the weight of an input's witness to keep into account for the fees
/// - `fee_amount`: the amount of fees in satoshi already accumulated from adding outputs
fn coin_select(
&self,
utxos: Vec<UTXO>,
@ -49,6 +149,10 @@ pub trait CoinSelectionAlgorithm: std::fmt::Debug {
) -> Result<CoinSelectionResult, Error>;
}
/// Simple and dumb coin selection
///
/// This coin selection algorithm sorts the available UTXOs by value and then picks them starting
/// from the largest ones until the required amount is reached.
#[derive(Debug, Default)]
pub struct DumbCoinSelection;

View File

@ -200,7 +200,7 @@ where
return Err(Error::SpendingPolicyRequired);
}
let requirements =
policy.get_requirements(builder.policy_path.as_ref().unwrap_or(&BTreeMap::new()))?;
policy.get_condition(builder.policy_path.as_ref().unwrap_or(&BTreeMap::new()))?;
debug!("requirements: {:?}", requirements);
let version = match builder.version {