General cleanup for the docs

This commit is contained in:
Alekos Filini 2020-08-31 10:49:44 +02:00
parent d61e974dbe
commit c0867a6adc
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
23 changed files with 305 additions and 201 deletions

View File

@ -83,3 +83,10 @@ required-features = ["cli-utils"]
[workspace] [workspace]
members = ["macros", "testutils", "testutils-macros"] 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"]

View File

@ -27,8 +27,8 @@ use std::sync::Arc;
use magical_bitcoin_wallet::bitcoin; use magical_bitcoin_wallet::bitcoin;
use magical_bitcoin_wallet::database::MemoryDatabase; use magical_bitcoin_wallet::database::MemoryDatabase;
use magical_bitcoin_wallet::descriptor::HDKeyPaths; 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::wallet::address_validator::{AddressValidator, AddressValidatorError};
use magical_bitcoin_wallet::ScriptType;
use magical_bitcoin_wallet::{OfflineWallet, Wallet}; use magical_bitcoin_wallet::{OfflineWallet, Wallet};
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;

View File

@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt;
use std::path::Path; use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -116,7 +117,7 @@ impl CompactFilters {
}) })
} }
fn process_tx<D: BatchDatabase + DatabaseUtils>( fn process_tx<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
tx: &Transaction, tx: &Transaction,
@ -207,7 +208,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
vec![Capability::FullHistory].into_iter().collect() vec![Capability::FullHistory].into_iter().collect()
} }
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>( fn setup<D: BatchDatabase, P: 'static + Progress>(
&self, &self,
_stop_gap: Option<usize>, // TODO: move to electrum and esplora only _stop_gap: Option<usize>, // TODO: move to electrum and esplora only
database: &mut D, database: &mut D,
@ -460,6 +461,14 @@ pub enum CompactFiltersError {
Global(Box<crate::error::Error>), Global(Box<crate::error::Error>),
} }
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 { macro_rules! impl_error {
( $from:ty, $to:ident ) => { ( $from:ty, $to:ident ) => {
impl std::convert::From<$from> for CompactFiltersError { impl std::convert::From<$from> for CompactFiltersError {

View File

@ -257,7 +257,7 @@ impl Peer {
*self.connected.read().unwrap() *self.connected.read().unwrap()
} }
pub fn reader_thread( fn reader_thread(
network: Network, network: Network,
connection: TcpStream, connection: TcpStream,
reader_thread_responses: Arc<RwLock<ResponsesMap>>, reader_thread_responses: Arc<RwLock<ResponsesMap>>,

View File

@ -33,7 +33,7 @@ use electrum_client::{Client, ElectrumApi};
use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync}; use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
use super::*; use super::*;
use crate::database::{BatchDatabase, DatabaseUtils}; use crate::database::BatchDatabase;
use crate::error::Error; use crate::error::Error;
use crate::FeeRate; use crate::FeeRate;
@ -73,7 +73,7 @@ impl OnlineBlockchain for ElectrumBlockchain {
.collect() .collect()
} }
fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>( fn setup<D: BatchDatabase, P: Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,

View File

@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt;
use futures::stream::{self, StreamExt, TryStreamExt}; use futures::stream::{self, StreamExt, TryStreamExt};
@ -92,7 +93,7 @@ impl OnlineBlockchain for EsploraBlockchain {
.collect() .collect()
} }
fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>( fn setup<D: BatchDatabase, P: Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,
@ -358,6 +359,14 @@ pub enum EsploraError {
TransactionNotFound(Txid), 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<reqwest::Error> for EsploraError { impl From<reqwest::Error> for EsploraError {
fn from(other: reqwest::Error) -> Self { fn from(other: reqwest::Error) -> Self {
EsploraError::Reqwest(other) EsploraError::Reqwest(other)

View File

@ -29,24 +29,29 @@ use std::sync::Arc;
use bitcoin::{Transaction, Txid}; use bitcoin::{Transaction, Txid};
use crate::database::{BatchDatabase, DatabaseUtils}; use crate::database::BatchDatabase;
use crate::error::Error; use crate::error::Error;
use crate::FeeRate; use crate::FeeRate;
pub mod utils; pub(crate) mod utils;
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]
pub mod electrum; pub mod electrum;
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
pub use self::electrum::ElectrumBlockchain; pub use self::electrum::ElectrumBlockchain;
#[cfg(feature = "esplora")] #[cfg(feature = "esplora")]
#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
pub mod esplora; pub mod esplora;
#[cfg(feature = "esplora")] #[cfg(feature = "esplora")]
pub use self::esplora::EsploraBlockchain; pub use self::esplora::EsploraBlockchain;
#[cfg(feature = "compact_filters")] #[cfg(feature = "compact_filters")]
#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
pub mod compact_filters; pub mod compact_filters;
#[cfg(feature = "compact_filters")]
pub use self::compact_filters::CompactFiltersBlockchain;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Capability { pub enum Capability {
@ -76,13 +81,13 @@ impl Blockchain for OfflineBlockchain {
pub trait OnlineBlockchain: Blockchain { pub trait OnlineBlockchain: Blockchain {
fn get_capabilities(&self) -> HashSet<Capability>; fn get_capabilities(&self) -> HashSet<Capability>;
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>( fn setup<D: BatchDatabase, P: 'static + Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,
progress_update: P, progress_update: P,
) -> Result<(), Error>; ) -> Result<(), Error>;
fn sync<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>( fn sync<D: BatchDatabase, P: 'static + Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,
@ -163,7 +168,7 @@ impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
maybe_await!(self.deref().get_capabilities()) maybe_await!(self.deref().get_capabilities())
} }
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>( fn setup<D: BatchDatabase, P: 'static + Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,
@ -172,7 +177,7 @@ impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
maybe_await!(self.deref().setup(stop_gap, database, progress_update)) maybe_await!(self.deref().setup(stop_gap, database, progress_update))
} }
fn sync<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>( fn sync<D: BatchDatabase, P: 'static + Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,

View File

@ -67,7 +67,7 @@ pub trait ElectrumLikeSync {
// Provided methods down here... // Provided methods down here...
fn electrum_like_setup<D: BatchDatabase + DatabaseUtils, P: Progress>( fn electrum_like_setup<D: BatchDatabase, P: Progress>(
&self, &self,
stop_gap: Option<usize>, stop_gap: Option<usize>,
database: &mut D, database: &mut D,
@ -196,7 +196,7 @@ pub trait ElectrumLikeSync {
Ok(()) Ok(())
} }
fn check_tx_and_descendant<D: DatabaseUtils + BatchDatabase>( fn check_tx_and_descendant<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
txid: &Txid, txid: &Txid,
@ -320,7 +320,7 @@ pub trait ElectrumLikeSync {
Ok(to_check_later) Ok(to_check_later)
} }
fn check_history<D: DatabaseUtils + BatchDatabase>( fn check_history<D: BatchDatabase>(
&self, &self,
database: &mut D, database: &mut D,
script_pubkey: Script, script_pubkey: Script,

View File

@ -40,7 +40,7 @@ use crate::error::Error;
use crate::types::ScriptType; use crate::types::ScriptType;
use crate::{FeeRate, TxBuilder, Wallet}; 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(); let parts: Vec<_> = s.split(":").collect();
if parts.len() != 2 { if parts.len() != 2 {
return Err("Invalid format".to_string()); return Err("Invalid format".to_string());
@ -62,8 +62,8 @@ fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
OutPoint::from_str(s).map_err(|e| format!("{:?}", e)) OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
} }
fn addressee_validator(s: String) -> Result<(), String> { fn recipient_validator(s: String) -> Result<(), String> {
parse_addressee(&s).map(|_| ()) parse_recipient(&s).map(|_| ())
} }
fn outpoint_validator(s: String) -> Result<(), String> { 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") Arg::with_name("to")
.long("to") .long("to")
.value_name("ADDRESS:SAT") .value_name("ADDRESS:SAT")
.help("Adds an addressee to the transaction") .help("Adds a recipient to the transaction")
.takes_value(true) .takes_value(true)
.number_of_values(1) .number_of_values(1)
.required(true) .required(true)
.multiple(true) .multiple(true)
.validator(addressee_validator), .validator(recipient_validator),
) )
.arg( .arg(
Arg::with_name("send_all") Arg::with_name("send_all")
.short("all") .short("all")
.long("send_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(
Arg::with_name("enable_rbf") Arg::with_name("enable_rbf")
@ -382,13 +382,13 @@ where
"satoshi": wallet.get_balance()? "satoshi": wallet.get_balance()?
})) }))
} else if let Some(sub_matches) = matches.subcommand_matches("create_tx") { } else if let Some(sub_matches) = matches.subcommand_matches("create_tx") {
let addressees = sub_matches let recipients = sub_matches
.values_of("to") .values_of("to")
.unwrap() .unwrap()
.map(|s| parse_addressee(s)) .map(|s| parse_recipient(s))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.map_err(|s| Error::Generic(s))?; .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") { if sub_matches.is_present("send_all") {
tx_builder = tx_builder.send_all(); tx_builder = tx_builder.send_all();
@ -503,13 +503,13 @@ where
})) }))
} else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") { } else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") {
let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap(); 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 let assume_height = sub_matches
.value_of("assume_height") .value_of("assume_height")
.and_then(|s| Some(s.parse().unwrap())); .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!({ Ok(json!({
"psbt": base64::encode(&serialize(&psbt)), "psbt": base64::encode(&serialize(&psbt)),
"is_finalized": finalized, "is_finalized": finalized,

View File

@ -28,10 +28,10 @@ use bitcoin::{OutPoint, Script, Transaction, TxOut};
use crate::error::Error; use crate::error::Error;
use crate::types::*; use crate::types::*;
#[cfg(any(feature = "key-value-db", feature = "default"))] #[cfg(feature = "key-value-db")]
pub mod keyvalue; pub(crate) mod keyvalue;
pub mod memory;
pub mod memory;
pub use memory::MemoryDatabase; pub use memory::MemoryDatabase;
pub trait BatchOperations { pub trait BatchOperations {
@ -102,7 +102,7 @@ pub trait BatchDatabase: Database {
fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>; 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<bool, Error> { fn is_mine(&self, script: &Script) -> Result<bool, Error> {
self.get_path_from_script_pubkey(script) self.get_path_from_script_pubkey(script)
.map(|o| o.is_some()) .map(|o| o.is_some())

View File

@ -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::bip32::Error, BIP32);
impl_error!(bitcoin::util::base58::Error, Base58); impl_error!(bitcoin::util::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, PK); impl_error!(bitcoin::util::key::Error, PK);

View File

@ -24,6 +24,7 @@
use std::cmp::max; use std::cmp::max;
use std::collections::{BTreeMap, HashSet, VecDeque}; use std::collections::{BTreeMap, HashSet, VecDeque};
use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use serde::ser::SerializeMap; use serde::ser::SerializeMap;
@ -423,8 +424,16 @@ pub enum PolicyError {
IncompatibleConditions, 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 { impl Policy {
pub fn new(item: SatisfiableItem) -> Self { fn new(item: SatisfiableItem) -> Self {
Policy { Policy {
id: item.id(), id: item.id(),
item, item,
@ -433,7 +442,7 @@ impl Policy {
} }
} }
pub fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> { fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
match (a, b) { match (a, b) {
(None, None) => Ok(None), (None, None) => Ok(None),
(Some(x), None) | (None, Some(x)) => Ok(Some(x)), (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
@ -441,7 +450,7 @@ impl Policy {
} }
} }
pub fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> { fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
match (a, b) { match (a, b) {
(None, None) => Ok(None), (None, None) => Ok(None),
(Some(x), None) | (None, Some(x)) => Ok(Some(x)), (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
@ -449,10 +458,7 @@ impl Policy {
} }
} }
pub fn make_thresh( fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
items: Vec<Policy>,
threshold: usize,
) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 { if threshold == 0 {
return Ok(None); return Ok(None);
} }

View File

@ -43,7 +43,9 @@ pub enum Error {
TransactionNotFound, TransactionNotFound,
TransactionConfirmed, TransactionConfirmed,
IrreplaceableTransaction, IrreplaceableTransaction,
FeeRateTooLow(crate::wallet::utils::FeeRate), FeeRateTooLow {
required: crate::types::FeeRate,
},
ChecksumMismatch, ChecksumMismatch,
DifferentDescriptorStructure, DifferentDescriptorStructure,

View File

@ -24,7 +24,7 @@
// only enables the `doc_cfg` feature when // only enables the `doc_cfg` feature when
// the `docsrs` configuration attribute is defined // the `docsrs` configuration attribute is defined
#[cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
pub extern crate bitcoin; pub extern crate bitcoin;
extern crate log; extern crate log;
@ -45,13 +45,9 @@ extern crate lazy_static;
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
pub extern crate electrum_client; pub extern crate electrum_client;
#[cfg(feature = "electrum")]
pub use electrum_client::client::Client;
#[cfg(feature = "esplora")] #[cfg(feature = "esplora")]
pub extern crate reqwest; pub extern crate reqwest;
#[cfg(feature = "esplora")]
pub use blockchain::esplora::EsploraBlockchain;
#[cfg(feature = "key-value-db")] #[cfg(feature = "key-value-db")]
pub extern crate sled; pub extern crate sled;
@ -74,11 +70,19 @@ pub mod error;
pub mod blockchain; pub mod blockchain;
pub mod database; pub mod database;
pub mod descriptor; pub mod descriptor;
pub mod psbt; pub(crate) mod psbt;
pub mod types; pub(crate) mod types;
pub mod wallet; 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::tx_builder::TxBuilder;
pub use wallet::utils::FeeRate;
pub use wallet::{OfflineWallet, Wallet}; pub use wallet::{OfflineWallet, Wallet};
#[cfg(feature = "esplora")]
pub use blockchain::esplora::EsploraBlockchain;
#[cfg(feature = "electrum")]
pub use blockchain::electrum::ElectrumBlockchain;

View File

@ -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)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct UTXO { pub struct UTXO {
pub outpoint: OutPoint, pub outpoint: OutPoint,

View File

@ -22,6 +22,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
use std::fmt;
use bitcoin::Script; use bitcoin::Script;
use crate::descriptor::HDKeyPaths; use crate::descriptor::HDKeyPaths;
@ -35,6 +37,14 @@ pub enum AddressValidatorError {
InvalidScript, 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 { pub trait AddressValidator {
fn validate( fn validate(
&self, &self,
@ -81,7 +91,7 @@ mod test {
let addr = testutils!(@external descriptors, 10); let addr = testutils!(@external descriptors, 10);
wallet wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
} }
} }

View File

@ -26,14 +26,14 @@ use bitcoin::consensus::encode::serialize;
use bitcoin::{Script, TxIn}; use bitcoin::{Script, TxIn};
use crate::error::Error; use crate::error::Error;
use crate::types::UTXO; use crate::types::{FeeRate, UTXO};
pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection; pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection;
#[derive(Debug)] #[derive(Debug)]
pub struct CoinSelectionResult { pub struct CoinSelectionResult {
pub txin: Vec<(TxIn, Script)>, pub txin: Vec<(TxIn, Script)>,
pub total_amount: u64, pub selected_amount: u64,
pub fee_amount: f32, pub fee_amount: f32,
} }
@ -42,8 +42,8 @@ pub trait CoinSelectionAlgorithm: std::fmt::Debug {
&self, &self,
utxos: Vec<UTXO>, utxos: Vec<UTXO>,
use_all_utxos: bool, use_all_utxos: bool,
fee_rate: f32, fee_rate: FeeRate,
outgoing_amount: u64, amount_needed: u64,
input_witness_weight: usize, input_witness_weight: usize,
fee_amount: f32, fee_amount: f32,
) -> Result<CoinSelectionResult, Error>; ) -> Result<CoinSelectionResult, Error>;
@ -57,16 +57,16 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
&self, &self,
mut utxos: Vec<UTXO>, mut utxos: Vec<UTXO>,
use_all_utxos: bool, use_all_utxos: bool,
fee_rate: f32, fee_rate: FeeRate,
outgoing_amount: u64, outgoing_amount: u64,
input_witness_weight: usize, input_witness_weight: usize,
mut fee_amount: f32, mut fee_amount: f32,
) -> Result<CoinSelectionResult, Error> { ) -> Result<CoinSelectionResult, Error> {
let mut txin = Vec::new(); 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!( log::debug!(
"outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{}`", "outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{:?}`",
outgoing_amount, outgoing_amount,
fee_amount, fee_amount,
fee_rate fee_rate
@ -75,11 +75,11 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
// sort so that we pick them starting from the larger. // sort so that we pick them starting from the larger.
utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap()); utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap());
let mut total_amount: u64 = 0; let mut selected_amount: u64 = 0;
while use_all_utxos || total_amount < outgoing_amount + (fee_amount.ceil() as u64) { while use_all_utxos || selected_amount < outgoing_amount + (fee_amount.ceil() as u64) {
let utxo = match utxos.pop() { let utxo = match utxos.pop() {
Some(utxo) => utxo, 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) return Err(Error::InsufficientFunds)
} }
None if use_all_utxos => break, None if use_all_utxos => break,
@ -100,13 +100,13 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
); );
txin.push((new_in, utxo.txout.script_pubkey)); txin.push((new_in, utxo.txout.script_pubkey));
total_amount += utxo.txout.value; selected_amount += utxo.txout.value;
} }
Ok(CoinSelectionResult { Ok(CoinSelectionResult {
txin, txin,
fee_amount, fee_amount,
total_amount, selected_amount,
}) })
} }
} }
@ -154,11 +154,18 @@ mod test {
let utxos = get_test_utxos(); let utxos = get_test_utxos();
let result = DumbCoinSelection 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(); .unwrap();
assert_eq!(result.txin.len(), 2); 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); assert_eq!(result.fee_amount, 186.0);
} }
@ -167,11 +174,18 @@ mod test {
let utxos = get_test_utxos(); let utxos = get_test_utxos();
let result = DumbCoinSelection 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(); .unwrap();
assert_eq!(result.txin.len(), 2); 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); assert_eq!(result.fee_amount, 186.0);
} }
@ -180,11 +194,18 @@ mod test {
let utxos = get_test_utxos(); let utxos = get_test_utxos();
let result = DumbCoinSelection 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(); .unwrap();
assert_eq!(result.txin.len(), 1); 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); assert_eq!(result.fee_amount, 118.0);
} }
@ -194,7 +215,14 @@ mod test {
let utxos = get_test_utxos(); let utxos = get_test_utxos();
DumbCoinSelection 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(); .unwrap();
} }
@ -204,7 +232,14 @@ mod test {
let utxos = get_test_utxos(); let utxos = get_test_utxos();
DumbCoinSelection 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(); .unwrap();
} }
} }

View File

@ -45,12 +45,14 @@ mod rbf;
pub mod signer; pub mod signer;
pub mod time; pub mod time;
pub mod tx_builder; pub mod tx_builder;
pub mod utils; pub(crate) mod utils;
pub use utils::IsDust;
use address_validator::AddressValidator; use address_validator::AddressValidator;
use signer::{Signer, SignerId, SignerOrdering, SignersContainer}; use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
use tx_builder::TxBuilder; use tx_builder::TxBuilder;
use utils::{After, FeeRate, IsDust, Older}; use utils::{After, Older};
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress}; use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
@ -185,7 +187,7 @@ where
&self, &self,
builder: TxBuilder<Cs>, builder: TxBuilder<Cs>,
) -> Result<(PSBT, TransactionDetails), Error> { ) -> Result<(PSBT, TransactionDetails), Error> {
if builder.addressees.is_empty() { if builder.recipients.is_empty() {
return Err(Error::NoAddressees); return Err(Error::NoAddressees);
} }
@ -239,8 +241,8 @@ where
output: vec![], output: vec![],
}; };
let fee_rate = builder.fee_rate.unwrap_or_default().as_sat_vb(); let fee_rate = builder.fee_rate.unwrap_or_default();
if builder.send_all && builder.addressees.len() != 1 { if builder.send_all && builder.recipients.len() != 1 {
return Err(Error::SendAllMultipleOutputs); return Err(Error::SendAllMultipleOutputs);
} }
@ -249,10 +251,10 @@ where
let mut outgoing: u64 = 0; let mut outgoing: u64 = 0;
let mut received: 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()); 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 { let value = match builder.send_all {
true => 0, true => 0,
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)), false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
@ -304,7 +306,7 @@ where
)?; )?;
let coin_selection::CoinSelectionResult { let coin_selection::CoinSelectionResult {
txin, txin,
total_amount, selected_amount,
mut fee_amount, mut fee_amount,
} = builder.coin_selection.coin_select( } = builder.coin_selection.coin_select(
available_utxos, available_utxos,
@ -342,7 +344,7 @@ where
}; };
let mut fee_amount = fee_amount.ceil() as u64; 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() { if !builder.send_all && !change_val.is_dust() {
let mut change_output = change_output.unwrap(); let mut change_output = change_output.unwrap();
change_output.value = change_val; change_output.value = change_val;
@ -366,7 +368,7 @@ where
} }
// sort input/outputs according to the chosen algorithm // 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 txid = tx.txid();
let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?; let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?;
@ -376,7 +378,7 @@ where
txid, txid,
timestamp: time::get_timestamp(), timestamp: time::get_timestamp(),
received, received,
sent: total_amount, sent: selected_amount,
fees: fee_amount, fees: fee_amount,
height: None, height: None,
}; };
@ -409,7 +411,9 @@ where
let new_feerate = builder.fee_rate.unwrap_or_default(); let new_feerate = builder.fee_rate.unwrap_or_default();
if new_feerate < required_feerate { if new_feerate < required_feerate {
return Err(Error::FeeRateTooLow(required_feerate)); return Err(Error::FeeRateTooLow {
required: required_feerate,
});
} }
let mut fee_difference = let mut fee_difference =
(new_feerate.as_sat_vb() * tx.get_weight() as f32 / 4.0).ceil() as u64 - details.fees; (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 { let coin_selection::CoinSelectionResult {
txin, txin,
total_amount, selected_amount,
fee_amount, fee_amount,
} = builder.coin_selection.coin_select( } = builder.coin_selection.coin_select(
available_utxos, available_utxos,
use_all_utxos, use_all_utxos,
new_feerate.as_sat_vb(), new_feerate,
fee_difference fee_difference
.checked_sub(removed_change_output.value) .checked_sub(removed_change_output.value)
.unwrap_or(0), .unwrap_or(0),
@ -538,8 +542,8 @@ where
.for_each(|i| i.sequence = tx.input[0].sequence); .for_each(|i| i.sequence = tx.input[0].sequence);
tx.input.extend_from_slice(&mut txin); tx.input.extend_from_slice(&mut txin);
details.sent += total_amount; details.sent += selected_amount;
total_amount selected_amount
} else { } else {
// otherwise just remove the output and add 0 new coins // otherwise just remove the output and add 0 new coins
0 0
@ -570,7 +574,7 @@ where
} }
// sort input/outputs according to the chosen algorithm // 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 // TODO: check that we are not replacing more than 100 txs from mempool
@ -620,9 +624,7 @@ where
} }
// attempt to finalize // attempt to finalize
let finalized = self.finalize_psbt(&mut psbt, assume_height)?; self.finalize_psbt(psbt, assume_height)
Ok((psbt, finalized))
} }
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> { pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
@ -650,9 +652,9 @@ where
pub fn finalize_psbt( pub fn finalize_psbt(
&self, &self,
psbt: &mut PSBT, mut psbt: PSBT,
assume_height: Option<u32>, assume_height: Option<u32>,
) -> Result<bool, Error> { ) -> Result<(PSBT, bool), Error> {
let mut tx = psbt.global.unsigned_tx.clone(); let mut tx = psbt.global.unsigned_tx.clone();
for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() { for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
@ -696,7 +698,7 @@ where
desc desc
} else { } else {
debug!("Couldn't find the right derived descriptor for input {}", n); debug!("Couldn't find the right derived descriptor for input {}", n);
return Ok(false); return Ok((psbt, false));
}; };
match desc.satisfy( match desc.satisfy(
@ -710,7 +712,7 @@ where
Ok(_) => continue, Ok(_) => continue,
Err(e) => { Err(e) => {
debug!("satisfy error {:?} for input {}", e, n); 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); psbt_input.final_script_witness = Some(input.witness);
} }
Ok(true) Ok((psbt, true))
} }
// Internals // Internals
@ -1236,10 +1238,10 @@ mod test {
#[test] #[test]
#[should_panic(expected = "NoAddressees")] #[should_panic(expected = "NoAddressees")]
fn test_create_tx_empty_addressees() { fn test_create_tx_empty_recipients() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
wallet wallet
.create_tx(TxBuilder::from_addressees(vec![]).version(0)) .create_tx(TxBuilder::with_recipients(vec![]).version(0))
.unwrap(); .unwrap();
} }
@ -1249,7 +1251,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(0)) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0))
.unwrap(); .unwrap();
} }
@ -1261,7 +1263,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(1)) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1))
.unwrap(); .unwrap();
} }
@ -1270,7 +1272,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.version, 42); assert_eq!(psbt.global.unsigned_tx.version, 42);
@ -1281,7 +1283,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 0); 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 (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000); 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 (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000); 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 (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000); 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 (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(50000)) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000))
.unwrap(); .unwrap();
} }
@ -1337,7 +1339,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6); 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 (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD); 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 (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet 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(); .unwrap();
} }
@ -1371,7 +1373,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE); assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
@ -1384,7 +1386,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr, 25_000)]) TxBuilder::with_recipients(vec![(addr, 25_000)])
.enable_rbf_with_sequence(0xFFFFFFFE), .enable_rbf_with_sequence(0xFFFFFFFE),
) )
.unwrap(); .unwrap();
@ -1396,7 +1398,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr, 25_000)]) TxBuilder::with_recipients(vec![(addr, 25_000)])
.enable_rbf_with_sequence(0xDEADBEEF), .enable_rbf_with_sequence(0xDEADBEEF),
) )
.unwrap(); .unwrap();
@ -1409,7 +1411,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF); assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
@ -1424,7 +1426,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet wallet
.create_tx( .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(); .unwrap();
} }
@ -1436,7 +1438,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
wallet wallet
.create_tx( .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(); .unwrap();
} }
@ -1446,7 +1448,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet 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(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1); assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
@ -1461,7 +1463,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet 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(); .unwrap();
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature); 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 addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet let (psbt, details) = wallet
.create_tx( .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)) .fee_rate(FeeRate::from_sat_per_vb(5.0))
.send_all(), .send_all(),
) )
@ -1490,7 +1492,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet let (psbt, details) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 25_000)]) TxBuilder::with_recipients(vec![(addr.clone(), 25_000)])
.ordering(TxOrdering::Untouched), .ordering(TxOrdering::Untouched),
) )
.unwrap(); .unwrap();
@ -1508,7 +1510,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 49_800)])) .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)]))
.unwrap(); .unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1); 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 // very high fee rate, so that the only output would be below dust
wallet wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 0)]) TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.send_all() .send_all()
.fee_rate(super::utils::FeeRate::from_sat_per_vb(453.0)), .fee_rate(crate::FeeRate::from_sat_per_vb(453.0)),
) )
.unwrap(); .unwrap();
} }
@ -1536,7 +1538,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet let (psbt, details) = wallet
.create_tx( .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), .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
) )
.unwrap(); .unwrap();
@ -1555,7 +1557,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 30_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]))
.unwrap(); .unwrap();
assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All)); 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 addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 30_000)]) TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])
.sighash(bitcoin::SigHashType::Single), .sighash(bitcoin::SigHashType::Single),
) )
.unwrap(); .unwrap();
@ -1586,7 +1588,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)"); let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1); assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
@ -1610,7 +1612,7 @@ mod test {
let addr = testutils!(@external descriptors, 5); let addr = testutils!(@external descriptors, 5);
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1); assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
@ -1631,7 +1633,7 @@ mod test {
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!( assert_eq!(
@ -1654,7 +1656,7 @@ mod test {
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert_eq!(psbt.inputs[0].redeem_script, None); assert_eq!(psbt.inputs[0].redeem_script, None);
@ -1677,7 +1679,7 @@ mod test {
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))"); get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
let script = Script::from( let script = Script::from(
@ -1697,7 +1699,7 @@ mod test {
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some()); assert!(psbt.inputs[0].non_witness_utxo.is_some());
@ -1710,7 +1712,7 @@ mod test {
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none()); assert!(psbt.inputs[0].non_witness_utxo.is_none());
@ -1724,7 +1726,7 @@ mod test {
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet let (psbt, _) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 0)]) TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.force_non_witness_utxo() .force_non_witness_utxo()
.send_all(), .send_all(),
) )
@ -1740,7 +1742,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet let (psbt, mut details) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
let txid = tx.txid(); let txid = tx.txid();
@ -1757,7 +1759,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet let (psbt, mut details) = wallet
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)])) .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap(); .unwrap();
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
let txid = tx.txid(); let txid = tx.txid();
@ -1775,7 +1777,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet 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(); .unwrap();
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
let txid = tx.txid(); let txid = tx.txid();
@ -1796,7 +1798,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet 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(); .unwrap();
let mut tx = psbt.extract_tx(); let mut tx = psbt.extract_tx();
let txid = tx.txid(); let txid = tx.txid();
@ -1858,7 +1860,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet let (psbt, mut original_details) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 0)]) TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.send_all() .send_all()
.enable_rbf(), .enable_rbf(),
) )
@ -1912,7 +1914,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet let (psbt, mut original_details) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 0)]) TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.utxos(vec![OutPoint { .utxos(vec![OutPoint {
txid: incoming_txid, txid: incoming_txid,
vout: 0, vout: 0,
@ -1959,7 +1961,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet 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(); .unwrap();
let mut tx = psbt.extract_tx(); let mut tx = psbt.extract_tx();
let txid = tx.txid(); let txid = tx.txid();
@ -2023,7 +2025,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet let (psbt, mut original_details) = wallet
.create_tx( .create_tx(
TxBuilder::from_addressees(vec![(addr.clone(), 0)]) TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.send_all() .send_all()
.add_utxo(OutPoint { .add_utxo(OutPoint {
txid: incoming_txid, txid: incoming_txid,
@ -2099,7 +2101,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet 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(); .unwrap();
let mut tx = psbt.extract_tx(); let mut tx = psbt.extract_tx();
assert_eq!(tx.input.len(), 1); assert_eq!(tx.input.len(), 1);
@ -2159,7 +2161,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet 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(); .unwrap();
let mut tx = psbt.extract_tx(); let mut tx = psbt.extract_tx();
let txid = tx.txid(); let txid = tx.txid();
@ -2224,7 +2226,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
@ -2240,7 +2242,7 @@ mod test {
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet 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(); .unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
@ -2255,7 +2257,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_new_address().unwrap();
let (mut psbt, _) = wallet 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(); .unwrap();
psbt.inputs[0].hd_keypaths.clear(); psbt.inputs[0].hd_keypaths.clear();

View File

@ -84,6 +84,14 @@ pub enum SignerError {
MissingHDKeypath, 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 /// Trait for signers
pub trait Signer: fmt::Debug { pub trait Signer: fmt::Debug {
fn sign( fn sign(
@ -92,9 +100,7 @@ pub trait Signer: fmt::Debug {
input_index: Option<usize>, input_index: Option<usize>,
) -> Result<(), SignerError>; ) -> Result<(), SignerError>;
fn sign_whole_tx(&self) -> bool { fn sign_whole_tx(&self) -> bool;
false
}
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> { fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
None None
@ -128,6 +134,10 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
derived_key.private_key.sign(psbt, Some(input_index)) derived_key.private_key.sign(psbt, Some(input_index))
} }
fn sign_whole_tx(&self) -> bool {
false
}
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> { fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
Some(DescriptorSecretKey::XPrv(self.clone())) Some(DescriptorSecretKey::XPrv(self.clone()))
} }
@ -176,6 +186,10 @@ impl Signer for PrivateKey {
Ok(()) Ok(())
} }
fn sign_whole_tx(&self) -> bool {
false
}
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> { fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
Some(DescriptorSecretKey::PrivKey(self.clone())) Some(DescriptorSecretKey::PrivKey(self.clone()))
} }
@ -299,7 +313,7 @@ impl<Pk: MiniscriptKey> SignersContainer<Pk> {
} }
} }
pub trait ComputeSighash { pub(crate) trait ComputeSighash {
fn sighash( fn sighash(
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
input_index: usize, input_index: usize,

View File

@ -44,9 +44,9 @@ pub fn get_timestamp() -> u64 {
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub struct Instant(SystemInstant); pub(crate) struct Instant(SystemInstant);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub struct Instant(Duration); pub(crate) struct Instant(Duration);
impl Instant { impl Instant {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]

View File

@ -28,12 +28,11 @@ use std::default::Default;
use bitcoin::{Address, OutPoint, SigHashType, Transaction}; use bitcoin::{Address, OutPoint, SigHashType, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use super::utils::FeeRate; use crate::types::{FeeRate, UTXO};
use crate::types::UTXO;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> { pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
pub(crate) addressees: Vec<(Address, u64)>, pub(crate) recipients: Vec<(Address, u64)>,
pub(crate) send_all: bool, pub(crate) send_all: bool,
pub(crate) fee_rate: Option<FeeRate>, pub(crate) fee_rate: Option<FeeRate>,
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>, pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
@ -54,19 +53,19 @@ impl TxBuilder<DefaultCoinSelectionAlgorithm> {
Self::default() Self::default()
} }
pub fn from_addressees(addressees: Vec<(Address, u64)>) -> Self { pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self {
Self::default().set_addressees(addressees) Self::default().set_recipients(recipients)
} }
} }
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> { impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
pub fn set_addressees(mut self, addressees: Vec<(Address, u64)>) -> Self { pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self {
self.addressees = addressees; self.recipients = recipients;
self self
} }
pub fn add_addressee(mut self, address: Address, amount: u64) -> Self { pub fn add_recipient(mut self, address: Address, amount: u64) -> Self {
self.addressees.push((address, amount)); self.recipients.push((address, amount));
self self
} }
@ -158,7 +157,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> { pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
TxBuilder { TxBuilder {
addressees: self.addressees, recipients: self.recipients,
send_all: self.send_all, send_all: self.send_all,
fee_rate: self.fee_rate, fee_rate: self.fee_rate,
policy_path: self.policy_path, policy_path: self.policy_path,
@ -190,7 +189,7 @@ impl Default for TxOrdering {
} }
impl TxOrdering { impl TxOrdering {
pub fn modify_tx(&self, tx: &mut Transaction) { pub fn sort_tx(&self, tx: &mut Transaction) {
match self { match self {
TxOrdering::Untouched => {} TxOrdering::Untouched => {}
TxOrdering::Shuffle => { TxOrdering::Shuffle => {
@ -279,7 +278,7 @@ mod test {
let original_tx = ordering_test_tx!(); let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone(); let mut tx = original_tx.clone();
TxOrdering::Untouched.modify_tx(&mut tx); TxOrdering::Untouched.sort_tx(&mut tx);
assert_eq!(original_tx, tx); assert_eq!(original_tx, tx);
} }
@ -289,7 +288,7 @@ mod test {
let original_tx = ordering_test_tx!(); let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone(); 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_eq!(original_tx.input, tx.input);
assert_ne!(original_tx.output, tx.output); assert_ne!(original_tx.output, tx.output);
@ -302,7 +301,7 @@ mod test {
let original_tx = ordering_test_tx!(); let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone(); let mut tx = original_tx.clone();
TxOrdering::BIP69Lexicographic.modify_tx(&mut tx); TxOrdering::BIP69Lexicographic.sort_tx(&mut tx);
assert_eq!( assert_eq!(
tx.input[0].previous_output, tx.input[0].previous_output,

View File

@ -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 struct After {
pub current_height: Option<u32>, pub current_height: Option<u32>,
pub assume_height_reached: bool, pub assume_height_reached: bool,
@ -158,7 +130,7 @@ impl<I: Iterator> Iterator for ChunksIterator<I> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use crate::types::FeeRate;
#[test] #[test]
fn test_fee_from_btc_per_kb() { fn test_fee_from_btc_per_kb() {

View File

@ -83,7 +83,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
use #root_ident::descriptor::ExtendedDescriptor; use #root_ident::descriptor::ExtendedDescriptor;
use #root_ident::database::MemoryDatabase; use #root_ident::database::MemoryDatabase;
use #root_ident::types::ScriptType; use #root_ident::types::ScriptType;
use #root_ident::{Wallet, TxBuilder}; use #root_ident::{Wallet, TxBuilder, FeeRate};
use super::*; use super::*;
@ -307,7 +307,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx(); 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(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap(); 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; let mut total_sent = 0;
for _ in 0..5 { 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); 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(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); 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(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); 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(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000); 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); 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(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000); 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(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();