diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 23c7b2bfb..1d794a2bb 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -15,7 +15,7 @@ pub use crate::database::{CoinStatus, LabelItem}; use bdk_coin_select::InsufficientFunds; use utils::{ deser_addr_assume_checked, deser_amount_from_sats, deser_fromstr, deser_hex, - select_coins_for_spend, ser_amount, ser_hex, ser_to_string, + select_coins_for_spend, ser_amount, ser_hex, ser_to_string, unsigned_tx_max_vbytes, }; use std::{ @@ -1111,6 +1111,12 @@ 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 feerate 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. @@ -1145,25 +1151,8 @@ impl DaemonControl { return Err(e); } }; - rbf_vsize = { - // TODO: Make this a function (can be used also in `sanity_check`). - let witness_factor: u64 = - descriptors::WITNESS_FACTOR.try_into().expect("it must fit"); - let max_sat_weight: u64 = self - .config - .main_descriptor - .max_sat_weight() - .try_into() - .expect("it must fit"); - let num_inputs: u64 = rbf_psbt.psbt.inputs.len().try_into().expect("it must fit"); - let rbf_weight = - rbf_psbt.psbt.unsigned_tx.weight().to_wu() + (max_sat_weight * num_inputs); - rbf_weight - .checked_add(witness_factor - 1) - .unwrap() - .checked_div(witness_factor) - .unwrap() - }; + rbf_vsize = unsigned_tx_max_vbytes(&rbf_psbt.psbt.unsigned_tx, max_sat_weight); + println!("replacement vsize estimated at {}", rbf_vsize); if rbf_psbt .psbt diff --git a/src/commands/utils.rs b/src/commands/utils.rs index ed2301d4c..d5f5e561a 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -5,7 +5,7 @@ use bdk_coin_select::{ use log::warn; use std::{convert::TryInto, str::FromStr}; -use miniscript::bitcoin::{self, consensus, hashes::hex::FromHex}; +use miniscript::bitcoin::{self, consensus, constants::WITNESS_SCALE_FACTOR, hashes::hex::FromHex}; use serde::{de, Deserialize, Deserializer, Serializer}; use crate::database::Coin; @@ -244,3 +244,26 @@ pub fn select_coins_for_spend( change_amount, )) } + +/// 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. +pub 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() +}