clementine_tx_sender/
rbf.rs

1use crate::{log_error_for_tx, TxSender};
2use bitcoin::absolute::{LockTime, LOCK_TIME_THRESHOLD};
3use bitcoin::hashes::Hash;
4use bitcoin::script::Instruction;
5use bitcoin::sighash::{Prevouts, SighashCache};
6use bitcoin::taproot::{self, LeafVersion};
7use bitcoin::{Address, Amount, ScriptBuf, TapLeafHash, Transaction};
8use bitcoin::{Psbt, TxOut, Txid, Witness};
9use bitcoincore_rpc::json::{
10    BumpFeeOptions, BumpFeeResult, CreateRawTransactionInput, WalletCreateFundedPsbtOutput,
11    WalletCreateFundedPsbtOutputs, WalletCreateFundedPsbtResult,
12};
13use bitcoincore_rpc::RpcApi;
14use clementine_config::NON_EPHEMERAL_ANCHOR_AMOUNT;
15use clementine_errors::SendTxError;
16use clementine_primitives::FeeRateKvb;
17use clementine_utils::sign::TapTweakData;
18use clementine_utils::{RbfSigningInfo, RbfSigningSpendPath, TxMetadata};
19use eyre::Context;
20use eyre::{eyre, OptionExt};
21use std::str::FromStr;
22
23use super::Result;
24
25/// Prefix for the reveal transaction ids for wtxid grinding.
26#[cfg(feature = "testing")]
27pub const REVEAL_TX_PREFIX: &[u8] = &[2];
28#[cfg(not(feature = "testing"))]
29pub const REVEAL_TX_PREFIX: &[u8] = &[2, 2];
30
31impl TxSender {
32    /// Calculates the appropriate fee rate for a Replace-By-Fee (RBF) transaction.
33    ///
34    /// This method determines the effective fee rate needed to successfully replace
35    /// an existing transaction in the mempool. It follows Bitcoin's RBF rules by:
36    ///
37    /// 1. Retrieving the original transaction and calculating its current fee rate
38    /// 2. Ensuring the new fee rate is higher than the original by at least the minimum
39    ///    required incremental relay fee
40    /// 3. Comparing the calculated minimum bump fee rate with the requested target fee rate
41    ///    and selecting the higher of the two
42    ///
43    /// # Arguments
44    /// * `txid` - The transaction ID of the original transaction to be replaced
45    /// * `new_feerate` - The target fee rate requested for the replacement transaction
46    ///
47    /// # Returns
48    /// * `Ok(Some(FeeRateKvb))` - The effective fee rate (in satoshis per kvB) to use for the replacement
49    /// * `Ok(None)` - If the original transaction already has a higher fee rate than requested
50    /// * `Err(...)` - If there was an error retrieving or analyzing the original transaction
51    pub async fn calculate_bump_feerate_if_needed(
52        &self,
53        txid: &Txid,
54        new_feerate: FeeRateKvb,
55    ) -> Result<Option<FeeRateKvb>> {
56        let original_tx = self.rpc.get_tx_of_txid(txid).await.map_err(|e| eyre!(e))?;
57
58        // Calculate original tx fee
59        let original_tx_fee = self.get_tx_fee(&original_tx).await.map_err(|e| eyre!(e))?;
60
61        let original_tx_weight = original_tx.weight();
62
63        // Original fee rate calculation according to Bitcoin Core
64        // Use sat/kvB to retain precision when converting to sat/vB.
65        let original_feerate_sat_per_kvb = FeeRateKvb::from_sat_per_kvb(
66            original_tx_fee
67                .to_sat()
68                .saturating_mul(1000)
69                .div_ceil(original_tx_weight.to_vbytes_ceil() as u64),
70        );
71
72        // If original feerate is already higher than target, avoid bumping
73        if original_feerate_sat_per_kvb >= new_feerate {
74            return Ok(None);
75        }
76
77        // Get minimum fee increment rate from node for BIP125 compliance. Returned value is in BTC/kvB
78        let incremental_fee_rate = self
79            .rpc
80            .get_network_info()
81            .await
82            .map_err(|e| eyre!(e))?
83            .incremental_fee;
84        let incremental_fee_rate_sat_per_kvb = incremental_fee_rate.to_sat();
85        let incremental_fee_rate = FeeRateKvb::from_sat_per_kvb(incremental_fee_rate_sat_per_kvb);
86
87        // Use max of target fee rate and original + minimum fee increment rate.
88        let min_bump_feerate =
89            original_feerate_sat_per_kvb.to_sat_per_kvb() + incremental_fee_rate.to_sat_per_kvb();
90
91        let effective_feerate_sat_per_kvb =
92            std::cmp::max(new_feerate.to_sat_per_kvb(), min_bump_feerate);
93
94        Ok(Some(FeeRateKvb::from_sat_per_kvb(
95            effective_feerate_sat_per_kvb,
96        )))
97    }
98
99    pub async fn fill_in_utxo_info(&self, psbt: &mut String) -> Result<()> {
100        let mut decoded_psbt = Psbt::from_str(psbt).map_err(|e| eyre!(e))?;
101        let tx = decoded_psbt.unsigned_tx.clone();
102
103        for (idx, input) in tx.input.iter().enumerate() {
104            let utxo = self
105                .rpc
106                .get_tx_out(
107                    &input.previous_output.txid,
108                    input.previous_output.vout,
109                    Some(false),
110                )
111                .await
112                .wrap_err("Failed to get UTXO info")?;
113
114            if let Some(utxo) = utxo {
115                decoded_psbt.inputs[idx].witness_utxo = Some(TxOut {
116                    value: utxo.value,
117                    script_pubkey: utxo
118                        .script_pub_key
119                        .script()
120                        .wrap_err("Failed to get script pubkey")?,
121                });
122            }
123        }
124
125        *psbt = decoded_psbt.to_string();
126
127        Ok(())
128    }
129
130    /// Given a PSBT with inputs, fill in the existing witnesses from the original tx
131    /// This allows us to create a finalized PSBT if
132    /// the original tx had SinglePlusAnyoneCanPay signatures.  If the original
133    /// tx did not have S+AP, these signatures will be added. The expected behavior is for them to be replaced using RbfSigningInfo.
134    ///
135    /// # Returns
136    /// The PSBT as a base64-encoded string.
137    pub async fn copy_witnesses(&self, psbt: String, initial_tx: &Transaction) -> Result<String> {
138        let mut decoded_psbt = Psbt::from_str(&psbt).map_err(|e| eyre!(e))?;
139
140        for (idx, input) in initial_tx.input.iter().enumerate() {
141            if let Some(sig) = input.witness.nth(0) {
142                if sig.len() == 65 && sig[64] == 0x83 {
143                    // This is a S+AP signature, copy it over
144                    decoded_psbt.inputs[idx].final_script_witness = Some(input.witness.clone());
145                }
146            }
147        }
148
149        Ok(decoded_psbt.to_string())
150    }
151
152    pub async fn create_funded_psbt(
153        &self,
154        tx: &Transaction,
155        fee_rate: FeeRateKvb,
156    ) -> Result<WalletCreateFundedPsbtResult> {
157        // 1. Create a funded PSBT using the wallet
158        let create_psbt_opts = bitcoincore_rpc::json::WalletCreateFundedPsbtOptions {
159            add_inputs: Some(true), // Let the wallet add its inputs
160            include_unsafe: Some(self.include_unsafe),
161            change_address: None,
162            change_position: Some(tx.output.len() as u16), // Add change output at last index (so that SinglePlusAnyoneCanPay signatures stay valid)
163            change_type: None,
164            include_watching: None,
165            lock_unspent: None,
166            // Bitcoincore expects BTC/kvbyte for fee_rate
167            fee_rate: Some(
168                fee_rate
169                    .fee_vb(1000)
170                    .ok_or_eyre("Failed to convert fee rate to BTC/kvbyte")?,
171            ),
172            subtract_fee_from_outputs: vec![],
173            replaceable: Some(true), // Mark as RBF enabled
174            conf_target: None,
175            estimate_mode: None,
176        };
177
178        let mut omitted = 0usize;
179        let filtered_outputs: Vec<WalletCreateFundedPsbtOutput> = tx
180            .output
181            .iter()
182            .filter_map(|out| {
183                if out.script_pubkey.is_op_return() {
184                    if let Some(Ok(Instruction::PushBytes(data))) =
185                        out.script_pubkey.instructions().last()
186                    {
187                        return Some(WalletCreateFundedPsbtOutput::OpReturn(
188                            data.as_bytes().to_vec(),
189                        ));
190                    }
191                }
192                let address = Address::from_script(
193                    &out.script_pubkey,
194                    self.network,
195                )
196                .map_err(|e| eyre!(e));
197                match address {
198                    Ok(address) => Some(WalletCreateFundedPsbtOutput::Spendable(
199                        address.to_string(),
200                        out.value,
201                    )),
202                    Err(err) => {
203                        tracing::error!(
204                            "Failed to get address from script for output of tx with txid {} for script: {}",
205                            tx.compute_txid(),
206                            err
207                        );
208                        omitted += 1;
209                        None
210                    }
211                }
212            })
213            .collect::<Vec<_>>();
214
215        if omitted > 0 {
216            return Err(eyre::eyre!("Failed to get address for outputs of tx with txid {} for {} outputs in create_funded_psbt", tx.compute_txid(), omitted).into());
217        }
218
219        let outputs = WalletCreateFundedPsbtOutputs(filtered_outputs);
220
221        self.rpc
222            .wallet_create_funded_psbt(
223                &tx.input
224                    .iter()
225                    .map(|inp| CreateRawTransactionInput {
226                        txid: inp.previous_output.txid,
227                        vout: inp.previous_output.vout,
228                        sequence: Some(inp.sequence.to_consensus_u32()),
229                        // give a specific weight if witness is not empty
230                        weight: if inp.witness.is_empty() {
231                            None
232                        } else {
233                            Some(inp.segwit_weight().to_wu())
234                        },
235                    })
236                    .collect::<Vec<_>>(),
237                outputs,
238                None,
239                Some(create_psbt_opts),
240                None,
241            )
242            .await
243            .map_err(|e| eyre!(e).into())
244    }
245    /// Given a PSBT with inputs that've been signed by the wallet except for our new input,
246    /// we have to sign the input with our secret key.
247    ///
248    /// # Arguments
249    /// * `psbt` - The PSBT to sign.
250    /// * `rbf_signing_info` - The RBF signing info.
251    /// * `cached_leaf_hash` - The cached leaf hash for script path spends.
252    ///
253    /// # Returns
254    /// The signed PSBT as a base64-encoded string.
255    pub async fn attempt_sign_psbt(
256        &self,
257        psbt: String,
258        rbf_signing_info: &RbfSigningInfo,
259        cached_leaf_hash: Option<TapLeafHash>,
260    ) -> Result<String> {
261        // Parse the PSBT from string
262        let mut decoded_psbt = Psbt::from_str(&psbt).map_err(|e| eyre!(e))?;
263
264        // Ensure we have inputs to sign
265        if decoded_psbt.inputs.is_empty() {
266            return Err(eyre!("PSBT has no inputs to sign").into());
267        }
268
269        let input_index = rbf_signing_info.vout as usize;
270
271        // Get the transaction to calculate the sighash
272        let tx = decoded_psbt.unsigned_tx.clone();
273        let mut sighash_cache = SighashCache::new(&tx);
274
275        let tap_sighash_type = rbf_signing_info.tap_sighash_type;
276
277        // Calculate the sighash for this input
278        // Extract previous outputs from the PSBT
279        let prevouts: Vec<bitcoin::TxOut> = decoded_psbt
280            .inputs
281            .iter()
282            .zip(tx.input.iter())
283            .map(|(psbt_input, tx_input)| {
284                // Try witness_utxo first (for segwit inputs)
285                if let Some(witness_utxo) = psbt_input.witness_utxo.clone() {
286                    Ok(witness_utxo)
287                } else if let Some(ref non_witness_tx) = psbt_input.non_witness_utxo {
288                    // For non-segwit inputs, extract the output from the previous transaction
289                    let vout = tx_input.previous_output.vout as usize;
290                    non_witness_tx
291                        .output
292                        .get(vout)
293                        .cloned()
294                        .ok_or_eyre(format!(
295                            "Output index {vout} out of bounds in previous transaction",
296                        ))
297                        .map_err(SendTxError::Other)
298                } else {
299                    Err(eyre!(
300                        "Neither witness_utxo nor non_witness_utxo found for input"
301                    ))
302                    .map_err(SendTxError::Other)
303                }
304            })
305            .collect::<Result<Vec<_>>>()?;
306
307        let sighash = match &rbf_signing_info.spend_path {
308            RbfSigningSpendPath::KeyPath { .. } => sighash_cache
309                .taproot_key_spend_signature_hash(
310                    input_index,
311                    &Prevouts::All(&prevouts),
312                    tap_sighash_type,
313                )
314                .map_err(|e| eyre!("Failed to calculate sighash: {}", e))?,
315            RbfSigningSpendPath::ScriptPath { .. } => sighash_cache
316                .taproot_script_spend_signature_hash(
317                    input_index,
318                    &Prevouts::All(&prevouts),
319                    match cached_leaf_hash {
320                        Some(leaf_hash) => leaf_hash,
321                        None => {
322                            return Err(eyre!(
323                                "Cached leaf hash expected but not found for RBF script spend"
324                            )
325                            .into())
326                        }
327                    },
328                    tap_sighash_type,
329                )
330                .map_err(|e| eyre!("Failed to calculate sighash: {}", e))?,
331        };
332
333        #[cfg(feature = "testing")]
334        let mut sighash = sighash;
335
336        #[cfg(feature = "testing")]
337        {
338            use bitcoin::sighash::Annex;
339            // This should provide the Sighash for the key spend
340            if let Some(ref annex_bytes) = rbf_signing_info.annex {
341                if let RbfSigningSpendPath::ScriptPath { .. } = &rbf_signing_info.spend_path {
342                    return Err(eyre!("Script path RBF signing with annex not supported").into());
343                }
344                let annex = Annex::new(annex_bytes).unwrap();
345                sighash = sighash_cache
346                    .taproot_signature_hash(
347                        input_index,
348                        &Prevouts::All(&prevouts),
349                        Some(annex),
350                        None,
351                        tap_sighash_type,
352                    )
353                    .map_err(|e| eyre!("Failed to calculate sighash with annex: {}", e))?;
354            }
355        }
356
357        // Sign the sighash with our signer
358        let tweak_data = match &rbf_signing_info.spend_path {
359            RbfSigningSpendPath::KeyPath { tweak_merkle_root } => {
360                TapTweakData::KeyPath(*tweak_merkle_root)
361            }
362            RbfSigningSpendPath::ScriptPath { .. } => TapTweakData::ScriptPath,
363        };
364
365        let signature = self
366            .signer
367            .sign_with_tweak_data(sighash, tweak_data)
368            .map_err(|e| eyre!("Failed to sign input: {}", e))?;
369
370        let mut witness = Witness::new();
371        let taproot_signature = taproot::Signature {
372            signature,
373            sighash_type: tap_sighash_type,
374        };
375
376        match &rbf_signing_info.spend_path {
377            RbfSigningSpendPath::KeyPath { .. } => {
378                witness.push(taproot_signature.serialize());
379                // Add the signature to the PSBT
380                decoded_psbt.inputs[input_index].tap_key_sig = Some(taproot_signature);
381            }
382            RbfSigningSpendPath::ScriptPath {
383                control_block,
384                script,
385            } => {
386                witness.push(taproot_signature.serialize());
387                witness.push(script.clone());
388                witness.push(control_block.clone());
389            }
390        }
391
392        #[cfg(feature = "testing")]
393        {
394            if let Some(ref annex_bytes) = rbf_signing_info.annex {
395                witness.push(annex_bytes);
396                tracing::info!("Decoded PSBT: {:?}", decoded_psbt);
397            }
398        }
399        decoded_psbt.inputs[input_index].final_script_witness = Some(witness);
400        // Serialize the signed PSBT back to base64
401        Ok(decoded_psbt.to_string())
402    }
403
404    #[track_caller]
405    pub fn handle_err(
406        &self,
407        err_msg: impl AsRef<str>,
408        err_state: impl Into<String>,
409        try_to_send_id: u32,
410    ) {
411        log_error_for_tx!(self.db, try_to_send_id, err_msg.as_ref());
412
413        let err_state = err_state.into();
414        let db = self.db.clone();
415
416        tokio::spawn(async move {
417            let _ = db
418                .update_tx_debug_sending_state(try_to_send_id, &err_state, true)
419                .await;
420        });
421    }
422
423    /// This function verifies that the wallet has added a funding input to the
424    /// PSBT.
425    ///
426    /// This is required for a transaction to be added to the wallet.
427    pub fn verify_new_inputs(&self, psbt: &str, original_tx: &Transaction) -> bool {
428        let Ok(psbt) = Psbt::from_str(psbt) else {
429            tracing::error!("Failed to parse PSBT");
430            return false;
431        };
432
433        psbt.inputs.len() > original_tx.input.len()
434    }
435
436    /// Reorders PSBT outputs so that the original transaction outputs appear first
437    /// in the same order, followed by any newly added outputs (e.g., change outputs).
438    ///
439    /// This is important for watchtower challenge transactions where the OP_RETURN
440    /// output is expected to remain at a specific index.
441    pub fn reorder_psbt_outputs(&self, psbt: &mut Psbt, original_tx: &Transaction) -> Result<()> {
442        if psbt.unsigned_tx.output.len() != psbt.outputs.len() {
443            return Err(SendTxError::Other(eyre!(
444                "PSBT outputs length mismatch: unsigned_tx outputs={} psbt outputs={}",
445                psbt.unsigned_tx.output.len(),
446                psbt.outputs.len()
447            )));
448        }
449
450        let mut used = vec![false; psbt.unsigned_tx.output.len()];
451        let mut new_outputs = Vec::with_capacity(psbt.unsigned_tx.output.len());
452        let mut new_psbt_outputs = Vec::with_capacity(psbt.outputs.len());
453
454        for original_out in &original_tx.output {
455            let mut found_idx = None;
456            for (idx, out) in psbt.unsigned_tx.output.iter().enumerate() {
457                if !used[idx]
458                    && out.value == original_out.value
459                    && out.script_pubkey == original_out.script_pubkey
460                {
461                    found_idx = Some(idx);
462                    break;
463                }
464            }
465
466            let Some(idx) = found_idx else {
467                return Err(SendTxError::Other(eyre!(
468                    "Failed to find original output in PSBT"
469                )));
470            };
471
472            used[idx] = true;
473            new_outputs.push(psbt.unsigned_tx.output[idx].clone());
474            new_psbt_outputs.push(psbt.outputs[idx].clone());
475        }
476
477        for (idx, out) in psbt.unsigned_tx.output.iter().enumerate() {
478            if !used[idx] {
479                new_outputs.push(out.clone());
480                new_psbt_outputs.push(psbt.outputs[idx].clone());
481            }
482        }
483
484        psbt.unsigned_tx.output = new_outputs;
485        psbt.outputs = new_psbt_outputs;
486
487        Ok(())
488    }
489
490    fn extract_final_tx_from_psbt(psbt: &str) -> Result<Transaction> {
491        let psbt = Psbt::from_str(psbt).map_err(|e| eyre!(e))?;
492        if psbt.inputs.len() != psbt.unsigned_tx.input.len() {
493            return Err(eyre!("PSBT input count mismatch").into());
494        }
495
496        let mut tx = psbt.unsigned_tx.clone();
497        for (idx, input) in tx.input.iter_mut().enumerate() {
498            let psbt_input = &psbt.inputs[idx];
499            if psbt_input.final_script_witness.is_none() && psbt_input.final_script_sig.is_none() {
500                return Err(eyre!("PSBT input {idx} is not finalized").into());
501            }
502            if let Some(witness) = psbt_input.final_script_witness.clone() {
503                input.witness = witness;
504            }
505            if let Some(sig) = psbt_input.final_script_sig.clone() {
506                input.script_sig = sig;
507            }
508        }
509        Ok(tx)
510    }
511
512    pub async fn get_tx_fee(&self, tx: &Transaction) -> Result<Amount> {
513        let inputs = {
514            let mut inputs = Amount::ZERO;
515            for inp in &tx.input {
516                inputs += self
517                    .rpc
518                    .get_txout_from_outpoint(&inp.previous_output)
519                    .await
520                    .map_err(|e| eyre!(e))?
521                    .value;
522            }
523            inputs
524        };
525        let outputs = tx.output.iter().map(|o| o.value).sum::<Amount>();
526
527        let tx_fee = inputs - outputs;
528
529        Ok(tx_fee)
530    }
531
532    /// Sends or bumps a transaction using the Replace-By-Fee (RBF) strategy.
533    ///
534    /// It interacts with the database to track the latest RBF attempt (`last_rbf_txid`).
535    ///
536    /// # Logic:
537    /// 1.  **Check for Existing RBF Tx:** Retrieves RBF txids for the `try_to_send_id` and
538    ///     selects the most recent one still in the mempool.
539    /// 2.  **Bump Existing Tx:** If a mempool tx exists, it calls `rpc.psbt_bump_fee`.
540    ///     - This internally uses the Bitcoin Core `psbtbumpfee` RPC.
541    ///     - We then sign the inputs that we can using our Actor and have the wallet sign the rest.
542    ///
543    /// 3.  **Send Initial RBF Tx:** If no RBF tx is found in the mempool:
544    ///     - It uses `fund_raw_transaction` RPC to let the wallet add (potentially) inputs,
545    ///       outputs, set the fee according to `fee_rate`, and mark the transaction as replaceable.
546    ///     - Uses `sign_raw_transaction_with_wallet` RPC to sign the funded transaction.
547    ///     - Uses `send_raw_transaction` RPC to broadcast the initial RBF transaction.
548    ///     - Saves the resulting `txid` to the database as the `last_rbf_txid`.
549    ///
550    /// # Arguments
551    /// * `try_to_send_id` - The database ID tracking this send attempt.
552    /// * `tx` - The original transaction intended for RBF (used only on the first attempt).
553    /// * `tx_metadata` - Optional metadata associated with the transaction.
554    /// * `fee_rate` - The target fee rate for the RBF replacement.
555    #[tracing::instrument(skip_all, fields(try_to_send_id, tx_meta=?tx_metadata))]
556    #[allow(clippy::too_many_arguments)]
557    pub async fn send_rbf_tx(
558        &self,
559        try_to_send_id: u32,
560        mut tx: Transaction,
561        tx_metadata: Option<TxMetadata>,
562        fee_rate: FeeRateKvb,
563        rbf_signing_info: Option<RbfSigningInfo>,
564        current_tip_height: u32,
565        needs_wtxid_grind: bool,
566    ) -> Result<()> {
567        tracing::debug!(?tx_metadata, "Sending RBF tx",);
568
569        tracing::debug!(?try_to_send_id, "Attempting to send.");
570
571        let _ = self
572            .db
573            .update_tx_debug_sending_state(try_to_send_id, "preparing_rbf", true)
574            .await;
575
576        let rbf_txids = self
577            .db
578            .list_rbf_txids_for_id(None, try_to_send_id)
579            .await
580            .wrap_err("Failed to list RBF txids")?;
581
582        // We check all bumps here but technically as wallet bumpfee rpcs do not use unsafe utxos, if the last rbf txid is
583        // evicted all should be evicted as well. Only while funding the first rbf tx can unsafe outputs be used.
584        let mut bump_from_txid = None;
585        for txid in rbf_txids {
586            match self.rpc.get_mempool_entry(&txid).await {
587                Ok(_) => {
588                    bump_from_txid = Some(txid);
589                    break;
590                }
591                Err(e) => {
592                    // If not in mempool, either evicted or already confirmed/replaced.
593                    if !e.to_string().contains("Transaction not in mempool") {
594                        return Err(eyre!("Failed to get mempool entry for {txid}: {e}").into());
595                    }
596
597                    if let Ok(tx_info) = self.rpc.get_transaction(&txid, None).await {
598                        if tx_info.info.blockhash.is_some() && tx_info.info.confirmations > 0 {
599                            tracing::debug!(
600                                ?try_to_send_id,
601                                "RBF tx {txid} already confirmed, skipping bump"
602                            );
603                            return Ok(());
604                        }
605                    }
606                }
607            }
608        }
609
610        // cache the leaf hash for script path spends
611        let cached_leaf_hash = match &rbf_signing_info {
612            Some(rbf_signing_info) => match &rbf_signing_info.spend_path {
613                RbfSigningSpendPath::ScriptPath { script, .. } => Some(TapLeafHash::from_script(
614                    ScriptBuf::from_bytes(script.clone()).as_script(),
615                    LeafVersion::TapScript,
616                )),
617                _ => None,
618            },
619            None => None,
620        };
621
622        let effective_feerate = if let Some(bump_from_txid) = bump_from_txid {
623            tracing::debug!(
624                ?try_to_send_id,
625                "Attempting to bump fee for txid {bump_from_txid} using psbt_bump_fee"
626            );
627
628            let effective_feerate = self
629                .calculate_bump_feerate_if_needed(&bump_from_txid, fee_rate)
630                .await?;
631
632            let Some(effective_feerate) = effective_feerate else {
633                tracing::debug!(
634                    ?try_to_send_id,
635                    "Original tx feerate already higher than target ({} sat/vB), skipping bump",
636                    fee_rate.to_sat_per_vb_ceil()
637                );
638                return Ok(());
639            };
640
641            let psbt_bump_opts = BumpFeeOptions {
642                conf_target: None, // Use fee_rate instead
643                fee_rate: Some(bitcoincore_rpc::json::FeeRate::per_kvbyte(
644                    Amount::from_sat(effective_feerate.to_sat_per_kvb()),
645                )),
646                replaceable: Some(true), // Ensure the bumped tx is also replaceable
647                estimate_mode: None,
648            };
649
650            let bump_result = self
651                .rpc
652                .psbt_bump_fee(&bump_from_txid, Some(&psbt_bump_opts))
653                .await;
654
655            let mut bumped_psbt = match bump_result {
656                Err(e) => {
657                    // Check for common errors indicating the tx is already confirmed or spent
658                    let rpc_error_str = e.to_string();
659                    if rpc_error_str.contains("Transaction already in block chain") {
660                        tracing::debug!(
661                            ?try_to_send_id,
662                            "RBF bump failed for {bump_from_txid}, likely confirmed or spent: {e}"
663                        );
664                        // No need to return error, just log and proceed
665                        return Ok(());
666                    } else {
667                        // Other potentially transient errors
668                        let error_message = format!("psbt_bump_fee failed: {e}");
669                        log_error_for_tx!(self.db, try_to_send_id, error_message);
670                        let _ = self
671                            .db
672                            .update_tx_debug_sending_state(
673                                try_to_send_id,
674                                "rbf_psbt_bump_failed",
675                                true,
676                            )
677                            .await;
678                        tracing::warn!(?try_to_send_id, "psbt_bump_fee failed: {e:?}");
679                        return Err(SendTxError::Other(eyre!(e)));
680                    }
681                }
682                Ok(BumpFeeResult {
683                    psbt: Some(psbt), ..
684                }) => psbt,
685                Ok(BumpFeeResult { errors, .. }) if !errors.is_empty() => {
686                    self.handle_err(
687                        format!("psbt_bump_fee failed: {errors:?}"),
688                        "rbf_psbt_bump_failed",
689                        try_to_send_id,
690                    );
691                    return Err(SendTxError::Other(eyre!(errors.join(", "))));
692                }
693                Ok(BumpFeeResult { psbt: None, .. }) => {
694                    self.handle_err(
695                        "psbt_bump_fee returned no psbt",
696                        "rbf_psbt_bump_failed",
697                        try_to_send_id,
698                    );
699                    return Err(SendTxError::Other(eyre!("psbt_bump_fee returned no psbt")));
700                }
701            };
702
703            self.fill_in_utxo_info(&mut bumped_psbt)
704                .await
705                .map_err(|err| {
706                    let err = eyre!(err).wrap_err("Failed to fill in utxo info");
707                    self.handle_err(
708                        format!("{err:?}"),
709                        "rbf_fill_in_utxo_info_failed",
710                        try_to_send_id,
711                    );
712
713                    err
714                })?;
715
716            let bumped_psbt = self
717                .copy_witnesses(bumped_psbt, &tx)
718                .await
719                .wrap_err("Failed to fill SAP signatures")?;
720
721            let mut unsigned_psbt = Psbt::from_str(&bumped_psbt).map_err(|e| eyre!(e))?;
722
723            if let Err(err) = self.reorder_psbt_outputs(&mut unsigned_psbt, &tx) {
724                let err_msg = format!("Failed to reorder bumped PSBT outputs: {err}");
725                self.handle_err(
726                    err_msg.clone(),
727                    "rbf_psbt_output_reorder_failed",
728                    try_to_send_id,
729                );
730                return Err(err);
731            }
732            let mut current_locktime = unsigned_psbt.unsigned_tx.lock_time;
733
734            let final_tx = loop {
735                unsigned_psbt.unsigned_tx.lock_time = current_locktime;
736                let bumped_psbt = unsigned_psbt.to_string();
737
738                // Wallet first pass
739                // We rely on the node's wallet here because psbt_bump_fee might add inputs from it.
740                let process_result = self
741                    .rpc
742                    .wallet_process_psbt(&bumped_psbt, Some(true), None, None) // sign=true
743                    .await;
744
745                let processed_psbt = match process_result {
746                    Ok(res) if res.complete => res.psbt,
747                    // attempt to sign
748                    Ok(res) => {
749                        let Some(rbf_signing_info) = &rbf_signing_info else {
750                            return Err(eyre!(
751                                "RBF signing info is required for non SighashSingle RBF txs"
752                            )
753                            .into());
754                        };
755                        self.attempt_sign_psbt(res.psbt, rbf_signing_info, cached_leaf_hash)
756                            .await?
757                    }
758                    Err(e) => {
759                        let err_msg = format!("wallet_process_psbt error: {e}");
760                        tracing::warn!(?try_to_send_id, "{}", err_msg);
761                        log_error_for_tx!(self.db, try_to_send_id, err_msg);
762                        let _ = self
763                            .db
764                            .update_tx_debug_sending_state(
765                                try_to_send_id,
766                                "rbf_psbt_sign_failed",
767                                true,
768                            )
769                            .await;
770                        return Err(SendTxError::Other(eyre!(e)));
771                    }
772                };
773
774                let final_tx = Self::extract_final_tx_from_psbt(&processed_psbt)?;
775                if !needs_wtxid_grind
776                    || final_tx
777                        .compute_wtxid()
778                        .as_raw_hash()
779                        .to_byte_array()
780                        .starts_with(REVEAL_TX_PREFIX)
781                {
782                    break final_tx;
783                } else {
784                    current_locktime = LockTime::from_consensus(std::cmp::max(
785                        current_locktime.to_consensus_u32() + 1,
786                        LOCK_TIME_THRESHOLD,
787                    ));
788                }
789            };
790
791            let bumped_txid = final_tx.compute_txid();
792
793            // Broadcast the finalized transaction
794            let sent_txid = match self.rpc.send_raw_transaction(&final_tx).await {
795                Ok(sent_txid) if sent_txid == bumped_txid => sent_txid,
796                Ok(other_txid) => {
797                    log_error_for_tx!(
798                        self.db,
799                        try_to_send_id,
800                        format!(
801                            "send_raw_transaction returned unexpected txid {} (expected {})",
802                            other_txid, bumped_txid
803                        )
804                    );
805                    let _ = self
806                        .db
807                        .update_tx_debug_sending_state(
808                            try_to_send_id,
809                            "rbf_send_txid_mismatch",
810                            true,
811                        )
812                        .await;
813                    return Err(SendTxError::Other(eyre!(
814                        "send_raw_transaction returned unexpected txid"
815                    )));
816                }
817                Err(e) => {
818                    log_error_for_tx!(
819                        self.db,
820                        try_to_send_id,
821                        format!("send_raw_transaction error for bumped RBF tx: {}", e)
822                    );
823                    let _ = self
824                        .db
825                        .update_tx_debug_sending_state(try_to_send_id, "rbf_bump_send_failed", true)
826                        .await;
827                    return Err(SendTxError::Other(eyre!(e)));
828                }
829            };
830
831            tracing::debug!(
832                ?try_to_send_id,
833                "RBF tx {bump_from_txid} successfully bumped and sent as {sent_txid}"
834            );
835
836            let _ = self
837                .db
838                .update_tx_debug_sending_state(try_to_send_id, "rbf_bumped_sent", true)
839                .await;
840
841            self.db
842                .save_rbf_txid(None, try_to_send_id, sent_txid)
843                .await
844                .wrap_err("Failed to save new RBF txid after bump")?;
845
846            effective_feerate
847        } else {
848            tracing::debug!(
849                ?try_to_send_id,
850                "Funding initial RBF tx using PSBT workflow"
851            );
852
853            let _ = self
854                .db
855                .update_tx_debug_sending_state(try_to_send_id, "creating_initial_rbf_psbt", true)
856                .await;
857
858            // for accurate fee calculation, fill in the witness with dummy data if its empty
859            if let Some(rbf_signing_info) = &rbf_signing_info {
860                let vout = rbf_signing_info.vout as usize;
861                let input_count = tx.input.len();
862                let input = tx.input.get_mut(vout).ok_or_else(|| {
863                    SendTxError::from(eyre!(
864                        "Input at vout {} given in RBF signing info does not exist in transaction (has {} inputs)",
865                        vout,
866                        input_count
867                    ))
868                })?;
869                if input.witness.is_empty() {
870                    match &rbf_signing_info.spend_path {
871                        RbfSigningSpendPath::KeyPath { .. } => {
872                            input.witness = Witness::from_slice(&[&[0u8; 65]]);
873                        }
874                        RbfSigningSpendPath::ScriptPath {
875                            script,
876                            control_block,
877                        } => {
878                            let mut witness = Witness::new();
879                            witness.push([0u8; 65]);
880                            witness.push(script.clone());
881                            witness.push(control_block.clone());
882                            input.witness = witness;
883                        }
884                    }
885                }
886            }
887
888            let mut added_dummy_output = false;
889            // if the tx has no outputs, btc core wallet fund transaction will fail, so we add a dummy output
890            // which we will remove later to save on fees.
891            if tx.output.is_empty() {
892                tx.output.push(TxOut {
893                    value: NON_EPHEMERAL_ANCHOR_AMOUNT,
894                    script_pubkey: ScriptBuf::from_hex("51024e73").expect("valid anchor script"),
895                });
896                added_dummy_output = true;
897            }
898
899            let create_result = self
900                .create_funded_psbt(&tx, fee_rate)
901                .await
902                .map_err(|err| {
903                    let err = eyre!(err).wrap_err("Failed to create funded PSBT");
904                    self.handle_err(format!("{err:?}"), "rbf_psbt_create_failed", try_to_send_id);
905
906                    err
907                })?;
908
909            if !self.verify_new_inputs(&create_result.psbt, &tx) {
910                tracing::warn!(
911                    ?try_to_send_id,
912                    "Transaction has not been funded and is being sent as is. This transaction will have to be manually bumped as the wallet will not add it to itself."
913                );
914            }
915            let mut funded_psbt_str = create_result.psbt;
916
917            self.fill_in_utxo_info(&mut funded_psbt_str)
918                .await
919                .map_err(|err| {
920                    let err = eyre!(err).wrap_err("Failed to fill in utxo info");
921                    self.handle_err(
922                        format!("{err:?}"),
923                        "rbf_fill_in_utxo_info_failed",
924                        try_to_send_id,
925                    );
926
927                    err
928                })?;
929
930            funded_psbt_str = self
931                .copy_witnesses(funded_psbt_str, &tx)
932                .await
933                .map_err(|err| {
934                    let err = eyre!(err).wrap_err("Failed to copy witnesses");
935                    self.handle_err(
936                        format!("{err:?}"),
937                        "rbf_copy_witnesses_failed",
938                        try_to_send_id,
939                    );
940
941                    err
942                })?;
943
944            let mut funded_psbt = Psbt::from_str(&funded_psbt_str).map_err(|e| eyre!(e))?;
945            if added_dummy_output {
946                // we delete the first output which is the dummy output we added earlier
947                // we also adjust the amount of the change output to compensate for removal of the dummy output.
948                let dummy_output_weight = funded_psbt.unsigned_tx.output[0].weight();
949                let dummy_output_value = funded_psbt.unsigned_tx.output[0].value;
950                let needed_fee_for_dummy_output = fee_rate.fee_wu(dummy_output_weight).ok_or_eyre(format!("Fee overflow occurred for dummy output: current fee rate: {fee_rate}, dummy_output_weight: {dummy_output_weight}"))?;
951                funded_psbt.unsigned_tx.output.remove(0);
952                funded_psbt.outputs.remove(0);
953                let Some(change_output) = funded_psbt.unsigned_tx.output.last_mut() else {
954                    let err_msg = "Failed to remove dummy output: funded no-output RBF transaction has no change output";
955                    return Err(SendTxError::Other(eyre!(err_msg)));
956                };
957                change_output.value += needed_fee_for_dummy_output + dummy_output_value;
958            } else if let Err(err) = self.reorder_psbt_outputs(&mut funded_psbt, &tx) {
959                // fund transaction shouldn't reorder but keep it here in case it does
960                let err_msg = format!("Failed to reorder initial PSBT outputs: {err}");
961                self.handle_err(
962                    err_msg.clone(),
963                    "rbf_psbt_output_reorder_failed",
964                    try_to_send_id,
965                );
966                return Err(err);
967            }
968            let mut current_locktime = tx.lock_time;
969
970            let final_tx = loop {
971                // replace locktime and version
972                funded_psbt.unsigned_tx.lock_time = current_locktime;
973                funded_psbt.unsigned_tx.version = tx.version;
974
975                tracing::debug!(
976                    try_to_send_id,
977                    "Successfully created initial RBF PSBT with fee {}",
978                    create_result.fee
979                );
980
981                let mut psbt = funded_psbt.to_string();
982
983                // 2. Process the PSBT (let the wallet sign its inputs)
984                let process_result = self
985                    .rpc
986                    .wallet_process_psbt(&psbt, Some(true), None, None)
987                    .await
988                    .map_err(|err| {
989                        let err = eyre!(err).wrap_err("Failed to process initial RBF PSBT");
990                        self.handle_err(
991                            format!("{err:?}"),
992                            "rbf_psbt_process_failed",
993                            try_to_send_id,
994                        );
995
996                        err
997                    })?;
998
999                if let Some(rbf_signing_info) = &rbf_signing_info {
1000                    psbt = self
1001                        .attempt_sign_psbt(process_result.psbt, rbf_signing_info, cached_leaf_hash)
1002                        .await
1003                        .map_err(|err| {
1004                            let err = eyre!(err).wrap_err("Failed to sign initial RBF PSBT");
1005                            self.handle_err(
1006                                format!("{err:?}"),
1007                                "rbf_psbt_sign_failed",
1008                                try_to_send_id,
1009                            );
1010
1011                            err
1012                        })?;
1013                } else {
1014                    psbt = process_result.psbt;
1015                }
1016
1017                tracing::debug!(try_to_send_id, "Successfully processed initial RBF PSBT");
1018
1019                let final_tx = Self::extract_final_tx_from_psbt(&psbt)?;
1020                // check if wtxid prefix is correct if grinding is needed
1021                if !needs_wtxid_grind
1022                    || final_tx
1023                        .compute_wtxid()
1024                        .as_raw_hash()
1025                        .to_byte_array()
1026                        .starts_with(REVEAL_TX_PREFIX)
1027                {
1028                    break final_tx;
1029                } else {
1030                    // increase locktime by 1 time unit
1031                    current_locktime = LockTime::from_consensus(std::cmp::max(
1032                        current_locktime.to_consensus_u32() + 1,
1033                        LOCK_TIME_THRESHOLD,
1034                    ));
1035                }
1036            };
1037
1038            let initial_txid = final_tx.compute_txid();
1039
1040            // 4. Broadcast the finalized transaction
1041            let sent_txid = match self.rpc.send_raw_transaction(&final_tx).await {
1042                Ok(sent_txid) => {
1043                    if sent_txid != initial_txid {
1044                        let err_msg = format!(
1045                            "send_raw_transaction returned unexpected txid {sent_txid} (expected {initial_txid}) for initial RBF",
1046                        );
1047                        log_error_for_tx!(self.db, try_to_send_id, err_msg);
1048                        let _ = self
1049                            .db
1050                            .update_tx_debug_sending_state(
1051                                try_to_send_id,
1052                                "rbf_initial_send_txid_mismatch",
1053                                true,
1054                            )
1055                            .await;
1056                        return Err(SendTxError::Other(eyre!(err_msg)));
1057                    }
1058                    tracing::debug!(
1059                        try_to_send_id,
1060                        "Successfully sent initial RBF tx with txid {sent_txid}"
1061                    );
1062                    sent_txid
1063                }
1064                Err(e) => {
1065                    tracing::error!("RBF failed for: {:?}", final_tx);
1066                    let err_msg = format!("send_raw_transaction error for initial RBF tx: {e}");
1067                    log_error_for_tx!(self.db, try_to_send_id, err_msg);
1068                    let _ = self
1069                        .db
1070                        .update_tx_debug_sending_state(
1071                            try_to_send_id,
1072                            "rbf_initial_send_failed",
1073                            true,
1074                        )
1075                        .await;
1076                    return Err(SendTxError::Other(eyre!(e)));
1077                }
1078            };
1079
1080            // Update debug sending state
1081            let _ = self
1082                .db
1083                .update_tx_debug_sending_state(try_to_send_id, "rbf_initial_sent", true)
1084                .await;
1085
1086            self.db
1087                .save_rbf_txid(None, try_to_send_id, sent_txid)
1088                .await
1089                .wrap_err("Failed to save initial RBF txid")?;
1090
1091            fee_rate
1092        };
1093
1094        self.db
1095            .update_effective_fee_rate(None, try_to_send_id, effective_feerate, current_tip_height)
1096            .await
1097            .wrap_err("Failed to update effective fee rate")?;
1098
1099        Ok(())
1100    }
1101}