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]
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::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;

View File

@ -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<D: BatchDatabase + DatabaseUtils>(
fn process_tx<D: BatchDatabase>(
&self,
database: &mut D,
tx: &Transaction,
@ -207,7 +208,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
vec![Capability::FullHistory].into_iter().collect()
}
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
_stop_gap: Option<usize>, // TODO: move to electrum and esplora only
database: &mut D,
@ -460,6 +461,14 @@ pub enum CompactFiltersError {
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 {
( $from:ty, $to:ident ) => {
impl std::convert::From<$from> for CompactFiltersError {

View File

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

View File

@ -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<D: BatchDatabase + DatabaseUtils, P: Progress>(
fn setup<D: BatchDatabase, P: Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,

View File

@ -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<D: BatchDatabase + DatabaseUtils, P: Progress>(
fn setup<D: BatchDatabase, P: Progress>(
&self,
stop_gap: Option<usize>,
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<reqwest::Error> for EsploraError {
fn from(other: reqwest::Error) -> Self {
EsploraError::Reqwest(other)

View File

@ -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<Capability>;
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
progress_update: P,
) -> Result<(), Error>;
fn sync<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
fn sync<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
@ -163,7 +168,7 @@ impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
maybe_await!(self.deref().get_capabilities())
}
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
@ -172,7 +177,7 @@ impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
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,
stop_gap: Option<usize>,
database: &mut D,

View File

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

View File

@ -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, String> {
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::<Result<Vec<_>, _>>()
.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,

View File

@ -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<bool, Error> {
self.get_path_from_script_pubkey(script)
.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::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, PK);

View File

@ -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<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, 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<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, 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<Policy>,
threshold: usize,
) -> Result<Option<Policy>, PolicyError> {
fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
return Ok(None);
}

View File

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

View File

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

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)]
pub struct UTXO {
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
// 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();
}
}

View File

@ -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<UTXO>,
use_all_utxos: bool,
fee_rate: f32,
outgoing_amount: u64,
fee_rate: FeeRate,
amount_needed: u64,
input_witness_weight: usize,
fee_amount: f32,
) -> Result<CoinSelectionResult, Error>;
@ -57,16 +57,16 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
&self,
mut utxos: Vec<UTXO>,
use_all_utxos: bool,
fee_rate: f32,
fee_rate: FeeRate,
outgoing_amount: u64,
input_witness_weight: usize,
mut fee_amount: f32,
) -> Result<CoinSelectionResult, Error> {
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();
}
}

View File

@ -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<Cs>,
) -> 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<Option<Policy>, Error> {
@ -650,9 +652,9 @@ where
pub fn finalize_psbt(
&self,
psbt: &mut PSBT,
mut psbt: PSBT,
assume_height: Option<u32>,
) -> Result<bool, Error> {
) -> 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();

View File

@ -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<usize>,
) -> Result<(), SignerError>;
fn sign_whole_tx(&self) -> bool {
false
}
fn sign_whole_tx(&self) -> bool;
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
None
@ -128,6 +134,10 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
derived_key.private_key.sign(psbt, Some(input_index))
}
fn sign_whole_tx(&self) -> bool {
false
}
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
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<DescriptorSecretKey> {
Some(DescriptorSecretKey::PrivKey(self.clone()))
}
@ -299,7 +313,7 @@ impl<Pk: MiniscriptKey> SignersContainer<Pk> {
}
}
pub trait ComputeSighash {
pub(crate) trait ComputeSighash {
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,

View File

@ -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"))]

View File

@ -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<Cs: CoinSelectionAlgorithm> {
pub(crate) addressees: Vec<(Address, u64)>,
pub(crate) recipients: Vec<(Address, u64)>,
pub(crate) send_all: bool,
pub(crate) fee_rate: Option<FeeRate>,
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
@ -54,19 +53,19 @@ impl TxBuilder<DefaultCoinSelectionAlgorithm> {
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<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
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<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
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,

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