Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/wallet/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,6 @@ pub enum BuildFeeBumpError {
TransactionNotFound(Txid),
/// Happens when trying to bump a transaction that is already confirmed
TransactionConfirmed(Txid),
/// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
IrreplaceableTransaction(Txid),
/// Node doesn't have data to estimate a fee rate
FeeRateUnavailable,
/// Input references an invalid output index in the previous transaction
Expand All @@ -353,9 +351,6 @@ impl fmt::Display for BuildFeeBumpError {
Self::TransactionConfirmed(txid) => {
write!(f, "Transaction already confirmed with txid: {txid}")
}
Self::IrreplaceableTransaction(txid) => {
write!(f, "Transaction can't be replaced with txid: {txid}")
}
Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
Self::InvalidOutputIndex(op) => {
write!(f, "A txin referenced an invalid output: {op}")
Expand Down
21 changes: 8 additions & 13 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1551,9 +1551,14 @@ impl Wallet {

/// Bump the fee of a transaction previously created with this wallet.
///
/// Returns an error if the transaction is already confirmed or doesn't explicitly signal
/// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
/// pre-populated with the inputs and outputs of the original transaction.
/// Returns an error if the transaction is already confirmed. If the transaction can be fee
/// bumped then it returns a [`TxBuilder`] pre-populated with the inputs and outputs of the
/// original transaction.
///
/// Replacing an unconfirmed transaction does not require the original to signal opt-in RBF
/// (`nSequence` ≤ `0xFFFFFFFD`). Relay and mining acceptance depend on local policy; Bitcoin
/// Core 28+ defaults to full-RBF mempool relay, while other implementations or configs may
/// differ.
///
/// ## Example
///
Expand Down Expand Up @@ -1616,16 +1621,6 @@ impl Wallet {
return Err(BuildFeeBumpError::TransactionConfirmed(txid));
}

if !tx
.input
.iter()
.any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
{
return Err(BuildFeeBumpError::IrreplaceableTransaction(
tx.compute_txid(),
));
}

let fee = self
.calculate_fee(&tx)
.map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
Expand Down
26 changes: 23 additions & 3 deletions tests/build_fee_bump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,39 @@ mod common;
use common::*;

#[test]
#[should_panic(expected = "IrreplaceableTransaction")]
fn test_bump_fee_irreplaceable_tx() {
fn test_bump_fee_tx_without_rbf_signaling() {
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));
builder.set_exact_sequence(Sequence(0xFFFFFFFE));
let psbt = builder.finish().unwrap();
let original_fee = check_fee!(wallet, psbt);

let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.compute_txid();
insert_tx(&mut wallet, tx);
wallet.build_fee_bump(txid).unwrap().finish().unwrap();

let feerate = FeeRate::from_sat_per_kwu(625);
let mut builder = wallet
.build_fee_bump(txid)
.expect("fee bump without opt-in RBF");
builder.fee_rate(feerate);
let psbt = builder.finish().expect("finish fee bump");
let fee = check_fee!(wallet, psbt);
assert!(
fee > original_fee,
"fee bump must increase absolute fee: {fee} > {original_fee}"
);

let new_tx = psbt.clone().extract_tx().expect("failed to extract tx");
assert_ne!(
new_tx.compute_txid(),
txid,
"replacement transaction must differ from the original"
);

assert_fee_rate!(psbt, fee, feerate, @add_signature);
}

#[test]
Expand Down