use crate::types::{Transaction, TxInput};
use crate::error::{TxError, Result};
pub mod sequence {
pub const FINAL: u32 = 0xFFFFFFFF;
pub const RBF_ENABLED: u32 = 0xFFFFFFFD;
pub const DEFAULT_RBF: u32 = 0xFFFFFFFE - 1;
}
pub fn is_rbf_enabled(tx: &Transaction) -> bool {
tx.inputs.iter().any(|input| input.sequence < sequence::FINAL - 1)
}
pub fn enable_rbf(tx: &mut Transaction) {
for input in &mut tx.inputs {
if input.sequence == sequence::FINAL {
input.sequence = sequence::RBF_ENABLED;
}
}
}
pub fn disable_rbf(tx: &mut Transaction) {
for input in &mut tx.inputs {
input.sequence = sequence::FINAL;
}
}
pub fn create_replacement(
original: &Transaction,
new_fee_rate: u64,
change_output_index: usize,
) -> Result<Transaction> {
if !is_rbf_enabled(original) {
return Err(TxError::RbfNotEnabled);
}
if change_output_index >= original.outputs.len() {
return Err(TxError::InvalidOutputIndex(change_output_index));
}
let mut replacement = original.clone();
let current_vsize = original.vsize();
let current_fee = calculate_current_fee(original);
let new_fee = (current_vsize as u64) * new_fee_rate;
if new_fee <= current_fee {
return Err(TxError::RbfFeeTooLow {
current: current_fee,
required: current_fee + 1,
});
}
let fee_increase = new_fee - current_fee;
if replacement.outputs[change_output_index].value < fee_increase {
return Err(TxError::InsufficientFunds {
needed: fee_increase,
available: replacement.outputs[change_output_index].value,
});
}
replacement.outputs[change_output_index].value -= fee_increase;
for input in &mut replacement.inputs {
if input.sequence > 0 {
input.sequence -= 1;
}
}
for input in &mut replacement.inputs {
input.script_sig.clear();
input.witness.clear();
}
Ok(replacement)
}
pub fn bump_fee(
tx: &mut Transaction,
fee_increase: u64,
change_output_index: usize,
) -> Result<()> {
if !is_rbf_enabled(tx) {
return Err(TxError::RbfNotEnabled);
}
if change_output_index >= tx.outputs.len() {
return Err(TxError::InvalidOutputIndex(change_output_index));
}
if tx.outputs[change_output_index].value < fee_increase {
return Err(TxError::InsufficientFunds {
needed: fee_increase,
available: tx.outputs[change_output_index].value,
});
}
tx.outputs[change_output_index].value -= fee_increase;
for input in &mut tx.inputs {
if input.sequence > 0 {
input.sequence -= 1;
}
}
for input in &mut tx.inputs {
input.script_sig.clear();
input.witness.clear();
}
Ok(())
}
fn calculate_current_fee(tx: &Transaction) -> u64 {
let vsize = tx.vsize() as u64;
vsize * 10 }
pub struct RbfBuilder {
sequence: u32,
}
impl Default for RbfBuilder {
fn default() -> Self {
Self::new()
}
}
impl RbfBuilder {
pub fn new() -> Self {
Self {
sequence: sequence::RBF_ENABLED,
}
}
pub fn with_sequence(mut self, seq: u32) -> Self {
self.sequence = seq;
self
}
pub fn create_input(&self, txid: [u8; 32], vout: u32) -> TxInput {
let mut input = TxInput::new(txid, vout);
input.sequence = self.sequence;
input
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::TxOutput;
fn create_test_tx() -> Transaction {
let mut tx = Transaction::new();
let mut input = TxInput::new([0u8; 32], 0);
input.sequence = sequence::RBF_ENABLED;
tx.inputs.push(input);
tx.outputs.push(TxOutput::new(50000, vec![0x00, 0x14]));
tx.outputs.push(TxOutput::new(40000, vec![0x00, 0x14])); tx
}
#[test]
fn test_is_rbf_enabled() {
let mut tx = create_test_tx();
assert!(is_rbf_enabled(&tx));
tx.inputs[0].sequence = sequence::FINAL;
assert!(!is_rbf_enabled(&tx));
}
#[test]
fn test_enable_disable_rbf() {
let mut tx = Transaction::new();
tx.inputs.push(TxInput::new([0u8; 32], 0));
assert!(!is_rbf_enabled(&tx));
enable_rbf(&mut tx);
assert!(is_rbf_enabled(&tx));
disable_rbf(&mut tx);
assert!(!is_rbf_enabled(&tx));
}
#[test]
fn test_bump_fee() {
let mut tx = create_test_tx();
let original_change = tx.outputs[1].value;
bump_fee(&mut tx, 1000, 1).unwrap();
assert_eq!(tx.outputs[1].value, original_change - 1000);
}
#[test]
fn test_rbf_builder() {
let builder = RbfBuilder::new();
let input = builder.create_input([1u8; 32], 0);
assert_eq!(input.sequence, sequence::RBF_ENABLED);
}
}