Write more docs
This commit is contained in:
parent
6b9c363937
commit
7065c1fed6
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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(),
|
||||
|
@ -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>;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user