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::{SpendableInputInfo, TxSender, TxSenderDatabase, TxSenderSigner, TxSenderTxBuilder};
22use bitcoin::absolute::LockTime;
23use bitcoin::transaction::Version;
24use bitcoin::{Amount, FeeRate, OutPoint, Transaction, TxOut, Weight};
25use bitcoincore_rpc::json::FundRawTransactionOptions;
26use bitcoincore_rpc::{PackageTransactionResult, RpcApi};
27use clementine_errors::{BitcoinRPCError, BridgeError, ResultExt as _, SendTxError};
28use clementine_primitives::MIN_TAPROOT_AMOUNT;
29use clementine_utils::{FeePayingType, TxMetadata};
30use eyre::{eyre, Context};
31use std::collections::HashSet;
32use std::env;
33
34impl<S, D, B> TxSender<S, D, B>
35where
36    S: TxSenderSigner,
37    D: TxSenderDatabase,
38    B: TxSenderTxBuilder,
39{
40    /// Creates and broadcasts a new "fee payer" UTXO to be used for CPFP
41    /// transactions.
42    ///
43    /// This function is called when a CPFP attempt fails due to insufficient funds
44    /// in the existing confirmed fee payer UTXOs associated with a transaction (`bumped_id`).
45    /// It calculates the required fee based on the parent transaction (`tx`) and the current
46    /// `fee_rate`, adding a buffer (2x required fee + dust limit) to handle potential fee spikes.
47    /// It then sends funds to the `TxSender`'s own signer address using the RPC's
48    /// `send_to_address` and saves the resulting UTXO information (`outpoint`, `amount`)
49    /// to the database, linking it to the `bumped_id`.
50    ///
51    /// # Arguments
52    /// * `bumped_id` - The database ID of the parent transaction requiring the fee bump.
53    /// * `tx` - The parent transaction itself.
54    /// * `fee_rate` - The target fee rate for the CPFP package.
55    /// * `total_fee_payer_amount` - The sum of amounts in currently available confirmed fee payer UTXOs.
56    /// * `fee_payer_utxos_len` - The number of currently available confirmed fee payer UTXOs.
57    async fn create_fee_payer_utxo(
58        &self,
59        bumped_id: u32,
60        dbtx: Option<&mut D::Transaction>,
61        tx: &Transaction,
62        fee_rate: FeeRate,
63        total_fee_payer_amount: Amount,
64        fee_payer_utxos_len: usize,
65    ) -> Result<()> {
66        tracing::debug!(
67            "Creating fee payer UTXO for txid {} with bump id {}",
68            &tx.compute_txid().to_string(),
69            bumped_id
70        );
71        let required_fee = Self::calculate_required_fee(
72            tx.weight(),
73            fee_payer_utxos_len + 1,
74            fee_rate,
75            FeePayingType::CPFP,
76        )?;
77
78        // Aggressively add 2x required fee to the total amount to account for sudden spikes
79        // 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
80        // if fees increase the utxo should still be sufficient to fund the tx with high probability
81        // leftover fees will get sent back to wallet with a change output in fn create_child_tx
82        let new_total_fee_needed = required_fee
83            .checked_mul(2)
84            .and_then(|fee| fee.checked_add(MIN_TAPROOT_AMOUNT));
85        if new_total_fee_needed.is_none() {
86            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());
87        }
88        let new_fee_payer_amount =
89            new_total_fee_needed.and_then(|fee| fee.checked_sub(total_fee_payer_amount));
90
91        let new_fee_payer_amount = match new_fee_payer_amount {
92            Some(fee) => fee,
93            // if underflow, no new fee payer utxo is needed, log it anyway in case its a bug
94            None => {
95                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);
96                return Ok(());
97            }
98        };
99
100        tracing::debug!(
101            "Creating fee payer UTXO with amount {} ({} sat/vb)",
102            new_fee_payer_amount,
103            fee_rate
104        );
105
106        let fee_payer_tx = Transaction {
107            version: Version::TWO,
108            lock_time: LockTime::ZERO,
109            input: vec![],
110            output: vec![TxOut {
111                value: new_fee_payer_amount,
112                script_pubkey: self.signer.address().script_pubkey(),
113            }],
114        };
115
116        // Manually serialize in legacy format for 0-input transactions
117        // Because fund_raw_transaction RPC gives deserialization error for 0-input transactions with segwit flag
118        // but in the end fund_raw_transaction returns a segwit transaction after adding inputs
119        let fee_payer_bytes = if fee_payer_tx.input.is_empty() {
120            use bitcoin::consensus::Encodable;
121            let mut buf = Vec::new();
122            // Serialize version
123            fee_payer_tx
124                .version
125                .consensus_encode(&mut buf)
126                .expect("Failed to serialize version");
127            fee_payer_tx
128                .input
129                .consensus_encode(&mut buf)
130                .expect("Failed to serialize inputs");
131            fee_payer_tx
132                .output
133                .consensus_encode(&mut buf)
134                .expect("Failed to serialize outputs");
135            // Serialize locktime
136            fee_payer_tx
137                .lock_time
138                .consensus_encode(&mut buf)
139                .expect("Failed to serialize locktime");
140
141            buf
142        } else {
143            bitcoin::consensus::encode::serialize(&fee_payer_tx)
144        };
145
146        let funded_fee_payer_tx = self
147            .rpc
148            .fund_raw_transaction(
149                &fee_payer_bytes,
150                Some(&FundRawTransactionOptions {
151                    add_inputs: Some(true),
152                    change_address: None,
153                    change_position: None,
154                    change_type: None,
155                    include_watching: None,
156                    lock_unspents: None,
157                    fee_rate: Some(Amount::from_sat(fee_rate.to_sat_per_vb_ceil() * 1000)),
158                    subtract_fee_from_outputs: None,
159                    replaceable: Some(true),
160                    conf_target: None,
161                    estimate_mode: None,
162                }),
163                None,
164            )
165            .await
166            .wrap_err("Failed to fund cpfp fee payer tx")?
167            .hex;
168
169        let signed_fee_payer_tx: Transaction = bitcoin::consensus::deserialize(
170            &self
171                .rpc
172                .sign_raw_transaction_with_wallet(&funded_fee_payer_tx, None, None)
173                .await
174                .wrap_err("Failed to sign funded tx through bitcoin RPC")?
175                .hex,
176        )
177        .wrap_err("Failed to deserialize signed tx")?;
178
179        let outpoint_vout = signed_fee_payer_tx
180            .output
181            .iter()
182            .position(|o| {
183                o.value == new_fee_payer_amount
184                    && o.script_pubkey == self.signer.address().script_pubkey()
185            })
186            .ok_or(eyre!("Failed to find outpoint vout"))?;
187
188        self.rpc
189            .send_raw_transaction(&signed_fee_payer_tx)
190            .await
191            .wrap_err("Failed to send signed fee payer tx")?;
192
193        self.db
194            .save_fee_payer_tx(
195                dbtx,
196                bumped_id,
197                signed_fee_payer_tx.compute_txid(),
198                outpoint_vout as u32,
199                new_fee_payer_amount,
200                None,
201            )
202            .await
203            .map_to_eyre()?;
204
205        Ok(())
206    }
207
208    /// Creates a Child-Pays-For-Parent (CPFP) child transaction.
209    ///
210    /// This transaction spends:
211    /// 1.  The designated "P2A anchor" output of the parent transaction (`p2a_anchor`).
212    /// 2.  One or more confirmed "fee payer" UTXOs (`fee_payer_utxos`) controlled by the `signer`.
213    ///
214    /// It calculates the total fee required (`required_fee`) to make the combined parent + child
215    /// package attractive to miners at the target `fee_rate`. The `required_fee` is paid entirely
216    /// by this child transaction.
217    ///
218    /// The remaining value (total input value - `required_fee`) is sent to the `change_address`.
219    ///
220    /// # Signing
221    /// We sign the input spending the P2A anchor and all fee payer UTXOs.
222    ///
223    /// # Returns
224    /// The constructed and partially signed child transaction.
225    async fn create_child_tx(
226        &self,
227        p2a_anchor: OutPoint,
228        anchor_sat: Amount,
229        fee_payer_utxos: Vec<B::SpendableInput>,
230        parent_tx_size: Weight,
231        fee_rate: FeeRate,
232    ) -> Result<Transaction> {
233        let required_fee = Self::calculate_required_fee(
234            parent_tx_size,
235            fee_payer_utxos.len(),
236            fee_rate,
237            FeePayingType::CPFP,
238        )?;
239
240        let change_address = self
241            .rpc
242            .get_new_wallet_address()
243            .await
244            .wrap_err("Failed to get new wallet address")?;
245
246        let total_fee_payer_amount = fee_payer_utxos
247            .iter()
248            .map(|utxo| utxo.get_prevout().value)
249            .sum::<Amount>()
250            + anchor_sat;
251
252        if change_address.script_pubkey().minimal_non_dust() + required_fee > total_fee_payer_amount
253        {
254            return Err(SendTxError::InsufficientFeePayerAmount);
255        }
256
257        // Delegate to the TxBuilder's static method
258        B::build_child_tx(
259            p2a_anchor,
260            anchor_sat,
261            fee_payer_utxos,
262            change_address,
263            required_fee,
264            &self.signer,
265        )
266        .map_err(|e| SendTxError::Other(e.into()))
267    }
268
269    /// Creates a transaction package for CPFP submission.
270    ///
271    /// Finds the P2A anchor output in the parent transaction (`tx`), then constructs
272    /// the child transaction using `create_child_tx`.
273    ///
274    /// # Returns
275    ///
276    /// - [`Vec<Transaction>`]: Parent transaction followed by the child
277    ///   transaction ready for submission via the `submitpackage` RPC.
278    async fn create_package(
279        &self,
280        tx: Transaction,
281        fee_rate: FeeRate,
282        fee_payer_utxos: Vec<B::SpendableInput>,
283    ) -> Result<Vec<Transaction>> {
284        let txid = tx.compute_txid();
285        let p2a_vout = self
286            .find_p2a_vout(&tx)
287            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
288        let anchor_sat = tx.output[p2a_vout].value;
289
290        let child_tx = self
291            .create_child_tx(
292                OutPoint {
293                    txid,
294                    vout: p2a_vout as u32,
295                },
296                anchor_sat,
297                fee_payer_utxos,
298                tx.weight(),
299                fee_rate,
300            )
301            .await?;
302
303        Ok(vec![tx, child_tx])
304    }
305
306    /// Retrieves confirmed fee payer UTXOs associated with a specific send attempt.
307    ///
308    /// Queries the database for UTXOs linked to `try_to_send_id` that are marked as confirmed.
309    /// These UTXOs are controlled by the `TxSender`'s `signer` and are intended to be
310    /// spent by a CPFP child transaction.
311    ///
312    /// # Returns
313    ///
314    /// - [`Vec<B::SpendableInput>`]: [`B::SpendableInput`]s of the confirmed fee payer
315    ///   UTXOs that are ready to be included as inputs in the CPFP child tx.
316    async fn get_confirmed_fee_payer_utxos(
317        &self,
318        try_to_send_id: u32,
319    ) -> Result<Vec<B::SpendableInput>> {
320        let utxos = self
321            .db
322            .get_confirmed_fee_payer_utxos(None, try_to_send_id)
323            .await
324            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
325        Ok(B::utxos_to_spendable_inputs(utxos, self.signer.address()))
326    }
327
328    /// Attempts to bump the fees of unconfirmed "fee payer" UTXOs using RBF.
329    ///
330    /// Fee payer UTXOs are created to fund CPFP child transactions. However, these
331    /// fee payer creation transactions might themselves get stuck due to low fees.
332    /// This function identifies such unconfirmed fee payer transactions associated with
333    /// a parent transaction (`bumped_id`) and attempts to RBF them using the provided `fee_rate`.
334    ///
335    /// This ensures the fee payer UTXOs confirm quickly, making them available to be spent
336    /// by the actual CPFP child transaction.
337    ///
338    /// # Arguments
339    /// * `fee_rate` - The target fee rate for bumping the fee payer transactions.
340    #[tracing::instrument(skip_all, fields(fee_rate))]
341    pub async fn bump_fees_of_unconfirmed_fee_payer_txs(&self, fee_rate: FeeRate) -> Result<()> {
342        let bumpable_txs = self
343            .db
344            .get_all_unconfirmed_fee_payer_txs(None)
345            .await
346            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
347        let mut not_evicted_ids = HashSet::new();
348        let mut all_parent_ids = HashSet::new();
349
350        for (id, try_to_send_id, fee_payer_txid, vout, amount, replacement_of_id) in bumpable_txs {
351            tracing::debug!(
352                "Bumping fee for fee payer tx {} for try to send id {} for fee rate {}",
353                fee_payer_txid,
354                try_to_send_id,
355                fee_rate
356            );
357            let parent_id = replacement_of_id.unwrap_or(id);
358            all_parent_ids.insert(parent_id);
359
360            match self.rpc.get_mempool_entry(&fee_payer_txid).await {
361                Ok(info) => {
362                    not_evicted_ids.insert(parent_id);
363                    // if it has descendants, it cannot be bumped, or if it was bumped recently, we should not bump it again
364                    if info.descendant_count > 1
365                        || std::time::SystemTime::now()
366                            .duration_since(std::time::UNIX_EPOCH)
367                            .unwrap()
368                            .as_secs()
369                            .saturating_sub(info.time)
370                            < self.tx_sender_limits.cpfp_fee_payer_bump_wait_time_seconds
371                    {
372                        continue;
373                    }
374                }
375                Err(e) => {
376                    // If not in mempool we should ignore, it was either evicted or replaced by a bumped feepayer tx
377                    // give an error if the error is not "Transaction not in mempool"
378                    if !e.to_string().contains("Transaction not in mempool") {
379                        return Err(
380                            eyre!("Failed to get mempool entry for {fee_payer_txid}: {e}").into(),
381                        );
382                    }
383                    // get_transaction only returns if tx is wallet owned, it should not be an issue here as if it is not wallet owned,
384                    // for example if wallet was changed and txsender restarted, it cannot be bumped anyway
385                    if let Ok(tx_info) = self.rpc.get_transaction(&fee_payer_txid, None).await {
386                        if tx_info.info.blockhash.is_some() && tx_info.info.confirmations > 0 {
387                            not_evicted_ids.insert(parent_id);
388                        }
389                    }
390                    continue;
391                }
392            }
393
394            match self
395                .rpc
396                .bump_fee_with_fee_rate(fee_payer_txid, fee_rate)
397                .await
398            {
399                Ok(new_txid) => {
400                    if new_txid != fee_payer_txid {
401                        self.db
402                            .save_fee_payer_tx(
403                                None,
404                                try_to_send_id,
405                                new_txid,
406                                vout,
407                                amount,
408                                Some(parent_id),
409                            )
410                            .await
411                            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
412                    } else {
413                        tracing::trace!(
414                            "Fee payer tx {} has enough fee, no need to bump",
415                            fee_payer_txid
416                        );
417                    }
418                }
419                Err(e) => match e {
420                    BitcoinRPCError::TransactionAlreadyInBlock(block_hash) => {
421                        tracing::debug!(
422                            "Fee payer tx {} is already in block {}, skipping",
423                            fee_payer_txid,
424                            block_hash
425                        );
426                        continue;
427                    }
428                    BitcoinRPCError::BumpFeeUTXOSpent(outpoint) => {
429                        tracing::debug!(
430                            "Fee payer tx {} is already onchain, skipping: {:?}",
431                            fee_payer_txid,
432                            outpoint
433                        );
434                        continue;
435                    }
436                    _ => {
437                        tracing::warn!(
438                            "Failed to bump fee the fee payer tx {} with error {e}, skipping",
439                            fee_payer_txid
440                        );
441                        continue;
442                    }
443                },
444            }
445        }
446
447        for parent_id in all_parent_ids {
448            if !not_evicted_ids.contains(&parent_id) {
449                self.db
450                    .mark_fee_payer_utxo_as_evicted(None, parent_id)
451                    .await
452                    .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
453            }
454        }
455        Ok(())
456    }
457
458    /// Sends a transaction using the Child-Pays-For-Parent (CPFP) strategy.
459    ///
460    /// # Logic:
461    /// 1.  **Check Unconfirmed Fee Payers:** Ensures no unconfirmed fee payer UTXOs exist
462    ///     for this `try_to_send_id`. If they do, skips this transaction for now
463    ///     as they need to confirm before being spendable by the child.
464    /// 2.  **Get Confirmed Fee Payers:** Retrieves the available confirmed fee payer UTXOs.
465    /// 3.  **Create Package:** Calls `create_package` to build the `vec![parent_tx, child_tx]`.
466    ///     The `child_tx` spends the parent's anchor output and the fee payer UTXOs, paying
467    ///     a fee calculated for the whole package.
468    /// 4.  **Test Mempool Accept (Not implemented right now as testmempoolaccept didn't support TRUC package submission #1011):**
469    ///     Uses `testmempoolaccept` RPC to check if the package is likely to be accepted by the network before submitting.
470    /// 5.  **Submit Package:** Uses the `submitpackage` RPC to atomically submit the parent
471    ///     and child transactions. Bitcoin Core evaluates the fee rate of the package together.
472    /// 6.  **Handle Results:** Checks the `submitpackage` result. If successful or already in
473    ///     mempool, updates the effective fee rate in the database. If failed, logs an error.
474    ///
475    /// # Arguments
476    /// * `try_to_send_id` - The database ID tracking this send attempt.
477    /// * `tx` - The parent transaction requiring the fee bump.
478    /// * `tx_metadata` - Optional metadata associated with the transaction.
479    /// * `fee_rate` - The target fee rate for the CPFP package.
480    /// * `current_tip_height` - The current height of the tip of the chain.
481    #[tracing::instrument(skip_all, fields(try_to_send_id, tx_meta=?tx_metadata))]
482    pub async fn send_cpfp_tx(
483        &self,
484        try_to_send_id: u32,
485        tx: Transaction,
486        tx_metadata: Option<TxMetadata>,
487        fee_rate: FeeRate,
488        current_tip_height: u32,
489    ) -> Result<()> {
490        let unconfirmed = self
491            .db
492            .get_unconfirmed_fee_payer_txs(None, try_to_send_id)
493            .await
494            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
495        if !unconfirmed.is_empty() {
496            // Log that we're waiting for unconfirmed UTXOs
497            tracing::debug!(
498                try_to_send_id,
499                "Waiting for {} UTXOs to confirm",
500                unconfirmed.len()
501            );
502
503            let _ = self
504                .db
505                .update_tx_debug_sending_state(
506                    try_to_send_id,
507                    "waiting_for_utxo_confirmation",
508                    true,
509                )
510                .await;
511            return Ok(());
512        }
513
514        let confirmed = self.get_confirmed_fee_payer_utxos(try_to_send_id).await?;
515        let total_amount: Amount = confirmed.iter().map(|u| u.get_prevout().value).sum();
516
517        let _ = self
518            .db
519            .update_tx_debug_sending_state(try_to_send_id, "creating_package", true)
520            .await;
521
522        let package = match self
523            .create_package(tx.clone(), fee_rate, confirmed.clone())
524            .await
525        {
526            Ok(p) => p,
527            Err(SendTxError::InsufficientFeePayerAmount) => {
528                self.create_fee_payer_utxo(
529                    try_to_send_id,
530                    None,
531                    &tx,
532                    fee_rate,
533                    total_amount,
534                    confirmed.len(),
535                )
536                .await?;
537                let _ = self
538                    .db
539                    .update_tx_debug_sending_state(
540                        try_to_send_id,
541                        "waiting_for_fee_payer_utxos",
542                        true,
543                    )
544                    .await;
545                return Ok(());
546            }
547            Err(e) => {
548                tracing::error!(try_to_send_id, "Failed to create CPFP package: {:?}", e);
549                return Err(e);
550            }
551        };
552
553        let package_refs: Vec<&Transaction> = package.iter().collect();
554
555        tracing::debug!(
556            try_to_send_id,
557            "Submitting package\n Pkg tx hexs: {:?}",
558            if env::var("DBG_PACKAGE_HEX").is_ok() {
559                package
560                    .iter()
561                    .map(|tx| hex::encode(bitcoin::consensus::serialize(tx)))
562                    .collect::<Vec<_>>()
563            } else {
564                vec!["use DBG_PACKAGE_HEX=1 to print the package as hex".into()]
565            }
566        );
567
568        // Save the effective fee rate before attempting to send
569        // This ensures that even if the send fails, we track the attempt
570        // so the 10-block stuck logic can trigger a bump
571        self.db
572            .update_effective_fee_rate(None, try_to_send_id, fee_rate, current_tip_height)
573            .await
574            .wrap_err("Failed to update effective fee rate")?;
575
576        // Update sending state to submitting_package
577        let _ = self
578            .db
579            .update_tx_debug_sending_state(try_to_send_id, "submitting_package", true)
580            .await;
581
582        let submit_result = self
583            .rpc
584            .submit_package(&package_refs, Some(Amount::ZERO), None)
585            .await
586            .wrap_err("Failed to submit package")?;
587
588        // If tx_results is empty, it means the txs were already accepted by the network.
589        if submit_result.tx_results.is_empty() {
590            return Ok(());
591        }
592
593        for (_txid, result) in submit_result.tx_results {
594            if let PackageTransactionResult::Failure { error, .. } = result {
595                tracing::error!(
596                    try_to_send_id,
597                    "Error submitting package: {:?}, package: {:?}",
598                    error,
599                    package_refs
600                        .iter()
601                        .map(|tx| hex::encode(bitcoin::consensus::serialize(tx)))
602                        .collect::<Vec<_>>()
603                );
604            }
605        }
606
607        Ok(())
608    }
609}