From 0f8571b90127fc68ac1304892ca95fee8e16d024 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:18:34 +0000 Subject: [PATCH 1/4] descriptors: use witness scale factor from lib --- src/descriptors/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index e09c7e26..e4f7c916 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -1,6 +1,7 @@ use miniscript::{ bitcoin::{ self, bip32, + constants::WITNESS_SCALE_FACTOR, psbt::{Input as PsbtIn, Psbt}, secp256k1, }, @@ -17,8 +18,6 @@ pub use keys::*; pub mod analysis; pub use analysis::*; -pub const WITNESS_FACTOR: usize = 4; - #[derive(Debug)] pub enum LianaDescError { Miniscript(miniscript::Error), @@ -204,9 +203,9 @@ impl LianaDescriptor { /// size of the witness stack length varint. pub fn max_sat_vbytes(&self) -> usize { self.max_sat_weight() - .checked_add(WITNESS_FACTOR - 1) + .checked_add(WITNESS_SCALE_FACTOR - 1) .unwrap() - .checked_div(WITNESS_FACTOR) + .checked_div(WITNESS_SCALE_FACTOR) .unwrap() } @@ -476,8 +475,8 @@ mod tests { // Convert a size in weight units to a size in virtual bytes, rounding up. fn wu_to_vb(vb: usize) -> usize { - (vb + WITNESS_FACTOR - 1) - .checked_div(WITNESS_FACTOR) + (vb + WITNESS_SCALE_FACTOR - 1) + .checked_div(WITNESS_SCALE_FACTOR) .expect("Non 0") } From e9264bdf0d8ed2b1532a9cde231a0e86ff657690 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:34:54 +0000 Subject: [PATCH 2/4] descriptors: add helper for unsigned tx max size --- src/descriptors/mod.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index e4f7c916..b822ac82 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -8,7 +8,7 @@ use miniscript::{ descriptor, translate_hash_clone, ForEachKey, TranslatePk, Translator, }; -use std::{collections::BTreeMap, error, fmt, str}; +use std::{collections::BTreeMap, convert::TryInto, error, fmt, str}; use serde::{Deserialize, Serialize}; @@ -354,6 +354,26 @@ impl LianaDescriptor { .unwrap_or(&policy.primary_path); Ok(self.prune_bip32_derivs(psbt, path_info)) } + + /// Maximum possible size in vbytes of an unsigned transaction, `tx`, + /// after satisfaction, assuming all inputs of `tx` are from this + /// descriptor. + pub fn unsigned_tx_max_vbytes(&self, tx: &bitcoin::Transaction) -> u64 { + let witness_factor: u64 = WITNESS_SCALE_FACTOR.try_into().unwrap(); + let num_inputs: u64 = tx.input.len().try_into().unwrap(); + let max_sat_weight: u64 = self.max_sat_weight().try_into().unwrap(); + // Add weights together before converting to vbytes to avoid rounding up multiple times. + let tx_wu = tx + .weight() + .to_wu() + .checked_add(max_sat_weight.checked_mul(num_inputs).unwrap()) + .unwrap(); + tx_wu + .checked_add(witness_factor.checked_sub(1).unwrap()) + .unwrap() + .checked_div(witness_factor) + .unwrap() + } } impl SinglePathLianaDesc { From af416f6502924946da8d2e9a070fde2f5d731776 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:43:04 +0000 Subject: [PATCH 3/4] commands: use helper for unsigned tx max size --- src/commands/mod.rs | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6f3510b7..12343d37 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -29,10 +29,7 @@ use std::{ }; use miniscript::{ - bitcoin::{ - self, address, bip32, constants::WITNESS_SCALE_FACTOR, - psbt::PartiallySignedTransaction as Psbt, - }, + bitcoin::{self, address, bip32, psbt::PartiallySignedTransaction as Psbt}, psbt::PsbtExt, }; use serde::{Deserialize, Serialize}; @@ -179,29 +176,6 @@ impl<'a> TxGetter for BitcoindTxGetter<'a> { } } -/// An unsigned transaction's maximum possible size in vbytes after satisfaction. -/// -/// This assumes all inputs are internal (or have the same `max_sat_weight` value). -/// -/// `tx` is the unsigned transaction. -/// -/// `max_sat_weight` is the maximum weight difference of an input in the -/// transaction before and after satisfaction. Must be in weight units. -fn unsigned_tx_max_vbytes(tx: &bitcoin::Transaction, max_sat_weight: u64) -> u64 { - let witness_factor: u64 = WITNESS_SCALE_FACTOR.try_into().unwrap(); - let num_inputs: u64 = tx.input.len().try_into().unwrap(); - let tx_wu: u64 = tx - .weight() - .to_wu() - .checked_add(max_sat_weight.checked_mul(num_inputs).unwrap()) - .unwrap(); - tx_wu - .checked_add(witness_factor.checked_sub(1).unwrap()) - .unwrap() - .checked_div(witness_factor) - .unwrap() -} - fn coin_to_candidate( coin: &Coin, must_select: bool, @@ -826,12 +800,6 @@ impl DaemonControl { if !is_cancel { candidate_coins.extend(&confirmed_cands); } - let max_sat_weight: u64 = self - .config - .main_descriptor - .max_sat_weight() - .try_into() - .expect("it must fit"); // Try with increasing fee until fee paid by replacement transaction is high enough. // Replacement fee must be at least: // sum of fees paid by original transactions + incremental feerate * replacement size. @@ -868,7 +836,10 @@ impl DaemonControl { return Err(e.into()); } }; - replacement_vsize = unsigned_tx_max_vbytes(&rbf_psbt.unsigned_tx, max_sat_weight); + replacement_vsize = self + .config + .main_descriptor + .unsigned_tx_max_vbytes(&rbf_psbt.unsigned_tx); // Make sure it satisfies RBF rule 4. if rbf_psbt.fee().expect("has already been sanity checked") From 927c252d2e1f4755241a5f83666c034f7f8c43c3 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:43:49 +0000 Subject: [PATCH 4/4] spend: use helper for unsigned tx max size --- src/spend.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/spend.rs b/src/spend.rs index 35936e0c..9a308521 100644 --- a/src/spend.rs +++ b/src/spend.rs @@ -11,7 +11,6 @@ use miniscript::bitcoin::{ self, absolute::{Height, LockTime}, bip32, - constants::WITNESS_SCALE_FACTOR, psbt::{Input as PsbtIn, Output as PsbtOut, Psbt}, secp256k1, }; @@ -134,14 +133,7 @@ fn sanity_check_psbt( } // Check the feerate isn't insane. - // Add weights together before converting to vbytes to avoid rounding up multiple times - // and increasing the result, which could lead to the feerate in sats/vb falling below 1. - let tx_wu = tx.weight().to_wu() + (spent_desc.max_sat_weight() * tx.input.len()) as u64; - let tx_vb = tx_wu - .checked_add(WITNESS_SCALE_FACTOR as u64 - 1) - .unwrap() - .checked_div(WITNESS_SCALE_FACTOR as u64) - .unwrap(); + let tx_vb = spent_desc.unsigned_tx_max_vbytes(tx); let feerate_sats_vb = abs_fee .checked_div(tx_vb) .ok_or(SpendCreationError::InsaneFees(