From c0867a6adc7b868ccd584b66eebaf8492b2cf925 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Mon, 31 Aug 2020 10:49:44 +0200 Subject: [PATCH] General cleanup for the docs --- Cargo.toml | 7 ++ examples/address_validator.rs | 2 +- src/blockchain/compact_filters/mod.rs | 13 ++- src/blockchain/compact_filters/peer.rs | 2 +- src/blockchain/electrum.rs | 4 +- src/blockchain/esplora.rs | 11 +- src/blockchain/mod.rs | 17 ++- src/blockchain/utils.rs | 6 +- src/cli.rs | 22 ++-- src/database/mod.rs | 8 +- src/descriptor/error.rs | 2 + src/descriptor/policy.rs | 20 ++-- src/error.rs | 4 +- src/lib.rs | 22 ++-- src/types.rs | 28 +++++ src/wallet/address_validator.rs | 12 +- src/wallet/coin_selection.rs | 75 ++++++++---- src/wallet/mod.rs | 152 +++++++++++++------------ src/wallet/signer.rs | 22 +++- src/wallet/time.rs | 4 +- src/wallet/tx_builder.rs | 27 +++-- src/wallet/utils.rs | 30 +---- testutils-macros/src/lib.rs | 16 +-- 23 files changed, 305 insertions(+), 201 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec9624b2..599e8072 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,3 +83,10 @@ required-features = ["cli-utils"] [workspace] members = ["macros", "testutils", "testutils-macros"] + +# Generate docs with nightly to add the "features required" badge +# https://stackoverflow.com/questions/61417452/how-to-get-a-feature-requirement-tag-in-the-documentation-generated-by-cargo-do +[package.metadata.docs.rs] +features = ["compiler", "electrum", "esplora", "compact_filters", "key-value-db"] +# defines the configuration attribute `docsrs` +rustdoc-args = ["--cfg", "docsrs"] diff --git a/examples/address_validator.rs b/examples/address_validator.rs index 1cf766c8..639bbdfd 100644 --- a/examples/address_validator.rs +++ b/examples/address_validator.rs @@ -27,8 +27,8 @@ use std::sync::Arc; use magical_bitcoin_wallet::bitcoin; use magical_bitcoin_wallet::database::MemoryDatabase; use magical_bitcoin_wallet::descriptor::HDKeyPaths; -use magical_bitcoin_wallet::types::ScriptType; use magical_bitcoin_wallet::wallet::address_validator::{AddressValidator, AddressValidatorError}; +use magical_bitcoin_wallet::ScriptType; use magical_bitcoin_wallet::{OfflineWallet, Wallet}; use bitcoin::hashes::hex::FromHex; diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index 5b078dd2..4b294046 100644 --- a/src/blockchain/compact_filters/mod.rs +++ b/src/blockchain/compact_filters/mod.rs @@ -23,6 +23,7 @@ // SOFTWARE. use std::collections::HashSet; +use std::fmt; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; @@ -116,7 +117,7 @@ impl CompactFilters { }) } - fn process_tx( + fn process_tx( &self, database: &mut D, tx: &Transaction, @@ -207,7 +208,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain { vec![Capability::FullHistory].into_iter().collect() } - fn setup( + fn setup( &self, _stop_gap: Option, // TODO: move to electrum and esplora only database: &mut D, @@ -460,6 +461,14 @@ pub enum CompactFiltersError { Global(Box), } +impl fmt::Display for CompactFiltersError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for CompactFiltersError {} + macro_rules! impl_error { ( $from:ty, $to:ident ) => { impl std::convert::From<$from> for CompactFiltersError { diff --git a/src/blockchain/compact_filters/peer.rs b/src/blockchain/compact_filters/peer.rs index c6459ce4..a317b3ea 100644 --- a/src/blockchain/compact_filters/peer.rs +++ b/src/blockchain/compact_filters/peer.rs @@ -257,7 +257,7 @@ impl Peer { *self.connected.read().unwrap() } - pub fn reader_thread( + fn reader_thread( network: Network, connection: TcpStream, reader_thread_responses: Arc>, diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index 3bb9e6bb..6dc8683c 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -33,7 +33,7 @@ use electrum_client::{Client, ElectrumApi}; use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync}; use super::*; -use crate::database::{BatchDatabase, DatabaseUtils}; +use crate::database::BatchDatabase; use crate::error::Error; use crate::FeeRate; @@ -73,7 +73,7 @@ impl OnlineBlockchain for ElectrumBlockchain { .collect() } - fn setup( + fn setup( &self, stop_gap: Option, database: &mut D, diff --git a/src/blockchain/esplora.rs b/src/blockchain/esplora.rs index 059f543b..65c2a340 100644 --- a/src/blockchain/esplora.rs +++ b/src/blockchain/esplora.rs @@ -23,6 +23,7 @@ // SOFTWARE. use std::collections::{HashMap, HashSet}; +use std::fmt; use futures::stream::{self, StreamExt, TryStreamExt}; @@ -92,7 +93,7 @@ impl OnlineBlockchain for EsploraBlockchain { .collect() } - fn setup( + fn setup( &self, stop_gap: Option, database: &mut D, @@ -358,6 +359,14 @@ pub enum EsploraError { TransactionNotFound(Txid), } +impl fmt::Display for EsploraError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for EsploraError {} + impl From for EsploraError { fn from(other: reqwest::Error) -> Self { EsploraError::Reqwest(other) diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 1692ccab..98b2161d 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -29,24 +29,29 @@ use std::sync::Arc; use bitcoin::{Transaction, Txid}; -use crate::database::{BatchDatabase, DatabaseUtils}; +use crate::database::BatchDatabase; use crate::error::Error; use crate::FeeRate; -pub mod utils; +pub(crate) mod utils; #[cfg(feature = "electrum")] +#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))] pub mod electrum; #[cfg(feature = "electrum")] pub use self::electrum::ElectrumBlockchain; #[cfg(feature = "esplora")] +#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))] pub mod esplora; #[cfg(feature = "esplora")] pub use self::esplora::EsploraBlockchain; #[cfg(feature = "compact_filters")] +#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))] pub mod compact_filters; +#[cfg(feature = "compact_filters")] +pub use self::compact_filters::CompactFiltersBlockchain; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Capability { @@ -76,13 +81,13 @@ impl Blockchain for OfflineBlockchain { pub trait OnlineBlockchain: Blockchain { fn get_capabilities(&self) -> HashSet; - fn setup( + fn setup( &self, stop_gap: Option, database: &mut D, progress_update: P, ) -> Result<(), Error>; - fn sync( + fn sync( &self, stop_gap: Option, database: &mut D, @@ -163,7 +168,7 @@ impl OnlineBlockchain for Arc { maybe_await!(self.deref().get_capabilities()) } - fn setup( + fn setup( &self, stop_gap: Option, database: &mut D, @@ -172,7 +177,7 @@ impl OnlineBlockchain for Arc { maybe_await!(self.deref().setup(stop_gap, database, progress_update)) } - fn sync( + fn sync( &self, stop_gap: Option, database: &mut D, diff --git a/src/blockchain/utils.rs b/src/blockchain/utils.rs index 016d5e27..63bd5a7b 100644 --- a/src/blockchain/utils.rs +++ b/src/blockchain/utils.rs @@ -67,7 +67,7 @@ pub trait ElectrumLikeSync { // Provided methods down here... - fn electrum_like_setup( + fn electrum_like_setup( &self, stop_gap: Option, database: &mut D, @@ -196,7 +196,7 @@ pub trait ElectrumLikeSync { Ok(()) } - fn check_tx_and_descendant( + fn check_tx_and_descendant( &self, database: &mut D, txid: &Txid, @@ -320,7 +320,7 @@ pub trait ElectrumLikeSync { Ok(to_check_later) } - fn check_history( + fn check_history( &self, database: &mut D, script_pubkey: Script, diff --git a/src/cli.rs b/src/cli.rs index f3b11ef6..51c56ef9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -40,7 +40,7 @@ use crate::error::Error; use crate::types::ScriptType; use crate::{FeeRate, TxBuilder, Wallet}; -fn parse_addressee(s: &str) -> Result<(Address, u64), String> { +fn parse_recipient(s: &str) -> Result<(Address, u64), String> { let parts: Vec<_> = s.split(":").collect(); if parts.len() != 2 { return Err("Invalid format".to_string()); @@ -62,8 +62,8 @@ fn parse_outpoint(s: &str) -> Result { OutPoint::from_str(s).map_err(|e| format!("{:?}", e)) } -fn addressee_validator(s: String) -> Result<(), String> { - parse_addressee(&s).map(|_| ()) +fn recipient_validator(s: String) -> Result<(), String> { + parse_recipient(&s).map(|_| ()) } fn outpoint_validator(s: String) -> Result<(), String> { @@ -95,18 +95,18 @@ pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> { Arg::with_name("to") .long("to") .value_name("ADDRESS:SAT") - .help("Adds an addressee to the transaction") + .help("Adds a recipient to the transaction") .takes_value(true) .number_of_values(1) .required(true) .multiple(true) - .validator(addressee_validator), + .validator(recipient_validator), ) .arg( Arg::with_name("send_all") .short("all") .long("send_all") - .help("Sends all the funds (or all the selected utxos). Requires only one addressees of value 0"), + .help("Sends all the funds (or all the selected utxos). Requires only one recipients of value 0"), ) .arg( Arg::with_name("enable_rbf") @@ -382,13 +382,13 @@ where "satoshi": wallet.get_balance()? })) } else if let Some(sub_matches) = matches.subcommand_matches("create_tx") { - let addressees = sub_matches + let recipients = sub_matches .values_of("to") .unwrap() - .map(|s| parse_addressee(s)) + .map(|s| parse_recipient(s)) .collect::, _>>() .map_err(|s| Error::Generic(s))?; - let mut tx_builder = TxBuilder::from_addressees(addressees); + let mut tx_builder = TxBuilder::with_recipients(recipients); if sub_matches.is_present("send_all") { tx_builder = tx_builder.send_all(); @@ -503,13 +503,13 @@ where })) } else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") { let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap(); - let mut psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); + let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); let assume_height = sub_matches .value_of("assume_height") .and_then(|s| Some(s.parse().unwrap())); - let finalized = wallet.finalize_psbt(&mut psbt, assume_height)?; + let (psbt, finalized) = wallet.finalize_psbt(psbt, assume_height)?; Ok(json!({ "psbt": base64::encode(&serialize(&psbt)), "is_finalized": finalized, diff --git a/src/database/mod.rs b/src/database/mod.rs index dab4279a..c91b385e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -28,10 +28,10 @@ use bitcoin::{OutPoint, Script, Transaction, TxOut}; use crate::error::Error; use crate::types::*; -#[cfg(any(feature = "key-value-db", feature = "default"))] -pub mod keyvalue; -pub mod memory; +#[cfg(feature = "key-value-db")] +pub(crate) mod keyvalue; +pub mod memory; pub use memory::MemoryDatabase; pub trait BatchOperations { @@ -102,7 +102,7 @@ pub trait BatchDatabase: Database { fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>; } -pub trait DatabaseUtils: Database { +pub(crate) trait DatabaseUtils: Database { fn is_mine(&self, script: &Script) -> Result { self.get_path_from_script_pubkey(script) .map(|o| o.is_some()) diff --git a/src/descriptor/error.rs b/src/descriptor/error.rs index 5d32b5de..08bc2baa 100644 --- a/src/descriptor/error.rs +++ b/src/descriptor/error.rs @@ -53,6 +53,8 @@ impl std::fmt::Display for Error { } } +impl std::error::Error for Error {} + impl_error!(bitcoin::util::bip32::Error, BIP32); impl_error!(bitcoin::util::base58::Error, Base58); impl_error!(bitcoin::util::key::Error, PK); diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 42bf5eb1..c8834bd5 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -24,6 +24,7 @@ use std::cmp::max; use std::collections::{BTreeMap, HashSet, VecDeque}; +use std::fmt; use std::sync::Arc; use serde::ser::SerializeMap; @@ -423,8 +424,16 @@ pub enum PolicyError { IncompatibleConditions, } +impl fmt::Display for PolicyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for PolicyError {} + impl Policy { - pub fn new(item: SatisfiableItem) -> Self { + fn new(item: SatisfiableItem) -> Self { Policy { id: item.id(), item, @@ -433,7 +442,7 @@ impl Policy { } } - pub fn make_and(a: Option, b: Option) -> Result, PolicyError> { + fn make_and(a: Option, b: Option) -> Result, PolicyError> { match (a, b) { (None, None) => Ok(None), (Some(x), None) | (None, Some(x)) => Ok(Some(x)), @@ -441,7 +450,7 @@ impl Policy { } } - pub fn make_or(a: Option, b: Option) -> Result, PolicyError> { + fn make_or(a: Option, b: Option) -> Result, PolicyError> { match (a, b) { (None, None) => Ok(None), (Some(x), None) | (None, Some(x)) => Ok(Some(x)), @@ -449,10 +458,7 @@ impl Policy { } } - pub fn make_thresh( - items: Vec, - threshold: usize, - ) -> Result, PolicyError> { + fn make_thresh(items: Vec, threshold: usize) -> Result, PolicyError> { if threshold == 0 { return Ok(None); } diff --git a/src/error.rs b/src/error.rs index 1bd6272e..7bee83ff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,7 +43,9 @@ pub enum Error { TransactionNotFound, TransactionConfirmed, IrreplaceableTransaction, - FeeRateTooLow(crate::wallet::utils::FeeRate), + FeeRateTooLow { + required: crate::types::FeeRate, + }, ChecksumMismatch, DifferentDescriptorStructure, diff --git a/src/lib.rs b/src/lib.rs index 7de7c20d..433a201d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ // only enables the `doc_cfg` feature when // the `docsrs` configuration attribute is defined -#[cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] pub extern crate bitcoin; extern crate log; @@ -45,13 +45,9 @@ extern crate lazy_static; #[cfg(feature = "electrum")] pub extern crate electrum_client; -#[cfg(feature = "electrum")] -pub use electrum_client::client::Client; #[cfg(feature = "esplora")] pub extern crate reqwest; -#[cfg(feature = "esplora")] -pub use blockchain::esplora::EsploraBlockchain; #[cfg(feature = "key-value-db")] pub extern crate sled; @@ -74,11 +70,19 @@ pub mod error; pub mod blockchain; pub mod database; pub mod descriptor; -pub mod psbt; -pub mod types; +pub(crate) mod psbt; +pub(crate) mod types; pub mod wallet; -pub use descriptor::ExtendedDescriptor; +pub use error::Error; +pub use types::*; +pub use wallet::address_validator; +pub use wallet::signer; pub use wallet::tx_builder::TxBuilder; -pub use wallet::utils::FeeRate; pub use wallet::{OfflineWallet, Wallet}; + +#[cfg(feature = "esplora")] +pub use blockchain::esplora::EsploraBlockchain; + +#[cfg(feature = "electrum")] +pub use blockchain::electrum::ElectrumBlockchain; diff --git a/src/types.rs b/src/types.rs index 83b32dc1..08e7131c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -58,6 +58,34 @@ impl AsRef<[u8]> for ScriptType { } } +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +// Internally stored as satoshi/vbyte +pub struct FeeRate(f32); + +impl FeeRate { + pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self { + FeeRate(btc_per_kvb * 1e5) + } + + pub fn from_sat_per_vb(sat_per_vb: f32) -> Self { + FeeRate(sat_per_vb) + } + + pub fn default_min_relay_fee() -> Self { + FeeRate(1.0) + } + + pub fn as_sat_vb(&self) -> f32 { + self.0 + } +} + +impl std::default::Default for FeeRate { + fn default() -> Self { + FeeRate::default_min_relay_fee() + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct UTXO { pub outpoint: OutPoint, diff --git a/src/wallet/address_validator.rs b/src/wallet/address_validator.rs index fc1a58a1..a96df3e6 100644 --- a/src/wallet/address_validator.rs +++ b/src/wallet/address_validator.rs @@ -22,6 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use std::fmt; + use bitcoin::Script; use crate::descriptor::HDKeyPaths; @@ -35,6 +37,14 @@ pub enum AddressValidatorError { InvalidScript, } +impl fmt::Display for AddressValidatorError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for AddressValidatorError {} + pub trait AddressValidator { fn validate( &self, @@ -81,7 +91,7 @@ mod test { let addr = testutils!(@external descriptors, 10); wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); } } diff --git a/src/wallet/coin_selection.rs b/src/wallet/coin_selection.rs index ab03ce15..d6836047 100644 --- a/src/wallet/coin_selection.rs +++ b/src/wallet/coin_selection.rs @@ -26,14 +26,14 @@ use bitcoin::consensus::encode::serialize; use bitcoin::{Script, TxIn}; use crate::error::Error; -use crate::types::UTXO; +use crate::types::{FeeRate, UTXO}; pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection; #[derive(Debug)] pub struct CoinSelectionResult { pub txin: Vec<(TxIn, Script)>, - pub total_amount: u64, + pub selected_amount: u64, pub fee_amount: f32, } @@ -42,8 +42,8 @@ pub trait CoinSelectionAlgorithm: std::fmt::Debug { &self, utxos: Vec, use_all_utxos: bool, - fee_rate: f32, - outgoing_amount: u64, + fee_rate: FeeRate, + amount_needed: u64, input_witness_weight: usize, fee_amount: f32, ) -> Result; @@ -57,16 +57,16 @@ impl CoinSelectionAlgorithm for DumbCoinSelection { &self, mut utxos: Vec, use_all_utxos: bool, - fee_rate: f32, + fee_rate: FeeRate, outgoing_amount: u64, input_witness_weight: usize, mut fee_amount: f32, ) -> Result { let mut txin = Vec::new(); - let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0; + let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0; log::debug!( - "outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{}`", + "outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{:?}`", outgoing_amount, fee_amount, fee_rate @@ -75,11 +75,11 @@ impl CoinSelectionAlgorithm for DumbCoinSelection { // sort so that we pick them starting from the larger. utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap()); - let mut total_amount: u64 = 0; - while use_all_utxos || total_amount < outgoing_amount + (fee_amount.ceil() as u64) { + let mut selected_amount: u64 = 0; + while use_all_utxos || selected_amount < outgoing_amount + (fee_amount.ceil() as u64) { let utxo = match utxos.pop() { Some(utxo) => utxo, - None if total_amount < outgoing_amount + (fee_amount.ceil() as u64) => { + None if selected_amount < outgoing_amount + (fee_amount.ceil() as u64) => { return Err(Error::InsufficientFunds) } None if use_all_utxos => break, @@ -100,13 +100,13 @@ impl CoinSelectionAlgorithm for DumbCoinSelection { ); txin.push((new_in, utxo.txout.script_pubkey)); - total_amount += utxo.txout.value; + selected_amount += utxo.txout.value; } Ok(CoinSelectionResult { txin, fee_amount, - total_amount, + selected_amount, }) } } @@ -154,11 +154,18 @@ mod test { let utxos = get_test_utxos(); let result = DumbCoinSelection - .coin_select(utxos, false, 1.0, 250_000, P2WPKH_WITNESS_SIZE, 50.0) + .coin_select( + utxos, + false, + FeeRate::from_sat_per_vb(1.0), + 250_000, + P2WPKH_WITNESS_SIZE, + 50.0, + ) .unwrap(); assert_eq!(result.txin.len(), 2); - assert_eq!(result.total_amount, 300_000); + assert_eq!(result.selected_amount, 300_000); assert_eq!(result.fee_amount, 186.0); } @@ -167,11 +174,18 @@ mod test { let utxos = get_test_utxos(); let result = DumbCoinSelection - .coin_select(utxos, true, 1.0, 20_000, P2WPKH_WITNESS_SIZE, 50.0) + .coin_select( + utxos, + true, + FeeRate::from_sat_per_vb(1.0), + 20_000, + P2WPKH_WITNESS_SIZE, + 50.0, + ) .unwrap(); assert_eq!(result.txin.len(), 2); - assert_eq!(result.total_amount, 300_000); + assert_eq!(result.selected_amount, 300_000); assert_eq!(result.fee_amount, 186.0); } @@ -180,11 +194,18 @@ mod test { let utxos = get_test_utxos(); let result = DumbCoinSelection - .coin_select(utxos, false, 1.0, 20_000, P2WPKH_WITNESS_SIZE, 50.0) + .coin_select( + utxos, + false, + FeeRate::from_sat_per_vb(1.0), + 20_000, + P2WPKH_WITNESS_SIZE, + 50.0, + ) .unwrap(); assert_eq!(result.txin.len(), 1); - assert_eq!(result.total_amount, 200_000); + assert_eq!(result.selected_amount, 200_000); assert_eq!(result.fee_amount, 118.0); } @@ -194,7 +215,14 @@ mod test { let utxos = get_test_utxos(); DumbCoinSelection - .coin_select(utxos, false, 1.0, 500_000, P2WPKH_WITNESS_SIZE, 50.0) + .coin_select( + utxos, + false, + FeeRate::from_sat_per_vb(1.0), + 500_000, + P2WPKH_WITNESS_SIZE, + 50.0, + ) .unwrap(); } @@ -204,7 +232,14 @@ mod test { let utxos = get_test_utxos(); DumbCoinSelection - .coin_select(utxos, false, 1000.0, 250_000, P2WPKH_WITNESS_SIZE, 50.0) + .coin_select( + utxos, + false, + FeeRate::from_sat_per_vb(1000.0), + 250_000, + P2WPKH_WITNESS_SIZE, + 50.0, + ) .unwrap(); } } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 6f619931..452b90eb 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -45,12 +45,14 @@ mod rbf; pub mod signer; pub mod time; pub mod tx_builder; -pub mod utils; +pub(crate) mod utils; + +pub use utils::IsDust; use address_validator::AddressValidator; use signer::{Signer, SignerId, SignerOrdering, SignersContainer}; use tx_builder::TxBuilder; -use utils::{After, FeeRate, IsDust, Older}; +use utils::{After, Older}; use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; @@ -185,7 +187,7 @@ where &self, builder: TxBuilder, ) -> Result<(PSBT, TransactionDetails), Error> { - if builder.addressees.is_empty() { + if builder.recipients.is_empty() { return Err(Error::NoAddressees); } @@ -239,8 +241,8 @@ where output: vec![], }; - let fee_rate = builder.fee_rate.unwrap_or_default().as_sat_vb(); - if builder.send_all && builder.addressees.len() != 1 { + let fee_rate = builder.fee_rate.unwrap_or_default(); + if builder.send_all && builder.recipients.len() != 1 { return Err(Error::SendAllMultipleOutputs); } @@ -249,10 +251,10 @@ where let mut outgoing: u64 = 0; let mut received: u64 = 0; - let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0; + let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0; fee_amount += calc_fee_bytes(tx.get_weight()); - for (index, (address, satoshi)) in builder.addressees.iter().enumerate() { + for (index, (address, satoshi)) in builder.recipients.iter().enumerate() { let value = match builder.send_all { true => 0, false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)), @@ -304,7 +306,7 @@ where )?; let coin_selection::CoinSelectionResult { txin, - total_amount, + selected_amount, mut fee_amount, } = builder.coin_selection.coin_select( available_utxos, @@ -342,7 +344,7 @@ where }; let mut fee_amount = fee_amount.ceil() as u64; - let change_val = total_amount - outgoing - fee_amount; + let change_val = selected_amount - outgoing - fee_amount; if !builder.send_all && !change_val.is_dust() { let mut change_output = change_output.unwrap(); change_output.value = change_val; @@ -366,7 +368,7 @@ where } // sort input/outputs according to the chosen algorithm - builder.ordering.modify_tx(&mut tx); + builder.ordering.sort_tx(&mut tx); let txid = tx.txid(); let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?; @@ -376,7 +378,7 @@ where txid, timestamp: time::get_timestamp(), received, - sent: total_amount, + sent: selected_amount, fees: fee_amount, height: None, }; @@ -409,7 +411,9 @@ where let new_feerate = builder.fee_rate.unwrap_or_default(); if new_feerate < required_feerate { - return Err(Error::FeeRateTooLow(required_feerate)); + return Err(Error::FeeRateTooLow { + required: required_feerate, + }); } let mut fee_difference = (new_feerate.as_sat_vb() * tx.get_weight() as f32 / 4.0).ceil() as u64 - details.fees; @@ -515,12 +519,12 @@ where )?; let coin_selection::CoinSelectionResult { txin, - total_amount, + selected_amount, fee_amount, } = builder.coin_selection.coin_select( available_utxos, use_all_utxos, - new_feerate.as_sat_vb(), + new_feerate, fee_difference .checked_sub(removed_change_output.value) .unwrap_or(0), @@ -538,8 +542,8 @@ where .for_each(|i| i.sequence = tx.input[0].sequence); tx.input.extend_from_slice(&mut txin); - details.sent += total_amount; - total_amount + details.sent += selected_amount; + selected_amount } else { // otherwise just remove the output and add 0 new coins 0 @@ -570,7 +574,7 @@ where } // sort input/outputs according to the chosen algorithm - builder.ordering.modify_tx(&mut tx); + builder.ordering.sort_tx(&mut tx); // TODO: check that we are not replacing more than 100 txs from mempool @@ -620,9 +624,7 @@ where } // attempt to finalize - let finalized = self.finalize_psbt(&mut psbt, assume_height)?; - - Ok((psbt, finalized)) + self.finalize_psbt(psbt, assume_height) } pub fn policies(&self, script_type: ScriptType) -> Result, Error> { @@ -650,9 +652,9 @@ where pub fn finalize_psbt( &self, - psbt: &mut PSBT, + mut psbt: PSBT, assume_height: Option, - ) -> Result { + ) -> Result<(PSBT, bool), Error> { let mut tx = psbt.global.unsigned_tx.clone(); for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() { @@ -696,7 +698,7 @@ where desc } else { debug!("Couldn't find the right derived descriptor for input {}", n); - return Ok(false); + return Ok((psbt, false)); }; match desc.satisfy( @@ -710,7 +712,7 @@ where Ok(_) => continue, Err(e) => { debug!("satisfy error {:?} for input {}", e, n); - return Ok(false); + return Ok((psbt, false)); } } } @@ -721,7 +723,7 @@ where psbt_input.final_script_witness = Some(input.witness); } - Ok(true) + Ok((psbt, true)) } // Internals @@ -1236,10 +1238,10 @@ mod test { #[test] #[should_panic(expected = "NoAddressees")] - fn test_create_tx_empty_addressees() { + fn test_create_tx_empty_recipients() { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); wallet - .create_tx(TxBuilder::from_addressees(vec![]).version(0)) + .create_tx(TxBuilder::with_recipients(vec![]).version(0)) .unwrap(); } @@ -1249,7 +1251,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(0)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0)) .unwrap(); } @@ -1261,7 +1263,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let addr = wallet.get_new_address().unwrap(); wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(1)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1)) .unwrap(); } @@ -1270,7 +1272,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(42)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(42)) .unwrap(); assert_eq!(psbt.global.unsigned_tx.version, 42); @@ -1281,7 +1283,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); assert_eq!(psbt.global.unsigned_tx.lock_time, 0); @@ -1292,7 +1294,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000); @@ -1303,7 +1305,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(630_000)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000)) .unwrap(); assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000); @@ -1314,7 +1316,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(630_000)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000)) .unwrap(); assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000); @@ -1328,7 +1330,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let addr = wallet.get_new_address().unwrap(); wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(50000)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000)) .unwrap(); } @@ -1337,7 +1339,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6); @@ -1348,7 +1350,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).enable_rbf()) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf()) .unwrap(); assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD); @@ -1362,7 +1364,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let addr = wallet.get_new_address().unwrap(); wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).enable_rbf_with_sequence(3)) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf_with_sequence(3)) .unwrap(); } @@ -1371,7 +1373,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE); @@ -1384,7 +1386,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); wallet .create_tx( - TxBuilder::from_addressees(vec![(addr, 25_000)]) + TxBuilder::with_recipients(vec![(addr, 25_000)]) .enable_rbf_with_sequence(0xFFFFFFFE), ) .unwrap(); @@ -1396,7 +1398,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr, 25_000)]) + TxBuilder::with_recipients(vec![(addr, 25_000)]) .enable_rbf_with_sequence(0xDEADBEEF), ) .unwrap(); @@ -1409,7 +1411,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF); @@ -1424,7 +1426,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 25_000)]).do_not_spend_change(), + TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).do_not_spend_change(), ) .unwrap(); } @@ -1436,7 +1438,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(), + TxBuilder::with_recipients(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(), ) .unwrap(); } @@ -1446,7 +1448,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert_eq!(psbt.global.unsigned_tx.output.len(), 1); @@ -1461,7 +1463,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature); @@ -1473,7 +1475,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); let (psbt, details) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 0)]) + TxBuilder::with_recipients(vec![(addr.clone(), 0)]) .fee_rate(FeeRate::from_sat_per_vb(5.0)) .send_all(), ) @@ -1490,7 +1492,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); let (psbt, details) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 25_000)]) + TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]) .ordering(TxOrdering::Untouched), ) .unwrap(); @@ -1508,7 +1510,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 49_800)])) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)])) .unwrap(); assert_eq!(psbt.global.unsigned_tx.output.len(), 1); @@ -1523,9 +1525,9 @@ mod test { // very high fee rate, so that the only output would be below dust wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 0)]) + TxBuilder::with_recipients(vec![(addr.clone(), 0)]) .send_all() - .fee_rate(super::utils::FeeRate::from_sat_per_vb(453.0)), + .fee_rate(crate::FeeRate::from_sat_per_vb(453.0)), ) .unwrap(); } @@ -1536,7 +1538,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); let (psbt, details) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)]) + TxBuilder::with_recipients(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)]) .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic), ) .unwrap(); @@ -1555,7 +1557,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 30_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])) .unwrap(); assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All)); @@ -1567,7 +1569,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 30_000)]) + TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]) .sighash(bitcoin::SigHashType::Single), ) .unwrap(); @@ -1586,7 +1588,7 @@ mod test { let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1); @@ -1610,7 +1612,7 @@ mod test { let addr = testutils!(@external descriptors, 5); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1); @@ -1631,7 +1633,7 @@ mod test { get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert_eq!( @@ -1654,7 +1656,7 @@ mod test { get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert_eq!(psbt.inputs[0].redeem_script, None); @@ -1677,7 +1679,7 @@ mod test { get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); let script = Script::from( @@ -1697,7 +1699,7 @@ mod test { get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert!(psbt.inputs[0].non_witness_utxo.is_some()); @@ -1710,7 +1712,7 @@ mod test { get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); assert!(psbt.inputs[0].non_witness_utxo.is_none()); @@ -1724,7 +1726,7 @@ mod test { let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 0)]) + TxBuilder::with_recipients(vec![(addr.clone(), 0)]) .force_non_witness_utxo() .send_all(), ) @@ -1740,7 +1742,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, mut details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1757,7 +1759,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, mut details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)])) .unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1775,7 +1777,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = wallet.get_new_address().unwrap(); let (psbt, mut details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).enable_rbf()) + .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf()) .unwrap(); let tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1796,7 +1798,7 @@ mod test { let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 25_000)]).enable_rbf()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).enable_rbf()) .unwrap(); let mut tx = psbt.extract_tx(); let txid = tx.txid(); @@ -1858,7 +1860,7 @@ mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 0)]) + TxBuilder::with_recipients(vec![(addr.clone(), 0)]) .send_all() .enable_rbf(), ) @@ -1912,7 +1914,7 @@ mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 0)]) + TxBuilder::with_recipients(vec![(addr.clone(), 0)]) .utxos(vec![OutPoint { txid: incoming_txid, vout: 0, @@ -1959,7 +1961,7 @@ mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 45_000)]).enable_rbf()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf()) .unwrap(); let mut tx = psbt.extract_tx(); let txid = tx.txid(); @@ -2023,7 +2025,7 @@ mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet .create_tx( - TxBuilder::from_addressees(vec![(addr.clone(), 0)]) + TxBuilder::with_recipients(vec![(addr.clone(), 0)]) .send_all() .add_utxo(OutPoint { txid: incoming_txid, @@ -2099,7 +2101,7 @@ mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 45_000)]).enable_rbf()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf()) .unwrap(); let mut tx = psbt.extract_tx(); assert_eq!(tx.input.len(), 1); @@ -2159,7 +2161,7 @@ mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let (psbt, mut original_details) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 45_000)]).enable_rbf()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf()) .unwrap(); let mut tx = psbt.extract_tx(); let txid = tx.txid(); @@ -2224,7 +2226,7 @@ mod test { let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); @@ -2240,7 +2242,7 @@ mod test { get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); let addr = wallet.get_new_address().unwrap(); let (psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); @@ -2255,7 +2257,7 @@ mod test { let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_new_address().unwrap(); let (mut psbt, _) = wallet - .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all()) + .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all()) .unwrap(); psbt.inputs[0].hd_keypaths.clear(); diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 81524e05..766f4bd2 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -84,6 +84,14 @@ pub enum SignerError { MissingHDKeypath, } +impl fmt::Display for SignerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for SignerError {} + /// Trait for signers pub trait Signer: fmt::Debug { fn sign( @@ -92,9 +100,7 @@ pub trait Signer: fmt::Debug { input_index: Option, ) -> Result<(), SignerError>; - fn sign_whole_tx(&self) -> bool { - false - } + fn sign_whole_tx(&self) -> bool; fn descriptor_secret_key(&self) -> Option { None @@ -128,6 +134,10 @@ impl Signer for DescriptorXKey { derived_key.private_key.sign(psbt, Some(input_index)) } + fn sign_whole_tx(&self) -> bool { + false + } + fn descriptor_secret_key(&self) -> Option { Some(DescriptorSecretKey::XPrv(self.clone())) } @@ -176,6 +186,10 @@ impl Signer for PrivateKey { Ok(()) } + fn sign_whole_tx(&self) -> bool { + false + } + fn descriptor_secret_key(&self) -> Option { Some(DescriptorSecretKey::PrivKey(self.clone())) } @@ -299,7 +313,7 @@ impl SignersContainer { } } -pub trait ComputeSighash { +pub(crate) trait ComputeSighash { fn sighash( psbt: &psbt::PartiallySignedTransaction, input_index: usize, diff --git a/src/wallet/time.rs b/src/wallet/time.rs index 7ab8c219..0d0a77c7 100644 --- a/src/wallet/time.rs +++ b/src/wallet/time.rs @@ -44,9 +44,9 @@ pub fn get_timestamp() -> u64 { } #[cfg(not(target_arch = "wasm32"))] -pub struct Instant(SystemInstant); +pub(crate) struct Instant(SystemInstant); #[cfg(target_arch = "wasm32")] -pub struct Instant(Duration); +pub(crate) struct Instant(Duration); impl Instant { #[cfg(not(target_arch = "wasm32"))] diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index 8a6bf4c2..aab9951f 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -28,12 +28,11 @@ use std::default::Default; use bitcoin::{Address, OutPoint, SigHashType, Transaction}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; -use super::utils::FeeRate; -use crate::types::UTXO; +use crate::types::{FeeRate, UTXO}; #[derive(Debug, Default)] pub struct TxBuilder { - pub(crate) addressees: Vec<(Address, u64)>, + pub(crate) recipients: Vec<(Address, u64)>, pub(crate) send_all: bool, pub(crate) fee_rate: Option, pub(crate) policy_path: Option>>, @@ -54,19 +53,19 @@ impl TxBuilder { Self::default() } - pub fn from_addressees(addressees: Vec<(Address, u64)>) -> Self { - Self::default().set_addressees(addressees) + pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self { + Self::default().set_recipients(recipients) } } impl TxBuilder { - pub fn set_addressees(mut self, addressees: Vec<(Address, u64)>) -> Self { - self.addressees = addressees; + pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self { + self.recipients = recipients; self } - pub fn add_addressee(mut self, address: Address, amount: u64) -> Self { - self.addressees.push((address, amount)); + pub fn add_recipient(mut self, address: Address, amount: u64) -> Self { + self.recipients.push((address, amount)); self } @@ -158,7 +157,7 @@ impl TxBuilder { pub fn coin_selection(self, coin_selection: P) -> TxBuilder

{ TxBuilder { - addressees: self.addressees, + recipients: self.recipients, send_all: self.send_all, fee_rate: self.fee_rate, policy_path: self.policy_path, @@ -190,7 +189,7 @@ impl Default for TxOrdering { } impl TxOrdering { - pub fn modify_tx(&self, tx: &mut Transaction) { + pub fn sort_tx(&self, tx: &mut Transaction) { match self { TxOrdering::Untouched => {} TxOrdering::Shuffle => { @@ -279,7 +278,7 @@ mod test { let original_tx = ordering_test_tx!(); let mut tx = original_tx.clone(); - TxOrdering::Untouched.modify_tx(&mut tx); + TxOrdering::Untouched.sort_tx(&mut tx); assert_eq!(original_tx, tx); } @@ -289,7 +288,7 @@ mod test { let original_tx = ordering_test_tx!(); let mut tx = original_tx.clone(); - TxOrdering::Shuffle.modify_tx(&mut tx); + TxOrdering::Shuffle.sort_tx(&mut tx); assert_eq!(original_tx.input, tx.input); assert_ne!(original_tx.output, tx.output); @@ -302,7 +301,7 @@ mod test { let original_tx = ordering_test_tx!(); let mut tx = original_tx.clone(); - TxOrdering::BIP69Lexicographic.modify_tx(&mut tx); + TxOrdering::BIP69Lexicographic.sort_tx(&mut tx); assert_eq!( tx.input[0].previous_output, diff --git a/src/wallet/utils.rs b/src/wallet/utils.rs index 08444087..bdf98364 100644 --- a/src/wallet/utils.rs +++ b/src/wallet/utils.rs @@ -40,34 +40,6 @@ impl IsDust for u64 { } } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] -// Internally stored as satoshi/vbyte -pub struct FeeRate(f32); - -impl FeeRate { - pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self { - FeeRate(btc_per_kvb * 1e5) - } - - pub fn from_sat_per_vb(sat_per_vb: f32) -> Self { - FeeRate(sat_per_vb) - } - - pub fn default_min_relay_fee() -> Self { - FeeRate(1.0) - } - - pub fn as_sat_vb(&self) -> f32 { - self.0 - } -} - -impl std::default::Default for FeeRate { - fn default() -> Self { - FeeRate::default_min_relay_fee() - } -} - pub struct After { pub current_height: Option, pub assume_height_reached: bool, @@ -158,7 +130,7 @@ impl Iterator for ChunksIterator { #[cfg(test)] mod test { - use super::*; + use crate::types::FeeRate; #[test] fn test_fee_from_btc_per_kb() { diff --git a/testutils-macros/src/lib.rs b/testutils-macros/src/lib.rs index cb8d7374..67847352 100644 --- a/testutils-macros/src/lib.rs +++ b/testutils-macros/src/lib.rs @@ -83,7 +83,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt use #root_ident::descriptor::ExtendedDescriptor; use #root_ident::database::MemoryDatabase; use #root_ident::types::ScriptType; - use #root_ident::{Wallet, TxBuilder}; + use #root_ident::{Wallet, TxBuilder, FeeRate}; use super::*; @@ -307,7 +307,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000); - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); let tx = psbt.extract_tx(); @@ -334,7 +334,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000); - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap(); @@ -373,7 +373,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt let mut total_sent = 0; for _ in 0..5 { - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 5_000)])).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)])).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); wallet.broadcast(psbt.extract_tx()).unwrap(); @@ -405,7 +405,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000); - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); wallet.broadcast(psbt.extract_tx()).unwrap(); @@ -437,7 +437,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 50_000); - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); wallet.broadcast(psbt.extract_tx()).unwrap(); @@ -470,7 +470,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000); - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); wallet.broadcast(psbt.extract_tx()).unwrap(); @@ -501,7 +501,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt wallet.sync(noop_progress(), None).unwrap(); assert_eq!(wallet.get_balance().unwrap(), 75_000); - let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap(); + let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); assert!(finalized, "Cannot finalize transaction"); wallet.broadcast(psbt.extract_tx()).unwrap();