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::{BridgeError, ResultExt as _, SendTxError};
28use clementine_primitives::MIN_TAPROOT_AMOUNT;
29use clementine_utils::{FeePayingType, TxMetadata};
30use eyre::{eyre, Context};
31use std::collections::HashSet;
32
33impl<S, D, B> TxSender<S, D, B>
34where
35    S: TxSenderSigner,
36    D: TxSenderDatabase,
37    B: TxSenderTxBuilder,
38{
39    /// Creates and broadcasts a new "fee payer" UTXO to be used for CPFP
40    /// transactions.
41    ///
42    /// This function is called when a CPFP attempt fails due to insufficient funds
43    /// in the existing confirmed fee payer UTXOs associated with a transaction (`bumped_id`).
44    /// It calculates the required fee based on the parent transaction (`tx`) and the current
45    /// `fee_rate`, adding a buffer (2x required fee + dust limit) to handle potential fee spikes.
46    /// It then sends funds to the `TxSender`'s own signer address using the RPC's
47    /// `send_to_address` and saves the resulting UTXO information (`outpoint`, `amount`)
48    /// to the database, linking it to the `bumped_id`.
49    ///
50    /// # Arguments
51    /// * `bumped_id` - The database ID of the parent transaction requiring the fee bump.
52    /// * `tx` - The parent transaction itself.
53    /// * `fee_rate` - The target fee rate for the CPFP package.
54    /// * `total_fee_payer_amount` - The sum of amounts in currently available confirmed fee payer UTXOs.
55    /// * `fee_payer_utxos_len` - The number of currently available confirmed fee payer UTXOs.
56    async fn create_fee_payer_utxo(
57        &self,
58        bumped_id: u32,
59        dbtx: Option<&mut D::Transaction>,
60        tx: &Transaction,
61        fee_rate: FeeRate,
62        total_fee_payer_amount: Amount,
63        fee_payer_utxos_len: usize,
64    ) -> Result<()> {
65        tracing::debug!(
66            "Creating fee payer UTXO for txid {} with bump id {}",
67            &tx.compute_txid().to_string(),
68            bumped_id
69        );
70        let required_fee = Self::calculate_required_fee(
71            tx.weight(),
72            fee_payer_utxos_len + 1,
73            fee_rate,
74            FeePayingType::CPFP,
75        )?;
76
77        // Aggressively add 2x required fee to the total amount to account for sudden spikes
78        // 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
79        // if fees increase the utxo should still be sufficient to fund the tx with high probability
80        // leftover fees will get sent back to wallet with a change output in fn create_child_tx
81        let new_total_fee_needed = required_fee
82            .checked_mul(2)
83            .and_then(|fee| fee.checked_add(MIN_TAPROOT_AMOUNT));
84        if new_total_fee_needed.is_none() {
85            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());
86        }
87        let new_fee_payer_amount =
88            new_total_fee_needed.and_then(|fee| fee.checked_sub(total_fee_payer_amount));
89
90        let new_fee_payer_amount = match new_fee_payer_amount {
91            Some(fee) => fee,
92            // if underflow, no new fee payer utxo is needed, log it anyway in case its a bug
93            None => {
94                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);
95                return Ok(());
96            }
97        };
98
99        tracing::debug!(
100            "Creating fee payer UTXO with amount {} ({} sat/vb)",
101            new_fee_payer_amount,
102            fee_rate
103        );
104
105        let fee_payer_tx = Transaction {
106            version: Version::TWO,
107            lock_time: LockTime::ZERO,
108            input: vec![],
109            output: vec![TxOut {
110                value: new_fee_payer_amount,
111                script_pubkey: self.signer.address().script_pubkey(),
112            }],
113        };
114
115        // Manually serialize in legacy format for 0-input transactions
116        // Because fund_raw_transaction RPC gives deserialization error for 0-input transactions with segwit flag
117        // but in the end fund_raw_transaction returns a segwit transaction after adding inputs
118        let fee_payer_bytes = if fee_payer_tx.input.is_empty() {
119            use bitcoin::consensus::Encodable;
120            let mut buf = Vec::new();
121            // Serialize version
122            fee_payer_tx
123                .version
124                .consensus_encode(&mut buf)
125                .expect("Failed to serialize version");
126            fee_payer_tx
127                .input
128                .consensus_encode(&mut buf)
129                .expect("Failed to serialize inputs");
130            fee_payer_tx
131                .output
132                .consensus_encode(&mut buf)
133                .expect("Failed to serialize outputs");
134            // Serialize locktime
135            fee_payer_tx
136                .lock_time
137                .consensus_encode(&mut buf)
138                .expect("Failed to serialize locktime");
139
140            buf
141        } else {
142            bitcoin::consensus::encode::serialize(&fee_payer_tx)
143        };
144
145        let funded_fee_payer_tx = self
146            .rpc
147            .fund_raw_transaction(
148                &fee_payer_bytes,
149                Some(&FundRawTransactionOptions {
150                    add_inputs: Some(true),
151                    change_address: None,
152                    change_position: None,
153                    change_type: None,
154                    include_watching: None,
155                    lock_unspents: None,
156                    fee_rate: Some(Amount::from_sat(fee_rate.to_sat_per_vb_ceil() * 1000)),
157                    subtract_fee_from_outputs: None,
158                    replaceable: Some(true),
159                    conf_target: None,
160                    estimate_mode: None,
161                }),
162                None,
163            )
164            .await
165            .wrap_err("Failed to fund cpfp fee payer tx")?
166            .hex;
167
168        let signed_fee_payer_tx: Transaction = bitcoin::consensus::deserialize(
169            &self
170                .rpc
171                .sign_raw_transaction_with_wallet(&funded_fee_payer_tx, None, None)
172                .await
173                .wrap_err("Failed to sign funded tx through bitcoin RPC")?
174                .hex,
175        )
176        .wrap_err("Failed to deserialize signed tx")?;
177
178        let outpoint_vout = signed_fee_payer_tx
179            .output
180            .iter()
181            .position(|o| {
182                o.value == new_fee_payer_amount
183                    && o.script_pubkey == self.signer.address().script_pubkey()
184            })
185            .ok_or(eyre!("Failed to find outpoint vout"))?;
186
187        self.rpc
188            .send_raw_transaction(&signed_fee_payer_tx)
189            .await
190            .wrap_err("Failed to send signed fee payer tx")?;
191
192        self.db
193            .save_fee_payer_tx(
194                dbtx,
195                None,
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_address(None, None)
243            .await
244            .wrap_err("Failed to get new wallet address")?
245            .assume_checked();
246
247        let total_fee_payer_amount = fee_payer_utxos
248            .iter()
249            .map(|utxo| utxo.get_prevout().value)
250            .sum::<Amount>()
251            + anchor_sat;
252
253        if change_address.script_pubkey().minimal_non_dust() + required_fee > total_fee_payer_amount
254        {
255            return Err(SendTxError::InsufficientFeePayerAmount);
256        }
257
258        // Delegate to the TxBuilder's static method
259        B::build_child_tx(
260            p2a_anchor,
261            anchor_sat,
262            fee_payer_utxos,
263            change_address,
264            required_fee,
265            &self.signer,
266        )
267        .map_err(|e| SendTxError::Other(e.into()))
268    }
269
270    /// Creates a transaction package for CPFP submission.
271    ///
272    /// Finds the P2A anchor output in the parent transaction (`tx`), then constructs
273    /// the child transaction using `create_child_tx`.
274    ///
275    /// # Returns
276    ///
277    /// - [`Vec<Transaction>`]: Parent transaction followed by the child
278    ///   transaction ready for submission via the `submitpackage` RPC.
279    async fn create_package(
280        &self,
281        tx: Transaction,
282        fee_rate: FeeRate,
283        fee_payer_utxos: Vec<B::SpendableInput>,
284    ) -> Result<Vec<Transaction>> {
285        let txid = tx.compute_txid();
286        let p2a_vout = self
287            .find_p2a_vout(&tx)
288            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
289        let anchor_sat = tx.output[p2a_vout].value;
290
291        let child_tx = self
292            .create_child_tx(
293                OutPoint {
294                    txid,
295                    vout: p2a_vout as u32,
296                },
297                anchor_sat,
298                fee_payer_utxos,
299                tx.weight(),
300                fee_rate,
301            )
302            .await?;
303
304        Ok(vec![tx, child_tx])
305    }
306
307    async fn get_confirmed_fee_payer_utxos(
308        &self,
309        try_to_send_id: u32,
310    ) -> Result<Vec<B::SpendableInput>> {
311        let utxos = self
312            .db
313            .get_confirmed_fee_payer_utxos(None, try_to_send_id)
314            .await
315            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
316        Ok(B::utxos_to_spendable_inputs(utxos, self.signer.address()))
317    }
318
319    pub async fn bump_fees_of_unconfirmed_fee_payer_txs(&self, fee_rate: FeeRate) -> Result<()> {
320        let bumpable_txs = self
321            .db
322            .get_all_unconfirmed_fee_payer_txs(None)
323            .await
324            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
325        let mut not_evicted_ids = HashSet::new();
326        let mut all_parent_ids = HashSet::new();
327
328        for (id, try_to_send_id, txid, vout, amount, replacement_of_id) in bumpable_txs {
329            let parent_id = replacement_of_id.unwrap_or(id);
330            all_parent_ids.insert(parent_id);
331
332            match self.rpc.get_mempool_entry(&txid).await {
333                Ok(info) => {
334                    not_evicted_ids.insert(parent_id);
335                    if info.descendant_count > 1
336                        || std::time::SystemTime::now()
337                            .duration_since(std::time::UNIX_EPOCH)
338                            .unwrap()
339                            .as_secs()
340                            .saturating_sub(info.time)
341                            < self.tx_sender_limits.cpfp_fee_payer_bump_wait_time_seconds
342                    {
343                        continue;
344                    }
345                }
346                Err(e) => {
347                    if !e.to_string().contains("Transaction not in mempool") {
348                        return Err(eyre!("Failed to get mempool entry for {txid}: {e}").into());
349                    }
350                    if let Ok(tx_info) = self.rpc.get_transaction(&txid, None).await {
351                        if tx_info.info.blockhash.is_some() && tx_info.info.confirmations > 0 {
352                            not_evicted_ids.insert(parent_id);
353                        }
354                    }
355                    continue;
356                }
357            }
358
359            if let Ok(new_txid) = self.rpc.bump_fee_with_fee_rate(txid, fee_rate).await {
360                if new_txid != txid {
361                    self.db
362                        .save_fee_payer_tx(
363                            None,
364                            Some(try_to_send_id),
365                            0, /* bumped_id not used here? */
366                            new_txid,
367                            vout,
368                            amount,
369                            Some(parent_id),
370                        )
371                        .await
372                        .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
373                }
374            }
375        }
376
377        for parent_id in all_parent_ids {
378            if !not_evicted_ids.contains(&parent_id) {
379                self.db
380                    .mark_fee_payer_utxo_as_evicted(None, parent_id)
381                    .await
382                    .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
383            }
384        }
385        Ok(())
386    }
387
388    pub async fn send_cpfp_tx(
389        &self,
390        try_to_send_id: u32,
391        tx: Transaction,
392        _tx_metadata: Option<TxMetadata>,
393        fee_rate: FeeRate,
394    ) -> Result<()> {
395        let unconfirmed = self
396            .db
397            .get_unconfirmed_fee_payer_txs(None, try_to_send_id)
398            .await
399            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
400        if !unconfirmed.is_empty() {
401            let _ = self
402                .db
403                .update_tx_debug_sending_state(
404                    try_to_send_id,
405                    "waiting_for_utxo_confirmation",
406                    true,
407                )
408                .await;
409            return Ok(());
410        }
411
412        let confirmed = self.get_confirmed_fee_payer_utxos(try_to_send_id).await?;
413        let total_amount: Amount = confirmed.iter().map(|u| u.get_prevout().value).sum();
414
415        let package = match self
416            .create_package(tx.clone(), fee_rate, confirmed.clone())
417            .await
418        {
419            Ok(p) => p,
420            Err(SendTxError::InsufficientFeePayerAmount) => {
421                self.create_fee_payer_utxo(
422                    try_to_send_id,
423                    None,
424                    &tx,
425                    fee_rate,
426                    total_amount,
427                    confirmed.len(),
428                )
429                .await?;
430                let _ = self
431                    .db
432                    .update_tx_debug_sending_state(
433                        try_to_send_id,
434                        "waiting_for_fee_payer_utxos",
435                        true,
436                    )
437                    .await;
438                return Ok(());
439            }
440            Err(e) => return Err(e),
441        };
442
443        let package_refs: Vec<&Transaction> = package.iter().collect();
444        let submit_result = self
445            .rpc
446            .submit_package(&package_refs, Some(Amount::ZERO), None)
447            .await
448            .wrap_err("Failed to submit package")?;
449
450        if submit_result.tx_results.is_empty() {
451            return Ok(());
452        }
453
454        for (_txid, result) in submit_result.tx_results {
455            if let PackageTransactionResult::Failure { error, .. } = result {
456                tracing::error!(try_to_send_id, "Error submitting package: {:?}", error);
457                return Ok(());
458            }
459        }
460
461        self.db
462            .update_effective_fee_rate(None, try_to_send_id, fee_rate)
463            .await
464            .map_err(|e: BridgeError| SendTxError::Other(e.into()))?;
465        Ok(())
466    }
467}