[wallet] Make coin_select return UTXOs instead of TxIns
- We want to keep the metadata in the UTXO around for things later - It is easier to turn a UTXO into a TxIn outside
This commit is contained in:
parent
13c1170304
commit
004f81b0a8
@ -63,18 +63,10 @@
|
||||
//! let all_utxos_selected = required_utxos
|
||||
//! .into_iter().chain(optional_utxos)
|
||||
//! .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), (utxo, weight)| {
|
||||
//! let txin = TxIn {
|
||||
//! previous_output: utxo.outpoint,
|
||||
//! ..Default::default()
|
||||
//! };
|
||||
//!
|
||||
//! **selected_amount += utxo.txout.value;
|
||||
//! **additional_weight += TXIN_BASE_WEIGHT + weight;
|
||||
//!
|
||||
//! Some((
|
||||
//! txin,
|
||||
//! utxo.txout.script_pubkey,
|
||||
//! ))
|
||||
//! Some(utxo)
|
||||
//! })
|
||||
//! .collect::<Vec<_>>();
|
||||
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
|
||||
@ -84,7 +76,7 @@
|
||||
//! }
|
||||
//!
|
||||
//! Ok(CoinSelectionResult {
|
||||
//! txin: all_utxos_selected,
|
||||
//! selected: all_utxos_selected,
|
||||
//! selected_amount,
|
||||
//! fee_amount: fee_amount + additional_fees,
|
||||
//! })
|
||||
@ -105,8 +97,6 @@
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
|
||||
use bitcoin::{Script, TxIn};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::error::Error;
|
||||
use crate::types::{FeeRate, UTXO};
|
||||
@ -124,11 +114,15 @@ pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
|
||||
#[cfg(test)]
|
||||
pub type DefaultCoinSelectionAlgorithm = LargestFirstCoinSelection; // make the tests more predictable
|
||||
|
||||
// Base weight of a Txin, not counting the weight needed for satisfaying it.
|
||||
// prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes) + script_len (1 bytes)
|
||||
pub const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4;
|
||||
|
||||
/// Result of a successful coin selection
|
||||
#[derive(Debug)]
|
||||
pub struct CoinSelectionResult {
|
||||
/// List of inputs to use, with the respective previous script_pubkey
|
||||
pub txin: Vec<(TxIn, Script)>,
|
||||
/// List of outputs selected for use as inputs
|
||||
pub selected: Vec<UTXO>,
|
||||
/// Sum of the selected inputs' value
|
||||
pub selected_amount: u64,
|
||||
/// Total fee amount in satoshi
|
||||
@ -204,28 +198,21 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
|
||||
// Keep including inputs until we've got enough.
|
||||
// Store the total input value in selected_amount and the total fee being paid in fee_amount
|
||||
let mut selected_amount = 0;
|
||||
let txin = utxos
|
||||
let selected = utxos
|
||||
.scan(
|
||||
(&mut selected_amount, &mut fee_amount),
|
||||
|(selected_amount, fee_amount), (required, (utxo, weight))| {
|
||||
if required || **selected_amount < amount_needed + (fee_amount.ceil() as u64) {
|
||||
let new_in = TxIn {
|
||||
previous_output: utxo.outpoint,
|
||||
script_sig: Script::default(),
|
||||
sequence: 0, // Let the caller choose the right nSequence
|
||||
witness: vec![],
|
||||
};
|
||||
|
||||
|(selected_amount, fee_amount), (must_use, (utxo, weight))| {
|
||||
if must_use || **selected_amount < amount_needed + (fee_amount.ceil() as u64) {
|
||||
**fee_amount += calc_fee_bytes(TXIN_BASE_WEIGHT + weight);
|
||||
**selected_amount += utxo.txout.value;
|
||||
|
||||
log::debug!(
|
||||
"Selected {}, updated fee_amount = `{}`",
|
||||
new_in.previous_output,
|
||||
utxo.outpoint,
|
||||
fee_amount
|
||||
);
|
||||
|
||||
Some((new_in, utxo.txout.script_pubkey))
|
||||
Some(utxo)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -238,17 +225,13 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
|
||||
}
|
||||
|
||||
Ok(CoinSelectionResult {
|
||||
txin,
|
||||
selected,
|
||||
fee_amount,
|
||||
selected_amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Base weight of a Txin, not counting the weight needed for satisfaying it.
|
||||
// prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes) + script_len (1 bytes)
|
||||
pub const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// Adds fee information to an UTXO.
|
||||
struct OutputGroup {
|
||||
@ -507,28 +490,20 @@ impl BranchAndBoundCoinSelection {
|
||||
}
|
||||
|
||||
fn calculate_cs_result(
|
||||
selected_utxos: Vec<OutputGroup>,
|
||||
required_utxos: Vec<OutputGroup>,
|
||||
fee_amount: f32,
|
||||
mut selected_utxos: Vec<OutputGroup>,
|
||||
mut required_utxos: Vec<OutputGroup>,
|
||||
mut fee_amount: f32,
|
||||
) -> CoinSelectionResult {
|
||||
let (txin, fee_amount, selected_amount) =
|
||||
selected_utxos.into_iter().chain(required_utxos).fold(
|
||||
(vec![], fee_amount, 0),
|
||||
|(mut txin, mut fee_amount, mut selected_amount), output_group| {
|
||||
selected_amount += output_group.utxo.txout.value;
|
||||
fee_amount += output_group.fee;
|
||||
txin.push((
|
||||
TxIn {
|
||||
previous_output: output_group.utxo.outpoint,
|
||||
..Default::default()
|
||||
},
|
||||
output_group.utxo.txout.script_pubkey,
|
||||
));
|
||||
(txin, fee_amount, selected_amount)
|
||||
},
|
||||
);
|
||||
selected_utxos.append(&mut required_utxos);
|
||||
fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>();
|
||||
let selected = selected_utxos
|
||||
.into_iter()
|
||||
.map(|u| u.utxo)
|
||||
.collect::<Vec<_>>();
|
||||
let selected_amount = selected.iter().map(|u| u.txout.value).sum();
|
||||
|
||||
CoinSelectionResult {
|
||||
txin,
|
||||
selected,
|
||||
fee_amount,
|
||||
selected_amount,
|
||||
}
|
||||
@ -539,7 +514,6 @@ impl BranchAndBoundCoinSelection {
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::consensus::encode::serialize;
|
||||
use bitcoin::{OutPoint, Script, TxOut};
|
||||
|
||||
use super::*;
|
||||
@ -648,7 +622,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.txin.len(), 2);
|
||||
assert_eq!(result.selected.len(), 2);
|
||||
assert_eq!(result.selected_amount, 300_000);
|
||||
assert_eq!(result.fee_amount, 186.0);
|
||||
}
|
||||
@ -669,7 +643,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.txin.len(), 2);
|
||||
assert_eq!(result.selected.len(), 2);
|
||||
assert_eq!(result.selected_amount, 300_000);
|
||||
assert_eq!(result.fee_amount, 186.0);
|
||||
}
|
||||
@ -690,7 +664,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.txin.len(), 1);
|
||||
assert_eq!(result.selected.len(), 1);
|
||||
assert_eq!(result.selected_amount, 200_000);
|
||||
assert_eq!(result.fee_amount, 118.0);
|
||||
}
|
||||
@ -750,7 +724,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.txin.len(), 3);
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount, 300_000);
|
||||
assert_eq!(result.fee_amount, 254.0);
|
||||
}
|
||||
@ -771,7 +745,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.txin.len(), 2);
|
||||
assert_eq!(result.selected.len(), 2);
|
||||
assert_eq!(result.selected_amount, 300_000);
|
||||
assert_eq!(result.fee_amount, 186.0);
|
||||
}
|
||||
@ -828,12 +802,11 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.txin.len(), 1);
|
||||
assert_eq!(result.selected.len(), 1);
|
||||
assert_eq!(result.selected_amount, 100_000);
|
||||
let result_size =
|
||||
serialize(result.txin.first().unwrap()).len() as f32 + P2WPKH_WITNESS_SIZE as f32 / 4.0;
|
||||
let input_size = (TXIN_BASE_WEIGHT as f32) / 4.0 + P2WPKH_WITNESS_SIZE as f32 / 4.0;
|
||||
let epsilon = 0.5;
|
||||
assert!((1.0 - (result.fee_amount / result_size)).abs() < epsilon);
|
||||
assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1013,6 +986,9 @@ mod test {
|
||||
);
|
||||
|
||||
assert!(result.selected_amount > target_amount);
|
||||
assert_eq!(result.fee_amount, 50.0 + result.txin.len() as f32 * 68.0);
|
||||
assert_eq!(
|
||||
result.fee_amount,
|
||||
50.0 + result.selected.len() as f32 * 68.0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -414,7 +414,7 @@ where
|
||||
)?;
|
||||
|
||||
let coin_selection::CoinSelectionResult {
|
||||
txin,
|
||||
selected,
|
||||
selected_amount,
|
||||
mut fee_amount,
|
||||
} = builder.coin_selection.coin_select(
|
||||
@ -425,16 +425,15 @@ where
|
||||
outgoing,
|
||||
fee_amount,
|
||||
)?;
|
||||
let (mut txin, prev_script_pubkeys): (Vec<_>, Vec<_>) = txin.into_iter().unzip();
|
||||
// map that allows us to lookup the prev_script_pubkey for a given previous_output
|
||||
let prev_script_pubkeys = txin
|
||||
tx.input = selected
|
||||
.iter()
|
||||
.zip(prev_script_pubkeys.into_iter())
|
||||
.map(|(txin, script)| (txin.previous_output, script))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
txin.iter_mut().for_each(|i| i.sequence = n_sequence);
|
||||
tx.input = txin;
|
||||
.map(|u| bitcoin::TxIn {
|
||||
previous_output: u.outpoint,
|
||||
script_sig: Script::default(),
|
||||
sequence: n_sequence,
|
||||
witness: vec![],
|
||||
})
|
||||
.collect();
|
||||
|
||||
// prepare the change output
|
||||
let change_output = match builder.single_recipient {
|
||||
@ -485,7 +484,11 @@ where
|
||||
builder.ordering.sort_tx(&mut tx);
|
||||
|
||||
let txid = tx.txid();
|
||||
let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?;
|
||||
let lookup_output = selected
|
||||
.into_iter()
|
||||
.map(|utxo| (utxo.outpoint, utxo))
|
||||
.collect();
|
||||
let psbt = self.complete_transaction(tx, lookup_output, builder)?;
|
||||
|
||||
let transaction_details = TransactionDetails {
|
||||
transaction: None,
|
||||
@ -701,7 +704,7 @@ where
|
||||
};
|
||||
|
||||
let coin_selection::CoinSelectionResult {
|
||||
txin,
|
||||
selected,
|
||||
selected_amount,
|
||||
fee_amount,
|
||||
} = builder.coin_selection.coin_select(
|
||||
@ -713,18 +716,16 @@ where
|
||||
initial_fee,
|
||||
)?;
|
||||
|
||||
let (mut txin, prev_script_pubkeys): (Vec<_>, Vec<_>) = txin.into_iter().unzip();
|
||||
// map that allows us to lookup the prev_script_pubkey for a given previous_output
|
||||
let prev_script_pubkeys = txin
|
||||
tx.input = selected
|
||||
.iter()
|
||||
.zip(prev_script_pubkeys.into_iter())
|
||||
.map(|(txin, script)| (txin.previous_output, script))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
// TODO: use builder.n_sequence??
|
||||
// use the same n_sequence
|
||||
txin.iter_mut().for_each(|i| i.sequence = original_sequence);
|
||||
tx.input = txin;
|
||||
.map(|u| bitcoin::TxIn {
|
||||
previous_output: u.outpoint,
|
||||
script_sig: Script::default(),
|
||||
// TODO: use builder.n_sequence??
|
||||
sequence: original_sequence,
|
||||
witness: vec![],
|
||||
})
|
||||
.collect();
|
||||
|
||||
details.sent = selected_amount;
|
||||
|
||||
@ -770,10 +771,14 @@ where
|
||||
// TODO: check that we are not replacing more than 100 txs from mempool
|
||||
|
||||
details.txid = tx.txid();
|
||||
let lookup_output = selected
|
||||
.into_iter()
|
||||
.map(|utxo| (utxo.outpoint, utxo))
|
||||
.collect();
|
||||
details.fees = fee_amount;
|
||||
details.timestamp = time::get_timestamp();
|
||||
|
||||
let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?;
|
||||
let psbt = self.complete_transaction(tx, lookup_output, builder)?;
|
||||
|
||||
Ok((psbt, details))
|
||||
}
|
||||
@ -1126,7 +1131,7 @@ where
|
||||
>(
|
||||
&self,
|
||||
tx: Transaction,
|
||||
prev_script_pubkeys: HashMap<OutPoint, Script>,
|
||||
lookup_output: HashMap<OutPoint, UTXO>,
|
||||
builder: TxBuilder<D, Cs, Ctx>,
|
||||
) -> Result<PSBT, Error> {
|
||||
let mut psbt = PSBT::from_unsigned_tx(tx)?;
|
||||
@ -1137,8 +1142,8 @@ where
|
||||
.iter_mut()
|
||||
.zip(psbt.global.unsigned_tx.input.iter())
|
||||
{
|
||||
let prev_script = match prev_script_pubkeys.get(&input.previous_output) {
|
||||
Some(prev_script) => prev_script,
|
||||
let utxo = match lookup_output.get(&input.previous_output) {
|
||||
Some(utxo) => utxo,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
@ -1153,7 +1158,7 @@ where
|
||||
let (script_type, child) = match self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&prev_script)?
|
||||
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
|
||||
{
|
||||
Some(x) => x,
|
||||
None => continue,
|
||||
|
Loading…
x
Reference in New Issue
Block a user