1pub mod client;
7pub mod cpfp;
8pub mod nonstandard;
9pub mod rbf;
10pub mod task;
11
12#[macro_export]
14macro_rules! log_error_for_tx {
15 ($db:expr, $try_to_send_id:expr, $err:expr) => {{
16 let db = $db.clone();
17 let try_to_send_id = $try_to_send_id;
18 let err = $err.to_string();
19 tracing::warn!(try_to_send_id, "{}", err);
20 tokio::spawn(async move {
21 let _ = db
22 .save_tx_debug_submission_error(None, try_to_send_id, &err)
23 .await;
24 });
25 }};
26}
27
28pub use clementine_errors::SendTxError;
29pub use client::TxSenderClient;
30
31use async_trait::async_trait;
32use bitcoin::secp256k1::schnorr;
33use bitcoin::taproot::TaprootSpendInfo;
34use bitcoin::{
35 Address, Amount, FeeRate, OutPoint, Sequence, Transaction, Txid, Weight, XOnlyPublicKey,
36};
37use bitcoincore_rpc::RpcApi;
38use clementine_config::protocol::ProtocolParamset;
39use clementine_config::tx_sender::TxSenderLimits;
40use clementine_errors::{BridgeError, ResultExt};
41use clementine_primitives::BitcoinSyncerEvent;
42
43pub type Result<T, E = SendTxError> = std::result::Result<T, E>;
44
45use clementine_utils::sign::TapTweakData;
46use clementine_utils::{FeePayingType, RbfSigningInfo, TxMetadata};
47use eyre::OptionExt;
48use serde::{Deserialize, Serialize};
49
50#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
52pub struct ActivatedWithTxid {
53 pub txid: Txid,
55 pub relative_block_height: u32,
57}
58
59#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
61pub struct ActivatedWithOutpoint {
62 pub outpoint: OutPoint,
64 pub relative_block_height: u32,
66}
67
68pub const DEFAULT_SEQUENCE: Sequence = Sequence(0xFFFFFFFD);
70
71#[derive(Debug, Clone)]
73pub struct SpendableUtxo {
74 pub outpoint: OutPoint,
75 pub txout: bitcoin::TxOut,
76 pub spend_info: Option<TaprootSpendInfo>,
77}
78
79impl SpendableInputInfo for SpendableUtxo {
80 fn get_prevout(&self) -> &bitcoin::TxOut {
81 &self.txout
82 }
83
84 fn get_outpoint(&self) -> OutPoint {
85 self.outpoint
86 }
87}
88
89pub trait SpendableInputInfo: Send + Sync + Clone {
92 fn get_prevout(&self) -> &bitcoin::TxOut;
94
95 fn get_outpoint(&self) -> OutPoint;
97}
98
99pub trait TxSenderTxBuilder: Send + Sync + 'static {
106 type SpendableInput: SpendableInputInfo;
109
110 fn build_child_tx<S: TxSenderSigner>(
127 p2a_anchor: OutPoint,
128 anchor_sat: Amount,
129 fee_payer_utxos: Vec<Self::SpendableInput>,
130 change_address: Address,
131 required_fee: Amount,
132 signer: &S,
133 ) -> Result<Transaction, BridgeError>;
134
135 fn utxos_to_spendable_inputs(
144 utxos: Vec<(Txid, u32, Amount)>,
145 signer_address: &Address,
146 ) -> Vec<Self::SpendableInput>;
147}
148
149#[async_trait]
151pub trait TxSenderSigner: Send + Sync {
152 fn address(&self) -> &Address;
154
155 fn xonly_public_key(&self) -> XOnlyPublicKey;
157
158 fn sign_with_tweak_data(
160 &self,
161 sighash: bitcoin::TapSighash,
162 tweak_data: TapTweakData,
163 ) -> Result<schnorr::Signature, BridgeError>;
164}
165
166#[async_trait]
168pub trait TxSenderDatabase: Send + Sync + Clone {
169 type Transaction: Send;
171
172 async fn begin_transaction(&self) -> Result<Self::Transaction, BridgeError>;
174
175 async fn commit_transaction(&self, dbtx: Self::Transaction) -> Result<(), BridgeError>;
177
178 async fn save_tx_debug_submission_error(
180 &self,
181 dbtx: Option<&mut Self::Transaction>,
182 id: u32,
183 error: &str,
184 ) -> Result<(), BridgeError>;
185
186 async fn get_sendable_txs(
188 &self,
189 fee_rate: FeeRate,
190 current_tip_height: u32,
191 ) -> Result<Vec<u32>, BridgeError>;
192
193 async fn get_try_to_send_tx(
195 &self,
196 dbtx: Option<&mut Self::Transaction>,
197 id: u32,
198 ) -> Result<
199 (
200 Option<TxMetadata>,
201 Transaction,
202 FeePayingType,
203 Option<u32>,
204 Option<RbfSigningInfo>,
205 ),
206 BridgeError,
207 >;
208
209 async fn update_tx_debug_sending_state(
211 &self,
212 id: u32,
213 state: &str,
214 is_error: bool,
215 ) -> Result<(), BridgeError>;
216
217 async fn get_all_unconfirmed_fee_payer_txs(
219 &self,
220 dbtx: Option<&mut Self::Transaction>,
221 ) -> Result<Vec<(u32, u32, Txid, u32, Amount, Option<u32>)>, BridgeError>;
222
223 async fn get_unconfirmed_fee_payer_txs(
225 &self,
226 dbtx: Option<&mut Self::Transaction>,
227 bumped_id: u32,
228 ) -> Result<Vec<(u32, Txid, u32, Amount)>, BridgeError>;
229
230 async fn mark_fee_payer_utxo_as_evicted(
232 &self,
233 dbtx: Option<&mut Self::Transaction>,
234 id: u32,
235 ) -> Result<(), BridgeError>;
236
237 async fn get_confirmed_fee_payer_utxos(
239 &self,
240 dbtx: Option<&mut Self::Transaction>,
241 id: u32,
242 ) -> Result<Vec<(Txid, u32, Amount)>, BridgeError>;
243
244 #[allow(clippy::too_many_arguments)]
246 async fn save_fee_payer_tx(
247 &self,
248 dbtx: Option<&mut Self::Transaction>,
249 try_to_send_id: Option<u32>,
250 bumped_id: u32,
251 fee_payer_txid: Txid,
252 vout: u32,
253 amount: Amount,
254 replacement_of_id: Option<u32>,
255 ) -> Result<(), BridgeError>;
256
257 async fn get_last_rbf_txid(
259 &self,
260 dbtx: Option<&mut Self::Transaction>,
261 id: u32,
262 ) -> Result<Option<Txid>, BridgeError>;
263
264 async fn save_rbf_txid(
266 &self,
267 dbtx: Option<&mut Self::Transaction>,
268 id: u32,
269 txid: Txid,
270 ) -> Result<(), BridgeError>;
271
272 async fn save_cancelled_outpoint(
274 &self,
275 dbtx: Option<&mut Self::Transaction>,
276 cancelled_id: u32,
277 outpoint: OutPoint,
278 ) -> Result<(), BridgeError>;
279
280 async fn save_cancelled_txid(
282 &self,
283 dbtx: Option<&mut Self::Transaction>,
284 cancelled_id: u32,
285 txid: Txid,
286 ) -> Result<(), BridgeError>;
287
288 async fn save_activated_txid(
290 &self,
291 dbtx: Option<&mut Self::Transaction>,
292 activated_id: u32,
293 prerequisite_tx: &ActivatedWithTxid,
294 ) -> Result<(), BridgeError>;
295
296 async fn save_activated_outpoint(
298 &self,
299 dbtx: Option<&mut Self::Transaction>,
300 activated_id: u32,
301 activated_outpoint: &ActivatedWithOutpoint,
302 ) -> Result<(), BridgeError>;
303
304 async fn update_effective_fee_rate(
306 &self,
307 dbtx: Option<&mut Self::Transaction>,
308 id: u32,
309 effective_fee_rate: FeeRate,
310 ) -> Result<(), BridgeError>;
311
312 async fn check_if_tx_exists_on_txsender(
314 &self,
315 dbtx: Option<&mut Self::Transaction>,
316 txid: Txid,
317 ) -> Result<Option<u32>, BridgeError>;
318
319 async fn save_tx(
321 &self,
322 dbtx: Option<&mut Self::Transaction>,
323 tx_metadata: Option<TxMetadata>,
324 tx: &Transaction,
325 fee_paying_type: FeePayingType,
326 txid: Txid,
327 rbf_signing_info: Option<RbfSigningInfo>,
328 ) -> Result<u32, BridgeError>;
329
330 async fn get_tx_debug_info(
332 &self,
333 dbtx: Option<&mut Self::Transaction>,
334 id: u32,
335 ) -> Result<Option<String>, BridgeError>;
336
337 async fn get_tx_debug_submission_errors(
339 &self,
340 dbtx: Option<&mut Self::Transaction>,
341 id: u32,
342 ) -> Result<Vec<(String, String)>, BridgeError>;
343
344 async fn get_tx_debug_fee_payer_utxos(
346 &self,
347 dbtx: Option<&mut Self::Transaction>,
348 id: u32,
349 ) -> Result<Vec<(Txid, u32, Amount, bool)>, BridgeError>;
350
351 async fn fetch_next_bitcoin_syncer_evt(
353 &self,
354 dbtx: &mut Self::Transaction,
355 consumer_id: &str,
356 ) -> Result<Option<BitcoinSyncerEvent>, BridgeError>;
357
358 async fn get_block_info_from_id(
360 &self,
361 dbtx: Option<&mut Self::Transaction>,
362 block_id: u32,
363 ) -> Result<Option<(bitcoin::BlockHash, u32)>, BridgeError>;
364
365 async fn confirm_transactions(
367 &self,
368 dbtx: &mut Self::Transaction,
369 block_id: u32,
370 ) -> Result<(), BridgeError>;
371
372 async fn unconfirm_transactions(
374 &self,
375 dbtx: &mut Self::Transaction,
376 block_id: u32,
377 ) -> Result<(), BridgeError>;
378}
379
380#[derive(Clone, Debug)]
381pub struct MempoolConfig {
382 pub host: Option<String>,
383 pub endpoint: Option<String>,
384}
385
386#[derive(Clone)]
397pub struct TxSender<S, D, B>
398where
399 S: TxSenderSigner + 'static,
400 D: TxSenderDatabase + 'static,
401 B: TxSenderTxBuilder + 'static,
402{
403 pub signer: S,
404 pub rpc: clementine_extended_rpc::ExtendedBitcoinRpc,
405 pub db: D,
406 pub btc_syncer_consumer_id: String,
407 pub protocol_paramset: &'static ProtocolParamset,
408 pub tx_sender_limits: TxSenderLimits,
409 pub http_client: reqwest::Client,
410 mempool_config: MempoolConfig,
411 _tx_builder: std::marker::PhantomData<B>,
414}
415
416impl<S, D, B> std::fmt::Debug for TxSender<S, D, B>
417where
418 S: TxSenderSigner + std::fmt::Debug + 'static,
419 D: TxSenderDatabase + std::fmt::Debug + 'static,
420 B: TxSenderTxBuilder + 'static,
421{
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 f.debug_struct("TxSender")
424 .field("signer", &self.signer)
425 .field("db", &self.db)
426 .field("btc_syncer_consumer_id", &self.btc_syncer_consumer_id)
427 .field("protocol_paramset", &self.protocol_paramset)
428 .field("tx_sender_limits", &self.tx_sender_limits)
429 .finish()
430 }
431}
432
433impl<S, D, B> TxSender<S, D, B>
434where
435 S: TxSenderSigner + 'static,
436 D: TxSenderDatabase + 'static,
437 B: TxSenderTxBuilder + 'static,
438{
439 pub fn new(
444 signer: S,
445 rpc: clementine_extended_rpc::ExtendedBitcoinRpc,
446 db: D,
447 btc_syncer_consumer_id: String,
448 protocol_paramset: &'static ProtocolParamset,
449 tx_sender_limits: TxSenderLimits,
450 mempool_config: MempoolConfig,
451 ) -> Self {
452 Self {
453 signer,
454 rpc,
455 db,
456 btc_syncer_consumer_id,
457 protocol_paramset,
458 tx_sender_limits,
459 http_client: reqwest::Client::new(),
460 mempool_config,
461 _tx_builder: std::marker::PhantomData,
462 }
463 }
464
465 pub async fn get_fee_rate(&self) -> Result<FeeRate, BridgeError> {
466 self.rpc
467 .get_fee_rate(
468 self.protocol_paramset.network,
469 &self.mempool_config.host,
470 &self.mempool_config.endpoint,
471 self.tx_sender_limits.mempool_fee_rate_multiplier,
472 self.tx_sender_limits.mempool_fee_rate_offset_sat_kvb,
473 self.tx_sender_limits.fee_rate_hard_cap,
474 )
475 .await
476 .map_err(|e| BridgeError::Eyre(e.into()))
477 }
478
479 fn calculate_required_fee(
498 parent_tx_weight: Weight,
499 num_fee_payer_utxos: usize,
500 fee_rate: FeeRate,
501 fee_paying_type: FeePayingType,
502 ) -> Result<Amount> {
503 tracing::info!(
504 "Calculating required fee for {} fee payer utxos",
505 num_fee_payer_utxos
506 );
507 let child_tx_weight = match fee_paying_type {
514 FeePayingType::CPFP => Weight::from_wu_usize(230 * num_fee_payer_utxos + 207 + 172),
518 FeePayingType::RBF => Weight::from_wu_usize(230 * num_fee_payer_utxos + 172),
522 FeePayingType::NoFunding => Weight::from_wu_usize(0),
523 };
524
525 let total_weight = match fee_paying_type {
529 FeePayingType::CPFP => Weight::from_vb_unchecked(
530 child_tx_weight.to_vbytes_ceil() + parent_tx_weight.to_vbytes_ceil(),
531 ),
532 FeePayingType::RBF => child_tx_weight + parent_tx_weight, FeePayingType::NoFunding => parent_tx_weight,
534 };
535
536 fee_rate
537 .checked_mul_by_weight(total_weight)
538 .ok_or_eyre("Fee calculation overflow")
539 .map_err(Into::into)
540 }
541
542 pub fn is_p2a_anchor(&self, output: &bitcoin::TxOut) -> bool {
543 clementine_utils::address::is_p2a_anchor(output)
544 }
545
546 pub fn find_p2a_vout(&self, tx: &Transaction) -> Result<usize, BridgeError> {
547 tx.output
548 .iter()
549 .position(|output| self.is_p2a_anchor(output))
550 .ok_or_eyre("P2A anchor output not found in transaction")
551 .map_err(BridgeError::Eyre)
552 }
553
554 #[tracing::instrument(skip_all, fields(sender = self.btc_syncer_consumer_id, new_fee_rate, current_tip_height))]
574 async fn try_to_send_unconfirmed_txs(
575 &self,
576 new_fee_rate: FeeRate,
577 current_tip_height: u32,
578 is_tip_height_increased: bool,
579 ) -> Result<()> {
580 let get_sendable_txs_fee_rate = if is_tip_height_increased {
586 FeeRate::from_sat_per_kwu(u32::MAX as u64)
587 } else {
588 new_fee_rate
589 };
590 let txs = self
591 .db
592 .get_sendable_txs(get_sendable_txs_fee_rate, current_tip_height)
593 .await
594 .map_to_eyre()?;
595
596 self.bump_fees_of_unconfirmed_fee_payer_txs(new_fee_rate)
598 .await?;
599
600 if !txs.is_empty() {
601 tracing::debug!("Trying to send {} sendable txs ", txs.len());
602 }
603
604 for id in txs {
605 tracing::debug!(
607 try_to_send_id = id,
608 "Processing TX in try_to_send_unconfirmed_txs with fee rate {new_fee_rate}",
609 );
610
611 let (tx_metadata, tx, fee_paying_type, seen_block_id, rbf_signing_info) =
612 match self.db.get_try_to_send_tx(None, id).await {
613 Ok(res) => res,
614 Err(e) => {
615 log_error_for_tx!(self.db, id, format!("Failed to get tx details: {}", e));
616 continue;
617 }
618 };
619
620 if let Some(block_id) = seen_block_id {
622 tracing::debug!(
623 try_to_send_id = id,
624 "Transaction already confirmed in block with block id of {}",
625 block_id
626 );
627
628 let _ = self
630 .db
631 .update_tx_debug_sending_state(id, "confirmed", true)
632 .await;
633
634 continue;
635 }
636
637 let result = match fee_paying_type {
638 _ if self.protocol_paramset.network == bitcoin::Network::Testnet4
641 && self.is_bridge_tx_nonstandard(&tx) =>
642 {
643 self.send_testnet4_nonstandard_tx(&tx, id).await
644 }
645 FeePayingType::CPFP => self.send_cpfp_tx(id, tx, tx_metadata, new_fee_rate).await,
646 FeePayingType::RBF => {
647 self.send_rbf_tx(id, tx, tx_metadata, new_fee_rate, rbf_signing_info)
648 .await
649 }
650 FeePayingType::NoFunding => self.send_no_funding_tx(id, tx, tx_metadata).await,
651 };
652
653 if let Err(e) = result {
654 log_error_for_tx!(self.db, id, format!("Failed to send tx: {:?}", e));
655 }
656 }
657
658 Ok(())
659 }
660 pub fn client(&self) -> TxSenderClient<D> {
661 TxSenderClient::new(self.db.clone(), self.btc_syncer_consumer_id.clone())
662 }
663
664 #[tracing::instrument(skip_all, fields(sender = self.btc_syncer_consumer_id, try_to_send_id, tx_meta=?tx_metadata))]
684 pub async fn send_no_funding_tx(
685 &self,
686 try_to_send_id: u32,
687 tx: Transaction,
688 tx_metadata: Option<TxMetadata>,
689 ) -> Result<()> {
690 match self.rpc.send_raw_transaction(&tx).await {
691 Ok(sent_txid) => {
692 tracing::debug!(
693 try_to_send_id,
694 "Successfully sent no funding tx with txid {}",
695 sent_txid
696 );
697 let _ = self
698 .db
699 .update_tx_debug_sending_state(try_to_send_id, "no_funding_send_success", true)
700 .await;
701 }
702 Err(e) => {
703 tracing::error!(
704 "Failed to send no funding tx with try_to_send_id: {try_to_send_id:?} and metadata: {tx_metadata:?}"
705 );
706 let err_msg = format!("send_raw_transaction error for no funding tx: {e}");
707 log_error_for_tx!(self.db, try_to_send_id, err_msg);
708 let _ = self
709 .db
710 .update_tx_debug_sending_state(try_to_send_id, "no_funding_send_failed", true)
711 .await;
712 return Err(SendTxError::Other(eyre::eyre!(e)));
713 }
714 };
715
716 Ok(())
717 }
718}