Skip to content

Commit 9ee74f9

Browse files
committed
fix(wallet): allow fee bump without opt-in RBF signaling
1 parent fb7681a commit 9ee74f9

3 files changed

Lines changed: 30 additions & 21 deletions

File tree

src/wallet/error.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,6 @@ pub enum BuildFeeBumpError {
328328
TransactionNotFound(Txid),
329329
/// Happens when trying to bump a transaction that is already confirmed
330330
TransactionConfirmed(Txid),
331-
/// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
332-
IrreplaceableTransaction(Txid),
333331
/// Node doesn't have data to estimate a fee rate
334332
FeeRateUnavailable,
335333
/// Input references an invalid output index in the previous transaction
@@ -353,9 +351,6 @@ impl fmt::Display for BuildFeeBumpError {
353351
Self::TransactionConfirmed(txid) => {
354352
write!(f, "Transaction already confirmed with txid: {txid}")
355353
}
356-
Self::IrreplaceableTransaction(txid) => {
357-
write!(f, "Transaction can't be replaced with txid: {txid}")
358-
}
359354
Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
360355
Self::InvalidOutputIndex(op) => {
361356
write!(f, "A txin referenced an invalid output: {op}")

src/wallet/mod.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,9 +1551,13 @@ impl Wallet {
15511551

15521552
/// Bump the fee of a transaction previously created with this wallet.
15531553
///
1554-
/// Returns an error if the transaction is already confirmed or doesn't explicitly signal
1555-
/// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
1556-
/// pre-populated with the inputs and outputs of the original transaction.
1554+
/// Returns an error if the transaction is already confirmed. If the transaction can be fee
1555+
/// bumped then it returns a [`TxBuilder`] pre-populated with the inputs and outputs of the
1556+
/// original transaction.
1557+
///
1558+
/// Replacing an unconfirmed transaction does not require the original to signal opt-in RBF
1559+
/// (`nSequence` ≤ `0xFFFFFFFD`); mempools may still accept a replacement depending on local
1560+
/// policy (for example default full-RBF in recent Bitcoin Core).
15571561
///
15581562
/// ## Example
15591563
///
@@ -1616,16 +1620,6 @@ impl Wallet {
16161620
return Err(BuildFeeBumpError::TransactionConfirmed(txid));
16171621
}
16181622

1619-
if !tx
1620-
.input
1621-
.iter()
1622-
.any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
1623-
{
1624-
return Err(BuildFeeBumpError::IrreplaceableTransaction(
1625-
tx.compute_txid(),
1626-
));
1627-
}
1628-
16291623
let fee = self
16301624
.calculate_fee(&tx)
16311625
.map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;

tests/build_fee_bump.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,39 @@ mod common;
1616
use common::*;
1717

1818
#[test]
19-
#[should_panic(expected = "IrreplaceableTransaction")]
20-
fn test_bump_fee_irreplaceable_tx() {
19+
fn test_bump_fee_tx_without_rbf_signaling() {
2120
let (mut wallet, _) = get_funded_wallet_wpkh();
2221
let addr = wallet.next_unused_address(KeychainKind::External);
2322
let mut builder = wallet.build_tx();
2423
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
2524
builder.set_exact_sequence(Sequence(0xFFFFFFFE));
2625
let psbt = builder.finish().unwrap();
26+
let original_fee = check_fee!(wallet, psbt);
2727

2828
let tx = psbt.extract_tx().expect("failed to extract tx");
2929
let txid = tx.compute_txid();
3030
insert_tx(&mut wallet, tx);
31-
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
31+
32+
let feerate = FeeRate::from_sat_per_kwu(625);
33+
let mut builder = wallet
34+
.build_fee_bump(txid)
35+
.expect("fee bump without opt-in RBF");
36+
builder.fee_rate(feerate);
37+
let psbt = builder.finish().expect("finish fee bump");
38+
let fee = check_fee!(wallet, psbt);
39+
assert!(
40+
fee > original_fee,
41+
"fee bump must increase absolute fee: {fee} > {original_fee}"
42+
);
43+
44+
let new_tx = psbt.clone().extract_tx().expect("failed to extract tx");
45+
assert_ne!(
46+
new_tx.compute_txid(),
47+
txid,
48+
"replacement transaction must differ from the original"
49+
);
50+
51+
assert_fee_rate!(psbt, fee, feerate, @add_signature);
3252
}
3353

3454
#[test]

0 commit comments

Comments
 (0)