Skip to content

Commit

Permalink
feat(wallet): enable RBF by default on TxBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
luisschwab committed Sep 17, 2024
1 parent 88423f3 commit 1fc4bd7
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 49 deletions.
1 change: 0 additions & 1 deletion crates/wallet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ println!("Your new receive address is: {}", receive_address.address);
<!-- let mut builder = wallet.build_tx(); -->
<!-- builder -->
<!-- .add_recipient(send_to.script_pubkey(), 50_000) -->
<!-- .enable_rbf() -->
<!-- .do_not_spend_change() -->
<!-- .fee_rate(FeeRate::from_sat_per_vb(5.0)); -->
<!-- builder.finish()? -->
Expand Down
3 changes: 1 addition & 2 deletions crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1611,8 +1611,7 @@ impl Wallet {
/// let mut psbt = {
/// let mut builder = wallet.build_tx();
/// builder
/// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
/// .enable_rbf();
/// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
/// builder.finish()?
/// };
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
Expand Down
47 changes: 39 additions & 8 deletions crates/wallet/src/wallet/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@
//! // With a custom fee rate of 5.0 satoshi/vbyte
//! .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
//! // Only spend non-change outputs
//! .do_not_spend_change()
//! // Turn on RBF signaling
//! .enable_rbf();
//! .do_not_spend_change();
//! let psbt = tx_builder.finish()?;
//! # Ok::<(), anyhow::Error>(())
//! ```
Expand Down Expand Up @@ -120,7 +118,7 @@ pub struct TxBuilder<'a, Cs> {

/// The parameters for transaction creation sans coin selection algorithm.
//TODO: TxParams should eventually be exposed publicly.
#[derive(Default, Debug, Clone)]
#[derive(Debug, Clone)]
pub(crate) struct TxParams {
pub(crate) recipients: Vec<(ScriptBuf, Amount)>,
pub(crate) drain_wallet: bool,
Expand All @@ -145,6 +143,34 @@ pub(crate) struct TxParams {
pub(crate) allow_dust: bool,
}

impl Default for TxParams {
fn default() -> Self {
Self {
recipients: Vec::new(),
drain_wallet: false,
drain_to: None,
fee_policy: None,
internal_policy_path: None,
external_policy_path: None,
utxos: Vec::new(),
unspendable: HashSet::new(),
manually_selected_only: false,
sighash: None,
ordering: TxOrdering::default(),
locktime: None,
rbf: Some(RbfValue::Default),
version: None,
change_policy: ChangeSpendPolicy::default(),
only_witness_utxo: false,
add_global_xpubs: false,
include_output_redeem_witness_script: false,
bumping_fee: None,
current_height: None,
allow_dust: false,
}
}
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct PreviousFee {
pub absolute: Amount,
Expand Down Expand Up @@ -554,15 +580,15 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
}
}

/// Enable signaling RBF
/// Enable RBF signaling
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(&mut self) -> &mut Self {
self.params.rbf = Some(RbfValue::Default);
self
}

/// Enable signaling RBF with a specific nSequence value
/// Enable RBF signaling with a specific nSequence value
///
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
/// and the given `nsequence` is lower than the CSV value.
Expand All @@ -574,6 +600,12 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
self
}

/// Disable RBF signaling
pub fn disable_rbf(&mut self) -> &mut Self {
self.params.rbf = None;
self
}

/// Set the current blockchain height.
///
/// This will be used to:
Expand Down Expand Up @@ -654,8 +686,7 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
/// .drain_wallet()
/// // Send the excess (which is all the coins minus the fee) to this address.
/// .drain_to(to_address.script_pubkey())
/// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
/// .enable_rbf();
/// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"));
/// let psbt = tx_builder.finish()?;
/// # Ok::<(), anyhow::Error>(())
/// ```
Expand Down
62 changes: 24 additions & 38 deletions crates/wallet/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,7 @@ fn test_create_tx_with_default_rbf_csv() {
let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
// When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
// It will be set to the OP_CSV value, in this case 6
Expand All @@ -682,6 +681,8 @@ fn test_create_tx_no_rbf_cltv() {
let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
builder
.disable_rbf();
let psbt = builder.finish().unwrap();

assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
Expand Down Expand Up @@ -743,7 +744,7 @@ fn test_create_tx_default_sequence() {
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();

assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD));
}

macro_rules! check_fee {
Expand Down Expand Up @@ -1328,7 +1329,7 @@ fn test_create_tx_policy_path_no_csv() {
.policy_path(path, KeychainKind::External);
let psbt = builder.finish().unwrap();

assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF));
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD));
}

#[test]
Expand Down Expand Up @@ -1370,7 +1371,7 @@ fn test_create_tx_policy_path_ignored_subtree_with_csv() {
.policy_path(path, KeychainKind::External);
let psbt = builder.finish().unwrap();

assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD));
}

#[test]
Expand Down Expand Up @@ -1797,6 +1798,8 @@ fn test_bump_fee_irreplaceable_tx() {
let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
builder
.disable_rbf();
let psbt = builder.finish().unwrap();

let tx = psbt.extract_tx().expect("failed to extract tx");
Expand Down Expand Up @@ -1870,8 +1873,7 @@ fn test_bump_fee_low_abs() {
let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();

let tx = psbt.extract_tx().expect("failed to extract tx");
Expand All @@ -1892,8 +1894,7 @@ fn test_bump_fee_zero_abs() {
let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();

let tx = psbt.extract_tx().expect("failed to extract tx");
Expand All @@ -1914,8 +1915,7 @@ fn test_bump_fee_reduce_change() {
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
let original_sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
Expand All @@ -1928,7 +1928,7 @@ fn test_bump_fee_reduce_change() {

let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(feerate).enable_rbf();
builder.fee_rate(feerate);
let psbt = builder.finish().unwrap();
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
Expand Down Expand Up @@ -1964,7 +1964,6 @@ fn test_bump_fee_reduce_change() {

let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_absolute(Amount::from_sat(200));
builder.enable_rbf();
let psbt = builder.finish().unwrap();
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
Expand Down Expand Up @@ -2013,8 +2012,7 @@ fn test_bump_fee_reduce_single_recipient() {
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
.drain_wallet()
.enable_rbf();
.drain_wallet();
let psbt = builder.finish().unwrap();
let tx = psbt.clone().extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2060,8 +2058,7 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
.drain_wallet()
.enable_rbf();
.drain_wallet();
let psbt = builder.finish().unwrap();
let original_fee = check_fee!(wallet, psbt);
let tx = psbt.extract_tx().expect("failed to extract tx");
Expand Down Expand Up @@ -2135,8 +2132,7 @@ fn test_bump_fee_drain_wallet() {
vout: 0,
})
.unwrap()
.manually_selected_only()
.enable_rbf();
.manually_selected_only();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2201,8 +2197,7 @@ fn test_bump_fee_remove_output_manually_selected_only() {
.drain_to(addr.script_pubkey())
.add_utxo(outpoint)
.unwrap()
.manually_selected_only()
.enable_rbf();
.manually_selected_only();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2248,8 +2243,7 @@ fn test_bump_fee_add_input() {
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_details = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2304,8 +2298,7 @@ fn test_bump_fee_absolute_add_input() {
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2366,8 +2359,7 @@ fn test_bump_fee_no_change_add_input_and_change() {
.drain_to(addr.script_pubkey())
.add_utxo(op)
.unwrap()
.manually_selected_only()
.enable_rbf();
.manually_selected_only();
let psbt = builder.finish().unwrap();
let original_sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
Expand Down Expand Up @@ -2429,8 +2421,7 @@ fn test_bump_fee_add_input_change_dust() {
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
let psbt = builder.finish().unwrap();
let original_sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
Expand Down Expand Up @@ -2505,8 +2496,7 @@ fn test_bump_fee_force_add_input() {
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
let psbt = builder.finish().unwrap();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2570,8 +2560,7 @@ fn test_bump_fee_absolute_force_add_input() {
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
.enable_rbf();
.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
let psbt = builder.finish().unwrap();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
Expand Down Expand Up @@ -2643,8 +2632,7 @@ fn test_bump_fee_unconfirmed_inputs_only() {
let mut builder = wallet.build_tx();
builder
.drain_wallet()
.drain_to(addr.script_pubkey())
.enable_rbf();
.drain_to(addr.script_pubkey());
let psbt = builder.finish().unwrap();
// Now we receive one transaction with 0 confirmations. We won't be able to use that for
// fee bumping, as it's still unconfirmed!
Expand Down Expand Up @@ -2682,8 +2670,7 @@ fn test_bump_fee_unconfirmed_input() {
let mut builder = wallet.build_tx();
builder
.drain_wallet()
.drain_to(addr.script_pubkey())
.enable_rbf();
.drain_to(addr.script_pubkey());
let psbt = builder.finish().unwrap();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.compute_txid();
Expand Down Expand Up @@ -2727,7 +2714,6 @@ fn test_fee_amount_negative_drain_val() {
.add_recipient(send_to.script_pubkey(), Amount::from_sat(8630))
.add_utxo(incoming_op)
.unwrap()
.enable_rbf()
.fee_rate(fee_rate);
let psbt = builder.finish().unwrap();
let fee = check_fee!(wallet, psbt);
Expand Down

0 comments on commit 1fc4bd7

Please sign in to comment.