clementine_core/
tx_sender_ext.rs

1use crate::builder;
2use crate::builder::script::SpendPath;
3use crate::builder::sighash::TapTweakData;
4use crate::builder::transaction::input::SpendableTxIn;
5use crate::builder::transaction::output::UnspentTxOut;
6use crate::builder::transaction::TxHandlerBuilder;
7use crate::database::Database;
8use crate::rpc;
9use crate::rpc::clementine::{NormalSignatureKind, TxDebugInfo};
10use bitcoin::hashes::Hash;
11use bitcoin::{Address, Amount, OutPoint, TxOut, Txid};
12use clementine_errors::{BridgeError, ResultExt as _, RoundIndex, TransactionType};
13use clementine_primitives::NON_STANDARD_V3;
14use clementine_tx_sender::client::TxSenderClient;
15use clementine_tx_sender::{TxSender, TxSenderSigner, TxSenderTxBuilder, DEFAULT_SEQUENCE};
16use clementine_utils::FeePayingType;
17use tonic::async_trait;
18
19/// Core's implementation of TxSenderTxBuilder using SpendableTxIn and TxHandlerBuilder.
20///
21/// This struct provides static methods for building CPFP child transactions
22/// using the core builder module's SpendableTxIn type and TxHandlerBuilder.
23#[derive(Debug, Clone, Copy)]
24pub struct CoreTxBuilder;
25
26impl TxSenderTxBuilder for CoreTxBuilder {
27    type SpendableInput = SpendableTxIn;
28
29    fn build_child_tx<S: TxSenderSigner>(
30        p2a_anchor: OutPoint,
31        anchor_sat: Amount,
32        fee_payer_utxos: Vec<Self::SpendableInput>,
33        change_address: Address,
34        required_fee: Amount,
35        signer: &S,
36    ) -> Result<bitcoin::Transaction, BridgeError> {
37        let total_fee_payer_amount = fee_payer_utxos
38            .iter()
39            .map(|utxo| utxo.get_prevout().value)
40            .sum::<Amount>()
41            + anchor_sat;
42
43        let change_amount = total_fee_payer_amount
44            .checked_sub(required_fee)
45            .ok_or_else(|| {
46                BridgeError::Eyre(eyre::eyre!(
47                    "Required fee {} exceeds total amount {}",
48                    required_fee,
49                    total_fee_payer_amount
50                ))
51            })?;
52
53        let mut builder = TxHandlerBuilder::new(TransactionType::Dummy)
54            .with_version(NON_STANDARD_V3)
55            .add_input(
56                NormalSignatureKind::OperatorSighashDefault,
57                SpendableTxIn::new_partial(
58                    p2a_anchor,
59                    builder::transaction::anchor_output(anchor_sat),
60                ),
61                SpendPath::Unknown,
62                DEFAULT_SEQUENCE,
63            );
64
65        for fee_payer_utxo in fee_payer_utxos {
66            builder = builder.add_input(
67                NormalSignatureKind::OperatorSighashDefault,
68                fee_payer_utxo,
69                SpendPath::KeySpend,
70                DEFAULT_SEQUENCE,
71            );
72        }
73
74        builder = builder.add_output(UnspentTxOut::from_partial(TxOut {
75            value: change_amount,
76            script_pubkey: change_address.script_pubkey(),
77        }));
78
79        let mut tx_handler = builder.finalize();
80
81        for fee_payer_input in 1..tx_handler.get_cached_tx().input.len() {
82            let sighash = tx_handler
83                .calculate_pubkey_spend_sighash(fee_payer_input, bitcoin::TapSighashType::Default)
84                .map_err(|e| BridgeError::Eyre(eyre::eyre!("{}", e)))?;
85            let signature = signer
86                .sign_with_tweak_data(sighash, TapTweakData::KeyPath(None))
87                .map_err(|e| BridgeError::Eyre(eyre::eyre!("{}", e)))?;
88            tx_handler
89                .set_p2tr_key_spend_witness(
90                    &bitcoin::taproot::Signature {
91                        signature,
92                        sighash_type: bitcoin::TapSighashType::Default,
93                    },
94                    fee_payer_input,
95                )
96                .map_err(|e| BridgeError::Eyre(eyre::eyre!("{}", e)))?;
97        }
98
99        let child_tx = tx_handler.get_cached_tx().clone();
100        Ok(child_tx)
101    }
102
103    fn utxos_to_spendable_inputs(
104        utxos: Vec<(Txid, u32, Amount)>,
105        signer_address: &Address,
106    ) -> Vec<Self::SpendableInput> {
107        utxos
108            .into_iter()
109            .map(|(txid, vout, amount)| {
110                SpendableTxIn::new_partial(
111                    OutPoint { txid, vout },
112                    TxOut {
113                        value: amount,
114                        script_pubkey: signer_address.script_pubkey(),
115                    },
116                )
117            })
118            .collect()
119    }
120}
121
122#[async_trait]
123pub trait TxSenderClientExt {
124    /// Returns debugging information for a transaction
125    ///
126    /// This function gathers all debugging information about a transaction from the database,
127    /// including its state history, fee payer UTXOs, submission errors, and current state.
128    ///
129    /// # Arguments
130    /// * `id` - The ID of the transaction to debug
131    ///
132    /// # Returns
133    /// A comprehensive debug info structure with all available information about the transaction
134    async fn debug_tx(&self, id: u32) -> Result<TxDebugInfo, BridgeError>;
135}
136
137#[async_trait]
138impl TxSenderClientExt for TxSenderClient<Database> {
139    async fn debug_tx(&self, id: u32) -> Result<TxDebugInfo, BridgeError> {
140        use crate::rpc::clementine::{TxDebugFeePayerUtxo, TxDebugInfo, TxDebugSubmissionError};
141
142        let (tx_metadata, tx, fee_paying_type, seen_block_id, _) =
143            self.db.get_try_to_send_tx(None, id).await.map_to_eyre()?;
144
145        let submission_errors = self
146            .db
147            .get_tx_debug_submission_errors(None, id)
148            .await
149            .map_to_eyre()?;
150
151        let submission_errors = submission_errors
152            .into_iter()
153            .map(|(error_message, timestamp)| TxDebugSubmissionError {
154                error_message,
155                timestamp,
156            })
157            .collect();
158
159        let current_state = self.db.get_tx_debug_info(None, id).await.map_to_eyre()?;
160
161        let fee_payer_utxos = self
162            .db
163            .get_tx_debug_fee_payer_utxos(None, id)
164            .await
165            .map_to_eyre()?;
166
167        let fee_payer_utxos = fee_payer_utxos
168            .into_iter()
169            .map(|(txid, vout, amount, confirmed)| TxDebugFeePayerUtxo {
170                txid: Some(txid.into()),
171                vout,
172                amount: amount.to_sat(),
173                confirmed,
174            })
175            .collect::<Vec<_>>();
176
177        let txid = match fee_paying_type {
178            FeePayingType::CPFP | FeePayingType::NoFunding => tx.compute_txid(),
179            FeePayingType::RBF => self
180                .db
181                .get_last_rbf_txid(None, id)
182                .await
183                .map_to_eyre()?
184                .unwrap_or(bitcoin::Txid::all_zeros()),
185        };
186        let debug_info = TxDebugInfo {
187            id,
188            is_active: seen_block_id.is_none(),
189            current_state: current_state.unwrap_or_else(|| "unknown".to_string()),
190            submission_errors,
191            created_at: "".to_string(),
192            txid: Some(txid.into()),
193            fee_paying_type: format!("{fee_paying_type:?}"),
194            fee_payer_utxos_count: fee_payer_utxos.len() as u32,
195            fee_payer_utxos_confirmed_count: fee_payer_utxos
196                .iter()
197                .filter(|utxo| utxo.confirmed)
198                .count() as u32,
199            fee_payer_utxos,
200            raw_tx: bitcoin::consensus::serialize(&tx),
201            metadata: tx_metadata.map(|metadata| rpc::clementine::TxMetadata {
202                deposit_outpoint: metadata.deposit_outpoint.map(Into::into),
203                operator_xonly_pk: metadata.operator_xonly_pk.map(Into::into),
204
205                round_idx: metadata
206                    .round_idx
207                    .unwrap_or(RoundIndex::Round(0))
208                    .to_index() as u32,
209                kickoff_idx: metadata.kickoff_idx.unwrap_or(0),
210                tx_type: Some(metadata.tx_type.into()),
211            }),
212        };
213
214        Ok(debug_info)
215    }
216}
217
218#[async_trait]
219pub trait TxSenderExt {
220    async fn debug_tx(&self, id: u32) -> Result<TxDebugInfo, BridgeError>;
221}
222
223#[async_trait]
224impl<S> TxSenderExt for TxSender<S, Database, CoreTxBuilder>
225where
226    S: TxSenderSigner + 'static,
227{
228    async fn debug_tx(&self, id: u32) -> Result<TxDebugInfo, BridgeError> {
229        let client = self.client();
230        client.debug_tx(id).await
231    }
232}