Skip to content

Commit b33a011

Browse files
committed
Merge #1616: feat(wallet)!: enable RBF by default on TxBuilder
f15551b feat(wallet)!: enable RBF by default on TxBuilder (Luis Schwab) Pull request description: ### Description Closes #791 This PR enables RBF by default on `TxBuilder` ### Notes to the reviewers <!-- In this section you can include notes directed to the reviewers, like explaining why some parts of the PR were done in a specific way --> ### Changelog notice - On TxParams, `rbf` becomes `sequence` - Added `set_exact_sequence()`, so the user can define an arbitrary sequence value - `n_sequence` now defaults to `0xFFFFFFFD` - Adjusted tests accordingly ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [ ] I've added docs for the new feature #### Bugfixes: * [x] This pull request breaks the existing API * [ ] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: notmandatory: ACK f15551b Tree-SHA512: 784538ffd102f315a7a96bd7f9afcdca7d35252ee2b4489d9853797c7f6d8cf95537f0dea3855ea9fffc9bf148c81a8c090cfdfd1e94144b9343651129ab9504
2 parents 4942cc6 + f15551b commit b33a011

9 files changed

Lines changed: 81 additions & 173 deletions

File tree

crates/wallet/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ println!("Your new receive address is: {}", receive_address.address);
174174
<!-- let mut builder = wallet.build_tx(); -->
175175
<!-- builder -->
176176
<!-- .add_recipient(send_to.script_pubkey(), 50_000) -->
177-
<!-- .enable_rbf() -->
178177
<!-- .do_not_spend_change() -->
179178
<!-- .fee_rate(FeeRate::from_sat_per_vb(5.0)); -->
180179
<!-- builder.finish()? -->

crates/wallet/src/wallet/error.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,10 @@ pub enum CreateTxError {
6565
/// Required `LockTime`
6666
required: absolute::LockTime,
6767
},
68-
/// Cannot enable RBF with a `Sequence` >= 0xFFFFFFFE
69-
RbfSequence,
7068
/// Cannot enable RBF with `Sequence` given a required OP_CSV
7169
RbfSequenceCsv {
7270
/// Given RBF `Sequence`
73-
rbf: Sequence,
71+
sequence: Sequence,
7472
/// Required OP_CSV `Sequence`
7573
csv: Sequence,
7674
},
@@ -131,14 +129,11 @@ impl fmt::Display for CreateTxError {
131129
} => {
132130
write!(f, "TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", required, requested)
133131
}
134-
CreateTxError::RbfSequence => {
135-
write!(f, "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")
136-
}
137-
CreateTxError::RbfSequenceCsv { rbf, csv } => {
132+
CreateTxError::RbfSequenceCsv { sequence, csv } => {
138133
write!(
139134
f,
140135
"Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
141-
rbf, csv
136+
sequence, csv
142137
)
143138
}
144139
CreateTxError::FeeTooLow { required } => {

crates/wallet/src/wallet/mod.rs

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,36 +1366,20 @@ impl Wallet {
13661366
}
13671367
};
13681368

1369-
// The nSequence to be by default for inputs unless an explicit sequence is specified.
1370-
let n_sequence = match (params.rbf, requirements.csv) {
1371-
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
1372-
(None, None) if lock_time != absolute::LockTime::ZERO => {
1373-
Sequence::ENABLE_LOCKTIME_NO_RBF
1374-
}
1375-
// No RBF, CSV or nLockTime, make the transaction final
1376-
(None, None) => Sequence::MAX,
1377-
1378-
// No RBF requested, use the value from CSV. Note that this value is by definition
1379-
// non-final, so even if a timelock is enabled this nSequence is fine, hence why we
1380-
// don't bother checking for it here. The same is true for all the other branches below
1369+
// nSequence value for inputs
1370+
// When not explicitly specified, defaults to 0xFFFFFFFD,
1371+
// meaning RBF signaling is enabled
1372+
let n_sequence = match (params.sequence, requirements.csv) {
1373+
// Enable RBF by default
1374+
(None, None) => Sequence::ENABLE_RBF_NO_LOCKTIME,
1375+
// None requested, use required
13811376
(None, Some(csv)) => csv,
1382-
1383-
// RBF with a specific value but that value is too high
1384-
(Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
1385-
return Err(CreateTxError::RbfSequence)
1377+
// Requested sequence is incompatible with requirements
1378+
(Some(sequence), Some(csv)) if !check_nsequence_rbf(sequence, csv) => {
1379+
return Err(CreateTxError::RbfSequenceCsv { sequence, csv })
13861380
}
1387-
// RBF with a specific value requested, but the value is incompatible with CSV
1388-
(Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
1389-
if !check_nsequence_rbf(rbf, csv) =>
1390-
{
1391-
return Err(CreateTxError::RbfSequenceCsv { rbf, csv })
1392-
}
1393-
1394-
// RBF enabled with the default value with CSV also enabled. CSV takes precedence
1395-
(Some(tx_builder::RbfValue::Default), Some(csv)) => csv,
1396-
// Valid RBF, either default or with a specific value. We ignore the `CSV` value
1397-
// because we've already checked it before
1398-
(Some(rbf), _) => rbf.get_value(),
1381+
// Use requested nSequence value
1382+
(Some(sequence), _) => sequence,
13991383
};
14001384

14011385
let (fee_rate, mut fee_amount) = match params.fee_policy.unwrap_or_default() {
@@ -1609,8 +1593,7 @@ impl Wallet {
16091593
/// let mut psbt = {
16101594
/// let mut builder = wallet.build_tx();
16111595
/// builder
1612-
/// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
1613-
/// .enable_rbf();
1596+
/// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
16141597
/// builder.finish()?
16151598
/// };
16161599
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;

crates/wallet/src/wallet/tx_builder.rs

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
//! // With a custom fee rate of 5.0 satoshi/vbyte
3232
//! .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
3333
//! // Only spend non-change outputs
34-
//! .do_not_spend_change()
35-
//! // Turn on RBF signaling
36-
//! .enable_rbf();
34+
//! .do_not_spend_change();
3735
//! let psbt = tx_builder.finish()?;
3836
//! # Ok::<(), anyhow::Error>(())
3937
//! ```
@@ -134,7 +132,7 @@ pub(crate) struct TxParams {
134132
pub(crate) sighash: Option<psbt::PsbtSighashType>,
135133
pub(crate) ordering: TxOrdering,
136134
pub(crate) locktime: Option<absolute::LockTime>,
137-
pub(crate) rbf: Option<RbfValue>,
135+
pub(crate) sequence: Option<Sequence>,
138136
pub(crate) version: Option<Version>,
139137
pub(crate) change_policy: ChangeSpendPolicy,
140138
pub(crate) only_witness_utxo: bool,
@@ -554,23 +552,12 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
554552
}
555553
}
556554

557-
/// Enable signaling RBF
555+
/// Set an exact nSequence value
558556
///
559-
/// This will use the default nSequence value of `0xFFFFFFFD`.
560-
pub fn enable_rbf(&mut self) -> &mut Self {
561-
self.params.rbf = Some(RbfValue::Default);
562-
self
563-
}
564-
565-
/// Enable signaling RBF with a specific nSequence value
566-
///
567-
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
568-
/// and the given `nsequence` is lower than the CSV value.
569-
///
570-
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
571-
/// be a valid nSequence to signal RBF.
572-
pub fn enable_rbf_with_sequence(&mut self, nsequence: Sequence) -> &mut Self {
573-
self.params.rbf = Some(RbfValue::Value(nsequence));
557+
/// This can cause conflicts if the wallet's descriptors contain an
558+
/// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value.
559+
pub fn set_exact_sequence(&mut self, n_sequence: Sequence) -> &mut Self {
560+
self.params.sequence = Some(n_sequence);
574561
self
575562
}
576563

@@ -654,8 +641,7 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
654641
/// .drain_wallet()
655642
/// // Send the excess (which is all the coins minus the fee) to this address.
656643
/// .drain_to(to_address.script_pubkey())
657-
/// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
658-
/// .enable_rbf();
644+
/// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"));
659645
/// let psbt = tx_builder.finish()?;
660646
/// # Ok::<(), anyhow::Error>(())
661647
/// ```
@@ -835,24 +821,6 @@ impl Default for Version {
835821
}
836822
}
837823

838-
/// RBF nSequence value
839-
///
840-
/// Has a default value of `0xFFFFFFFD`
841-
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
842-
pub(crate) enum RbfValue {
843-
Default,
844-
Value(Sequence),
845-
}
846-
847-
impl RbfValue {
848-
pub(crate) fn get_value(&self) -> Sequence {
849-
match self {
850-
RbfValue::Default => Sequence::ENABLE_RBF_NO_LOCKTIME,
851-
RbfValue::Value(v) => *v,
852-
}
853-
}
854-
}
855-
856824
/// Policy regarding the use of change outputs when creating a transaction
857825
#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
858826
pub enum ChangeSpendPolicy {

crates/wallet/src/wallet/utils.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,20 @@ impl After {
5252
}
5353
}
5454

55-
pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
56-
// The RBF value must enable relative timelocks
57-
if !rbf.is_relative_lock_time() {
55+
pub(crate) fn check_nsequence_rbf(sequence: Sequence, csv: Sequence) -> bool {
56+
// The nSequence value must enable relative timelocks
57+
if !sequence.is_relative_lock_time() {
5858
return false;
5959
}
6060

6161
// Both values should be represented in the same unit (either time-based or
6262
// block-height based)
63-
if rbf.is_time_locked() != csv.is_time_locked() {
63+
if sequence.is_time_locked() != csv.is_time_locked() {
6464
return false;
6565
}
6666

6767
// The value should be at least `csv`
68-
if rbf < csv {
68+
if sequence < csv {
6969
return false;
7070
}
7171

0 commit comments

Comments
 (0)