}
};
- // The nSequence to be by default for inputs unless an explicit sequence is specified.
- let n_sequence = match (params.rbf, requirements.csv) {
- // No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
- (None, None) if lock_time != absolute::LockTime::ZERO => {
- Sequence::ENABLE_LOCKTIME_NO_RBF
- }
- // No RBF, CSV or nLockTime, make the transaction final
- (None, None) => Sequence::MAX,
-
- // No RBF requested, use the value from CSV. Note that this value is by definition
- // non-final, so even if a timelock is enabled this nSequence is fine, hence why we
- // don't bother checking for it here. The same is true for all the other branches below
+ // nSequence value for inputs
+ // When not explicitly specified, defaults to 0xFFFFFFFD,
+ // meaning RBF signaling is enabled
+ let n_sequence = match (params.sequence, requirements.csv) {
+ // Enable RBF by default
+ (None, None) => Sequence::ENABLE_RBF_NO_LOCKTIME,
+ // None requested, use required
(None, Some(csv)) => csv,
-
- // RBF with a specific value but that value is too high
- (Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
- return Err(CreateTxError::RbfSequence)
+ // Requested sequence is incompatible with requirements
+ (Some(sequence), Some(csv)) if !check_nsequence_rbf(sequence, csv) => {
+ return Err(CreateTxError::RbfSequenceCsv { sequence, csv })
}
- // RBF with a specific value requested, but the value is incompatible with CSV
- (Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
- if !check_nsequence_rbf(rbf, csv) =>
- {
- return Err(CreateTxError::RbfSequenceCsv { rbf, csv })
- }
-
- // RBF enabled with the default value with CSV also enabled. CSV takes precedence
- (Some(tx_builder::RbfValue::Default), Some(csv)) => csv,
- // Valid RBF, either default or with a specific value. We ignore the `CSV` value
- // because we've already checked it before
- (Some(rbf), _) => rbf.get_value(),
+ // Use requested nSequence value
+ (Some(sequence), _) => sequence,
};
let (fee_rate, mut fee_amount) = match params.fee_policy.unwrap_or_default() {
/// 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())?;
//! // 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>(())
//! ```
pub(crate) sighash: Option<psbt::PsbtSighashType>,
pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<absolute::LockTime>,
- pub(crate) rbf: Option<RbfValue>,
+ pub(crate) sequence: Option<Sequence>,
pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) only_witness_utxo: bool,
}
}
- /// Enable signaling RBF
+ /// Set an exact nSequence value
///
- /// 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
- ///
- /// 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.
- ///
- /// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
- /// be a valid nSequence to signal RBF.
- pub fn enable_rbf_with_sequence(&mut self, nsequence: Sequence) -> &mut Self {
- self.params.rbf = Some(RbfValue::Value(nsequence));
+ /// 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.
+ pub fn set_exact_sequence(&mut self, n_sequence: Sequence) -> &mut Self {
+ self.params.sequence = Some(n_sequence);
self
}
/// .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>(())
/// ```
}
}
-/// RBF nSequence value
-///
-/// Has a default value of `0xFFFFFFFD`
-#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
-pub(crate) enum RbfValue {
- Default,
- Value(Sequence),
-}
-
-impl RbfValue {
- pub(crate) fn get_value(&self) -> Sequence {
- match self {
- RbfValue::Default => Sequence::ENABLE_RBF_NO_LOCKTIME,
- RbfValue::Value(v) => *v,
- }
- }
-}
-
/// Policy regarding the use of change outputs when creating a transaction
#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum ChangeSpendPolicy {
}
#[test]
-fn test_create_tx_no_rbf_csv() {
+fn test_create_tx_custom_csv() {
+ // desc: wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_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));
+ builder
+ .set_exact_sequence(Sequence(42))
+ .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
-
- assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
+ // we allow setting a sequence higher than required
+ assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(42));
}
#[test]
-fn test_create_tx_with_default_rbf_csv() {
+fn test_create_tx_no_rbf_csv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_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();
+ builder.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
+
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
}
#[test]
-fn test_create_tx_with_custom_rbf_csv() {
+fn test_create_tx_incompatible_csv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_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_with_sequence(Sequence(3));
+ .set_exact_sequence(Sequence(3));
assert!(matches!(builder.finish(),
- Err(CreateTxError::RbfSequenceCsv { rbf, csv })
- if rbf.to_consensus_u32() == 3 && csv.to_consensus_u32() == 6));
+ Err(CreateTxError::RbfSequenceCsv { sequence, csv })
+ if sequence.to_consensus_u32() == 3 && csv.to_consensus_u32() == 6));
}
#[test]
-fn test_create_tx_no_rbf_cltv() {
- let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+fn test_create_tx_with_default_rbf_csv() {
+ let (mut wallet, _) = get_funded_wallet(get_test_single_sig_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));
let psbt = builder.finish().unwrap();
-
- assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+ // 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
+ assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
}
#[test]
-fn test_create_tx_invalid_rbf_sequence() {
- let (mut wallet, _) = get_funded_wallet_wpkh();
+fn test_create_tx_no_rbf_cltv() {
+ let (mut wallet, _) = get_funded_wallet(get_test_single_sig_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))
- .enable_rbf_with_sequence(Sequence(0xFFFFFFFE));
- assert!(matches!(builder.finish(), Err(CreateTxError::RbfSequence)));
+ builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+ builder.set_exact_sequence(Sequence(0xFFFFFFFE));
+ let psbt = builder.finish().unwrap();
+
+ assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
}
#[test]
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
- .enable_rbf_with_sequence(Sequence(0xDEADBEEF));
+ .set_exact_sequence(Sequence(0xDEADBEEF));
let psbt = builder.finish().unwrap();
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF));
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 {
.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]
.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]
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.set_exact_sequence(Sequence(0xFFFFFFFE));
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx().expect("failed to extract tx");
let (mut wallet, _) = get_funded_wallet_wpkh();
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();
+ builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+
let psbt = builder.finish().unwrap();
let feerate = psbt.fee_rate().unwrap();
let (mut wallet, _) = get_funded_wallet_wpkh();
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();
+ builder.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");
let (mut wallet, _) = get_funded_wallet_wpkh();
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();
+ builder.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");
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
- builder
- .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
- .enable_rbf();
+ builder.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"));
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"));
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"));
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
- builder
- .drain_to(addr.script_pubkey())
- .drain_wallet()
- .enable_rbf();
+ builder.drain_to(addr.script_pubkey()).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);
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
- builder
- .drain_to(addr.script_pubkey())
- .drain_wallet()
- .enable_rbf();
+ builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
let original_fee = check_fee!(wallet, psbt);
let tx = psbt.extract_tx().expect("failed to extract tx");
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);
.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);
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
- builder
- .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
- .enable_rbf();
+ builder.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);
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
- builder
- .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
- .enable_rbf();
+ builder.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);
.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"));
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
- builder
- .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
- .enable_rbf();
+ builder.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"));
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
- builder
- .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
- .enable_rbf();
+ builder.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);
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
- builder
- .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
- .enable_rbf();
+ builder.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);
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
- builder
- .drain_wallet()
- .drain_to(addr.script_pubkey())
- .enable_rbf();
+ builder.drain_wallet().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!
// in the drain tx.
receive_output(&mut wallet, 25_000, ConfirmationTime::unconfirmed(0));
let mut builder = wallet.build_tx();
- builder
- .drain_wallet()
- .drain_to(addr.script_pubkey())
- .enable_rbf();
+ builder.drain_wallet().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();
.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);