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#[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 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}