clementine_tx_sender/
cpfp.rs

1//! # Child Pays For Parent (CPFP) Support For Transaction Sender
2//!
3//! This module implements the Child Pays For Parent (CPFP) strategy for sending
4//! Bitcoin transactions with transaction sender.
5//!
6//! ## Child Transaction Details
7//!
8//! A child transaction is created to pay for the fees of a parent transaction.
9//! They must be submitted together as a package for Bitcoin nodes to accept
10//! them.
11//!
12//! ### Fee Payer Transactions/UTXOs
13//!
14//! Child transaction needs to spend an UTXO for the fees. But because of the
15//! TRUC rules (https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki#specification),
16//! a third transaction can't be put into the package. So, a so called "fee
17//! payer" transaction must be send and confirmed before the CPFP package is
18//! send.
19
20use super::Result;
21use crate::{TxSender, TxSenderTransaction};
22use bitcoin::absolute::LockTime;
23use bitcoin::sighash::{Prevouts, SighashCache};
24use bitcoin::taproot;
25use bitcoin::transaction::Version;
26use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Weight};
27use bitcoin::{TapSighashType, Witness};
28use bitcoincore_rpc::json::FundRawTransactionOptions;
29use bitcoincore_rpc::{PackageTransactionResult, RpcApi};
30use clementine_errors::{BitcoinRPCError, BridgeError, ResultExt as _, SendTxError};
31use clementine_primitives::FeeRateKvb;
32use clementine_primitives::{MIN_TAPROOT_AMOUNT, NON_STANDARD_V3};
33use clementine_utils::{FeePayingType, TxMetadata};
34use eyre::{eyre, Context};
35use std::collections::HashSet;
36use std::env;
37
38impl TxSender {
39    fn anchor_prevout(anchor_sat: Amount) -> TxOut {
40        // P2A anchor script: OP_1 OP_PUSHBYTES_2 0x4e73
41        TxOut {
42            value: anchor_sat,
43            script_pubkey: ScriptBuf::from_hex("51024e73").expect("statically valid anchor script"),
44        }
45    }
46
47    fn build_and_sign_child_tx(
48        &self,
49        p2a_anchor: OutPoint,
50        anchor_sat: Amount,
51        fee_payer_utxos: Vec<crate::SpendableUtxo>,
52        change_address: bitcoin::Address,
53        required_fee: Amount,
54    ) -> Result<Transaction> {
55        let total_in: Amount = fee_payer_utxos
56            .iter()
57            .map(|u| u.txout.value)
58            .sum::<Amount>()
59            + anchor_sat;
60
61        let change_amount = total_in
62            .checked_sub(required_fee)
63            .ok_or_else(|| SendTxError::Other(eyre!("required_fee > total_in")))?;
64
65        let mut inputs: Vec<TxIn> = Vec::with_capacity(1 + fee_payer_utxos.len());
66        inputs.push(TxIn {
67            previous_output: p2a_anchor,
68            script_sig: ScriptBuf::new(),
69            sequence: crate::DEFAULT_SEQUENCE,
70            witness: Witness::new(),
71        });
72
73        for utxo in &fee_payer_utxos {
74            inputs.push(TxIn {
75                previous_output: utxo.outpoint,
76                script_sig: ScriptBuf::new(),
77                sequence: crate::DEFAULT_SEQUENCE,
78                witness: Witness::new(),
79            });
80        }
81
82        let mut child_tx = Transaction {
83            version: NON_STANDARD_V3,
84            lock_time: LockTime::ZERO,
85            input: inputs,
86            output: vec![TxOut {
87                value: change_amount,
88                script_pubkey: change_address.script_pubkey(),
89            }],
90        };
91
92        // Prevouts must match the tx input order (anchor first).
93        let mut prevouts: Vec<TxOut> = Vec::with_capacity(child_tx.input.len());
94        prevouts.push(Self::anchor_prevout(anchor_sat));
95        prevouts.extend(fee_payer_utxos.into_iter().map(|u| u.txout));
96
97        // Compute witnesses without mutating tx while the sighash cache borrows it.
98        let mut cache = SighashCache::new(&child_tx);
99        let mut signed_witnesses: Vec<(usize, Witness)> = Vec::new();
100
101        for input_index in 1..child_tx.input.len() {
102            let sighash = cache
103                .taproot_key_spend_signature_hash(
104                    input_index,
105                    &Prevouts::All(&prevouts),
106                    TapSighashType::Default,
107                )
108                .map_err(|e| SendTxError::Other(eyre!("failed to compute sighash: {e}")))?;
109
110            let signature = self
111                .signer
112                .sign_with_tweak_data(sighash, clementine_utils::sign::TapTweakData::KeyPath(None))
113                .map_err(|e| SendTxError::Other(e.into()))?;
114
115            let tr_sig = taproot::Signature {
116                signature,
117                sighash_type: TapSighashType::Default,
118            };
119            signed_witnesses.push((input_index, Witness::p2tr_key_spend(&tr_sig)));
120        }
121
122        for (idx, witness) in signed_witnesses {
123            child_tx.input[idx].witness = witness;
124        }
125
126        Ok(child_tx)
127    }
128
129    /// Creates and broadcasts a new "fee payer" UTXO to be used for CPFP
130    /// transactions.
131    ///
132    /// This function is called when a CPFP attempt fails due to insufficient funds
133    /// in the existing confirmed fee payer UTXOs associated with a transaction (`bumped_id`).
134    /// It calculates the required fee based on the parent transaction (`tx`) and the current
135    /// `fee_rate`, adding a buffer (2x required fee + dust limit) to handle potential fee spikes.
136    /// It then sends funds to the `TxSender`'s own signer address using the RPC's
137    /// `send_to_address` and saves the resulting UTXO information (`outpoint`, `amount`)
138    /// to the database, linking it to the `bumped_id`.
139    ///
140    /// # Arguments
141    /// * `bumped_id` - The database ID of the parent transaction requiring the fee bump.
142    /// * `tx` - The parent transaction itself.
143    /// * `fee_rate` - The target fee rate for the CPFP package.
144    /// * `total_fee_payer_amount` - The sum of amounts in currently available confirmed fee payer UTXOs.
145    /// * `fee_payer_utxos_len` - The number of currently available confirmed fee payer UTXOs.
146    async fn create_fee_payer_utxo(
147        &self,
148        bumped_id: u32,
149        dbtx: Option<&mut TxSenderTransaction>,
150        tx: &Transaction,
151        fee_rate: FeeRateKvb,
152        total_fee_payer_amount: Amount,
153        fee_payer_utxos_len: usize,
154    ) -> Result<()> {
155        tracing::debug!(
156            "Creating fee payer UTXO for txid {} with bump id {}",
157            &tx.compute_txid().to_string(),
158            bumped_id
159        );
160        let required_fee = Self::calculate_required_fee(
161            tx.weight(),
162            fee_payer_utxos_len + 1,
163            fee_rate,
164            FeePayingType::CPFP,
165        )?;
166
167        // Aggressively add 2x required fee to the total amount to account for sudden spikes
168        // We won't actually use 2x fees, but the fee payer utxo will hold that much amount so that while fee payer utxo gets mined
169        // if fees increase the utxo should still be sufficient to fund the tx with high probability
170        // leftover fees will get sent back to wallet with a change output in fn create_child_tx
171        let new_total_fee_needed = required_fee
172            .checked_mul(2)
173            .and_then(|fee| fee.checked_add(MIN_TAPROOT_AMOUNT));
174        if new_total_fee_needed.is_none() {
175            return Err(eyre!("Total fee needed is too large, required fee: {}, total fee payer amount: {}, fee rate: {}", required_fee, total_fee_payer_amount, fee_rate).into());
176        }
177        let new_fee_payer_amount =
178            new_total_fee_needed.and_then(|fee| fee.checked_sub(total_fee_payer_amount));
179
180        let new_fee_payer_amount = match new_fee_payer_amount {
181            Some(fee) => fee,
182            // if underflow, no new fee payer utxo is needed, log it anyway in case its a bug
183            None => {
184                tracing::debug!("create_fee_payer_utxo was called but no new fee payer utxo is needed for tx: {:?}, required fee: {}, total fee payer amount: {}, current fee rate: {}", tx, required_fee, total_fee_payer_amount, fee_rate);
185                return Ok(());
186            }
187        };
188
189        tracing::debug!(
190            "Creating fee payer UTXO with amount {} ({} sat/kvB)",
191            new_fee_payer_amount,
192            fee_rate
193        );
194
195        let fee_payer_tx = Transaction {
196            version: Version::TWO,
197            lock_time: LockTime::ZERO,
198            input: vec![],
199            output: vec![TxOut {
200                value: new_fee_payer_amount,
201                script_pubkey: self.signer.address().script_pubkey(),
202            }],
203        };
204
205        let fee_payer_bytes = crate::serialize_tx_for_fund_raw(&fee_payer_tx);
206
207        let funded_fee_payer_tx = self
208            .rpc
209            .fund_raw_transaction(
210                &fee_payer_bytes,
211                Some(&FundRawTransactionOptions {
212                    add_inputs: Some(true),
213                    // for cpfp txs, the speed of tx inclusion is not that important, so we can not use unsafe utxos and wait for them to become safe. Also all cpfp fee payer tx's are safe (all wallet owned inputs), so wallet can already chain them
214                    include_unsafe: Some(self.include_unsafe),
215                    change_address: None,
216                    change_position: None,
217                    change_type: None,
218                    include_watching: None,
219                    lock_unspents: None,
220                    fee_rate: Some(Amount::from_sat(fee_rate.to_sat_per_kvb())),
221                    subtract_fee_from_outputs: None,
222                    replaceable: Some(true),
223                    conf_target: None,
224                    estimate_mode: None,
225                }),
226                None,
227            )
228            .await
229            .wrap_err("Failed to fund cpfp fee payer tx")?
230            .hex;
231
232        let signed_fee_payer_tx: Transaction = bitcoin::consensus::deserialize(
233            &self
234                .rpc
235                .sign_raw_transaction_with_wallet(&funded_fee_payer_tx, None, None)
236                .await
237                .wrap_err("Failed to sign funded tx through bitcoin RPC")?
238                .hex,
239        )
240        .wrap_err("Failed to deserialize signed tx")?;
241
242        let outpoint_vout = signed_fee_payer_tx
243            .output
244            .iter()
245            .position(|o| {
246                o.value == new_fee_payer_amount
247                    && o.script_pubkey == self.signer.address().script_pubkey()
248            })
249            .ok_or(eyre!("Failed to find outpoint vout"))?;
250
251        self.rpc
252            .send_raw_transaction(&signed_fee_payer_tx)
253            .await
254            .wrap_err("Failed to send signed fee payer tx")?;
255
256        self.db
257            .save_fee_payer_tx(
258                dbtx,
259                bumped_id,
260                signed_fee_payer_tx.compute_txid(),
261                outpoint_vout as u32,
262                new_fee_payer_amount,
263                None,
264            )
265            .await
266            .map_to_eyre()?;
267
268        Ok(())
269    }
270
271    /// Creates a Child-Pays-For-Parent (CPFP) child transaction.
272    ///
273    /// This transaction spends:
274    /// 1.  The designated "P2A anchor" output of the parent transaction (`p2a_anchor`).
275    /// 2.  One or more confirmed "fee payer" UTXOs (`fee_payer_utxos`) controlled by the `signer`.
276    ///
277    /// It calculates the total fee required (`required_fee`) to make the combined parent + child
278    /// package attractive to miners at the target `fee_rate`. The `required_fee` is paid entirely
279    /// by this child transaction.
280    ///
281    /// The remaining value (total input value - `required_fee`) is sent to the `change_address`.
282    ///
283    /// # Signing
284    /// We sign the input spending the P2A anchor and all fee payer UTXOs.
285    ///
286    /// # Returns
287    /// The constructed and partially signed child transaction.
288    async fn create_child_tx(
289        &self,
290        p2a_anchor: OutPoint,
291        anchor_sat: Amount,
292        fee_payer_utxos: Vec<crate::SpendableUtxo>,
293        parent_tx_size: Weight,
294        fee_rate: FeeRateKvb,
295    ) -> Result<Transaction> {
296        let required_fee = Self::calculate_required_fee(
297            parent_tx_size,
298            fee_payer_utxos.len(),
299            fee_rate,
300            FeePayingType::CPFP,
301        )?;
302
303        let change_address = self
304            .rpc
305            .get_new_wallet_address()
306            .await
307            .wrap_err("Failed to get new wallet address")?;
308
309        let total_fee_payer_amount = fee_payer_utxos
310            .iter()
311            .map(|utxo| utxo.txout.value)
312            .sum::<Amount>()
313            + anchor_sat;
314
315        if change_address.script_pubkey().minimal_non_dust() + required_fee > total_fee_payer_amount
316        {
317            return Err(SendTxError::InsufficientFeePayerAmount);
318        }
319
320        self.build_and_sign_child_tx(
321            p2a_anchor,
322            anchor_sat,
323            fee_payer_utxos,
324            change_address,
325            required_fee,
326        )
327    }
328
329    /// Creates a transaction package for CPFP submission.
330    ///
331    /// Finds the P2A anchor output in the parent transaction (`tx`), then constructs
332    /// the child transaction using `create_child_tx`.
333    ///
334    /// # Returns
335    ///
336    /// - [`Vec<Transaction>`]: Parent transaction followed by the child
337    ///   transaction ready for submission via the `submitpackage` RPC.
338    async fn create_package(
339        &self,
340        tx: Transaction,
341        fee_rate: FeeRateKvb,
342        fee_payer_utxos: Vec<crate::SpendableUtxo>,
343    ) -> Result<Vec<Transaction>> {
344        let txid = tx.compute_txid();
345        let p2a_vout = self
346            .find_p2a_vout(&tx)
347            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
348        let anchor_sat = tx.output[p2a_vout].value;
349
350        let child_tx = self
351            .create_child_tx(
352                OutPoint {
353                    txid,
354                    vout: p2a_vout as u32,
355                },
356                anchor_sat,
357                fee_payer_utxos,
358                tx.weight(),
359                fee_rate,
360            )
361            .await?;
362
363        Ok(vec![tx, child_tx])
364    }
365
366    /// Retrieves confirmed fee payer UTXOs associated with a specific send attempt.
367    ///
368    /// Queries the database for UTXOs linked to `try_to_send_id` that are marked as confirmed.
369    /// These UTXOs are controlled by the `TxSender`'s `signer` and are intended to be
370    /// spent by a CPFP child transaction.
371    ///
372    /// # Returns
373    ///
374    /// - [`Vec<B::SpendableInput>`]: [`B::SpendableInput`]s of the confirmed fee payer
375    ///   UTXOs that are ready to be included as inputs in the CPFP child tx.
376    async fn get_confirmed_fee_payer_utxos(
377        &self,
378        try_to_send_id: u32,
379    ) -> Result<Vec<crate::SpendableUtxo>> {
380        let utxos = self
381            .db
382            .get_confirmed_fee_payer_utxos(None, try_to_send_id)
383            .await
384            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
385
386        let mut spendables = Vec::with_capacity(utxos.len());
387
388        for (txid, vout, _db_amount) in utxos {
389            let utxo = self
390                .rpc
391                .get_tx_out(&txid, vout, Some(false))
392                .await
393                .wrap_err("Failed to gettxout for fee payer utxo")?;
394
395            let Some(utxo) = utxo else {
396                // We expected this to be a confirmed, spendable fee payer UTXO, but it is no
397                // longer unspent (spent/reorg/evicted). Do not mutate DB here; bubble up.
398                return Err(SendTxError::Other(eyre!(
399                    "Confirmed fee payer UTXO missing from gettxout: {txid}:{vout}"
400                )));
401            };
402
403            let script_pubkey = utxo
404                .script_pub_key
405                .script()
406                .wrap_err("Failed to parse script pubkey from gettxout")?;
407
408            spendables.push(crate::SpendableUtxo {
409                outpoint: OutPoint { txid, vout },
410                txout: TxOut {
411                    value: utxo.value,
412                    script_pubkey,
413                },
414                spend_info: None,
415            });
416        }
417
418        Ok(spendables)
419    }
420
421    /// Attempts to bump the fees of unconfirmed "fee payer" UTXOs using RBF.
422    ///
423    /// Fee payer UTXOs are created to fund CPFP child transactions. However, these
424    /// fee payer creation transactions might themselves get stuck due to low fees.
425    /// This function identifies such unconfirmed fee payer transactions associated with
426    /// a parent transaction (`bumped_id`) and attempts to RBF them using the provided `fee_rate`.
427    ///
428    /// This ensures the fee payer UTXOs confirm quickly, making them available to be spent
429    /// by the actual CPFP child transaction.
430    ///
431    /// # Arguments
432    /// * `fee_rate` - The target fee rate for bumping the fee payer transactions.
433    #[tracing::instrument(skip_all, fields(fee_rate))]
434    pub async fn bump_fees_of_unconfirmed_fee_payer_txs(&self, fee_rate: FeeRateKvb) -> Result<()> {
435        let bumpable_txs = self
436            .db
437            .get_all_unconfirmed_fee_payer_txs(None)
438            .await
439            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
440        let mut not_evicted_ids = HashSet::new();
441        let mut all_parent_ids = HashSet::new();
442
443        for (id, try_to_send_id, fee_payer_txid, vout, amount, replacement_of_id) in bumpable_txs {
444            tracing::debug!(
445                "Bumping fee for fee payer tx {} for try to send id {} for fee rate {}",
446                fee_payer_txid,
447                try_to_send_id,
448                fee_rate
449            );
450            let parent_id = replacement_of_id.unwrap_or(id);
451            all_parent_ids.insert(parent_id);
452
453            match self.rpc.get_mempool_entry(&fee_payer_txid).await {
454                Ok(info) => {
455                    not_evicted_ids.insert(parent_id);
456                    // if it has descendants, it cannot be bumped, or if it was bumped recently, we should not bump it again
457                    if info.descendant_count > 1
458                        || std::time::SystemTime::now()
459                            .duration_since(std::time::UNIX_EPOCH)
460                            .unwrap()
461                            .as_secs()
462                            .saturating_sub(info.time)
463                            < self.tx_sender_limits.cpfp_fee_payer_bump_wait_time_seconds
464                    {
465                        continue;
466                    }
467                }
468                Err(e) => {
469                    // If not in mempool we should ignore, it was either evicted or replaced by a bumped feepayer tx
470                    // give an error if the error is not "Transaction not in mempool"
471                    if !e.to_string().contains("Transaction not in mempool") {
472                        return Err(
473                            eyre!("Failed to get mempool entry for {fee_payer_txid}: {e}").into(),
474                        );
475                    }
476                    // get_transaction only returns if tx is wallet owned, it should not be an issue here as if it is not wallet owned,
477                    // for example if wallet was changed and txsender restarted, it cannot be bumped anyway
478                    if let Ok(tx_info) = self.rpc.get_transaction(&fee_payer_txid, None).await {
479                        if tx_info.info.blockhash.is_some() && tx_info.info.confirmations > 0 {
480                            not_evicted_ids.insert(parent_id);
481                        }
482                    }
483                    continue;
484                }
485            }
486
487            match self
488                .rpc
489                .bump_fee_with_fee_rate(fee_payer_txid, fee_rate)
490                .await
491            {
492                Ok(new_txid) => {
493                    if new_txid != fee_payer_txid {
494                        self.db
495                            .save_fee_payer_tx(
496                                None,
497                                try_to_send_id,
498                                new_txid,
499                                vout,
500                                amount,
501                                Some(parent_id),
502                            )
503                            .await
504                            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
505                    } else {
506                        tracing::trace!(
507                            "Fee payer tx {} has enough fee, no need to bump",
508                            fee_payer_txid
509                        );
510                    }
511                }
512                Err(e) => match e {
513                    BitcoinRPCError::TransactionAlreadyInBlock(block_hash) => {
514                        tracing::debug!(
515                            "Fee payer tx {} is already in block {}, skipping",
516                            fee_payer_txid,
517                            block_hash
518                        );
519                        continue;
520                    }
521                    BitcoinRPCError::BumpFeeUTXOSpent(outpoint) => {
522                        tracing::debug!(
523                            "Fee payer tx {} is already onchain, skipping: {:?}",
524                            fee_payer_txid,
525                            outpoint
526                        );
527                        continue;
528                    }
529                    _ => {
530                        tracing::warn!(
531                            "Failed to bump fee the fee payer tx {} with error {e}, skipping",
532                            fee_payer_txid
533                        );
534                        continue;
535                    }
536                },
537            }
538        }
539
540        for parent_id in all_parent_ids {
541            if !not_evicted_ids.contains(&parent_id) {
542                self.db
543                    .mark_fee_payer_utxo_as_evicted(None, parent_id)
544                    .await
545                    .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
546            }
547        }
548        Ok(())
549    }
550
551    /// Sends a transaction using the Child-Pays-For-Parent (CPFP) strategy.
552    ///
553    /// # Logic:
554    /// 1.  **Check Unconfirmed Fee Payers:** Ensures no unconfirmed fee payer UTXOs exist
555    ///     for this `try_to_send_id`. If they do, skips this transaction for now
556    ///     as they need to confirm before being spendable by the child.
557    /// 2.  **Get Confirmed Fee Payers:** Retrieves the available confirmed fee payer UTXOs.
558    /// 3.  **Create Package:** Calls `create_package` to build the `vec![parent_tx, child_tx]`.
559    ///     The `child_tx` spends the parent's anchor output and the fee payer UTXOs, paying
560    ///     a fee calculated for the whole package.
561    /// 4.  **Test Mempool Accept (Not implemented right now as testmempoolaccept didn't support TRUC package submission #1011):**
562    ///     Uses `testmempoolaccept` RPC to check if the package is likely to be accepted by the network before submitting.
563    /// 5.  **Submit Package:** Uses the `submitpackage` RPC to atomically submit the parent
564    ///     and child transactions. Bitcoin Core evaluates the fee rate of the package together.
565    /// 6.  **Handle Results:** Checks the `submitpackage` result. If successful or already in
566    ///     mempool, updates the effective fee rate in the database. If failed, returns an error.
567    ///
568    /// # Arguments
569    /// * `try_to_send_id` - The database ID tracking this send attempt.
570    /// * `tx` - The parent transaction requiring the fee bump.
571    /// * `tx_metadata` - Optional metadata associated with the transaction.
572    /// * `fee_rate` - The target fee rate for the CPFP package.
573    /// * `current_tip_height` - The current height of the tip of the chain.
574    #[tracing::instrument(skip_all, fields(try_to_send_id, tx_meta=?tx_metadata))]
575    pub async fn send_cpfp_tx(
576        &self,
577        try_to_send_id: u32,
578        tx: Transaction,
579        tx_metadata: Option<TxMetadata>,
580        fee_rate: FeeRateKvb,
581        current_tip_height: u32,
582    ) -> Result<()> {
583        let unconfirmed = self
584            .db
585            .get_unconfirmed_fee_payer_txs(None, try_to_send_id)
586            .await
587            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
588        if !unconfirmed.is_empty() {
589            // Log that we're waiting for unconfirmed UTXOs
590            tracing::debug!(
591                try_to_send_id,
592                "Waiting for {} UTXOs to confirm",
593                unconfirmed.len()
594            );
595
596            let _ = self
597                .db
598                .update_tx_debug_sending_state(
599                    try_to_send_id,
600                    "waiting_for_utxo_confirmation",
601                    true,
602                )
603                .await;
604            return Ok(());
605        }
606
607        let confirmed = self.get_confirmed_fee_payer_utxos(try_to_send_id).await?;
608        let total_amount: Amount = confirmed.iter().map(|u| u.txout.value).sum();
609
610        let _ = self
611            .db
612            .update_tx_debug_sending_state(try_to_send_id, "creating_package", true)
613            .await;
614
615        let package = match self
616            .create_package(tx.clone(), fee_rate, confirmed.clone())
617            .await
618        {
619            Ok(p) => p,
620            Err(SendTxError::InsufficientFeePayerAmount) => {
621                self.create_fee_payer_utxo(
622                    try_to_send_id,
623                    None,
624                    &tx,
625                    fee_rate,
626                    total_amount,
627                    confirmed.len(),
628                )
629                .await?;
630                let _ = self
631                    .db
632                    .update_tx_debug_sending_state(
633                        try_to_send_id,
634                        "waiting_for_fee_payer_utxos",
635                        true,
636                    )
637                    .await;
638                return Ok(());
639            }
640            Err(e) => {
641                tracing::error!(try_to_send_id, "Failed to create CPFP package: {:?}", e);
642                return Err(e);
643            }
644        };
645
646        let package_refs: Vec<&Transaction> = package.iter().collect();
647
648        tracing::debug!(
649            try_to_send_id,
650            "Submitting package\n Pkg tx hexs: {:?}",
651            if env::var("DBG_PACKAGE_HEX").is_ok() {
652                package
653                    .iter()
654                    .map(|tx| hex::encode(bitcoin::consensus::serialize(tx)))
655                    .collect::<Vec<_>>()
656            } else {
657                vec!["use DBG_PACKAGE_HEX=1 to print the package as hex".into()]
658            }
659        );
660
661        // Save the effective fee rate before attempting to send
662        // This ensures that even if the send fails, we track the attempt
663        // so the 10-block stuck logic can trigger a bump
664        self.db
665            .update_effective_fee_rate(None, try_to_send_id, fee_rate, current_tip_height)
666            .await
667            .wrap_err("Failed to update effective fee rate")?;
668
669        // Update sending state to submitting_package
670        let _ = self
671            .db
672            .update_tx_debug_sending_state(try_to_send_id, "submitting_package", true)
673            .await;
674
675        let submit_result = self
676            .rpc
677            .submit_package(&package_refs, Some(Amount::ZERO), None)
678            .await
679            .wrap_err("Failed to submit package")?;
680
681        // If tx_results is empty, it means the txs were already accepted by the network.
682        if submit_result.tx_results.is_empty() {
683            return Ok(());
684        }
685
686        let mut package_errors = Vec::new();
687        let mut has_replacement_error = false;
688
689        for result in submit_result.tx_results.into_values() {
690            if let PackageTransactionResult::Failure { error, .. } = result {
691                if crate::rpc_errors::is_rejecting_replacement_error(&error) {
692                    has_replacement_error = true;
693                }
694                package_errors.push(error);
695            }
696        }
697
698        if has_replacement_error {
699            tracing::debug!(
700                try_to_send_id,
701                "Package tx rejected (tx already in mempool): {:?}",
702                package_errors
703            );
704            return Ok(());
705        }
706
707        if !package_errors.is_empty() {
708            return Err(SendTxError::Other(eyre!(
709                "Failed to submit package: {:?}, package: {:?}",
710                package_errors,
711                package_refs
712                    .iter()
713                    .map(|tx| hex::encode(bitcoin::consensus::serialize(tx)))
714                    .collect::<Vec<_>>()
715            )));
716        }
717
718        Ok(())
719    }
720}