clementine_tx_sender/
lib.rs

1//! # Clementine Transaction Sender
2//!
3//! This crate handles the creation, signing, and broadcasting of Bitcoin transactions,
4//! supporting various fee-bumping strategies like CPFP and RBF.
5
6pub mod client;
7pub mod cpfp;
8pub mod nonstandard;
9pub mod rbf;
10pub mod task;
11
12// Define a macro for logging errors and saving them to the database
13#[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/// Activation condition based on a transaction ID.
51#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
52pub struct ActivatedWithTxid {
53    /// The transaction ID that must be seen.
54    pub txid: Txid,
55    /// Number of blocks that must pass after seeing the transaction.
56    pub relative_block_height: u32,
57}
58
59/// Activation condition based on an outpoint.
60#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
61pub struct ActivatedWithOutpoint {
62    /// The outpoint that must be spent.
63    pub outpoint: OutPoint,
64    /// Number of blocks that must pass after seeing the outpoint spent.
65    pub relative_block_height: u32,
66}
67
68/// Default sequence for transactions.
69pub const DEFAULT_SEQUENCE: Sequence = Sequence(0xFFFFFFFD);
70
71/// Represents a spendable UTXO.
72#[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
89/// Trait for extracting information from a spendable input.
90/// This allows different input types (SpendableUtxo, SpendableTxIn) to be used interchangeably.
91pub trait SpendableInputInfo: Send + Sync + Clone {
92    /// Returns a reference to the previous output (TxOut) for this input.
93    fn get_prevout(&self) -> &bitcoin::TxOut;
94
95    /// Returns the outpoint for this input.
96    fn get_outpoint(&self) -> OutPoint;
97}
98
99/// Trait for building child transactions in the transaction sender.
100///
101/// This abstraction allows the core crate to provide `SpendableTxIn`-based transaction building
102/// using `TxHandler`, while keeping the tx-sender crate independent of the builder module.
103///
104/// All methods are static - no instance of this trait is stored.
105pub trait TxSenderTxBuilder: Send + Sync + 'static {
106    /// The type representing a spendable transaction input.
107    /// In core, this would be `SpendableTxIn`.
108    type SpendableInput: SpendableInputInfo;
109
110    /// Builds a child transaction for CPFP.
111    ///
112    /// This method constructs a child transaction that spends the P2A anchor output
113    /// and fee payer UTXOs, paying the required fees for the CPFP package.
114    ///
115    /// # Arguments
116    /// * `p2a_anchor` - The P2A anchor output to spend
117    /// * `anchor_sat` - Amount in the anchor output
118    /// * `fee_payer_utxos` - UTXOs to fund the child transaction
119    /// * `change_address` - Address for the change output
120    /// * `required_fee` - The calculated required fee for the package
121    /// * `signer_address` - The signer's address (for script pubkey)
122    /// * `signer` - The signer to sign the transaction inputs
123    ///
124    /// # Returns
125    /// A signed child transaction ready for package submission.
126    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    /// Converts database UTXOs into the builder's SpendableInput type.
136    ///
137    /// # Arguments
138    /// * `utxos` - Vector of (txid, vout, amount) tuples from the database
139    /// * `signer_address` - The signer's address (for script pubkey generation)
140    ///
141    /// # Returns
142    /// Vector of SpendableInput instances ready for use in transaction building.
143    fn utxos_to_spendable_inputs(
144        utxos: Vec<(Txid, u32, Amount)>,
145        signer_address: &Address,
146    ) -> Vec<Self::SpendableInput>;
147}
148
149/// Trait for signing transactions in the transaction sender.
150#[async_trait]
151pub trait TxSenderSigner: Send + Sync {
152    /// Returns the signer's Bitcoin address.
153    fn address(&self) -> &Address;
154
155    /// Returns the signer's X-only public key.
156    fn xonly_public_key(&self) -> XOnlyPublicKey;
157
158    /// Signs a message with a tweak.
159    fn sign_with_tweak_data(
160        &self,
161        sighash: bitcoin::TapSighash,
162        tweak_data: TapTweakData,
163    ) -> Result<schnorr::Signature, BridgeError>;
164}
165
166/// Trait for database operations required by the transaction sender.
167#[async_trait]
168pub trait TxSenderDatabase: Send + Sync + Clone {
169    /// Type for database transactions.
170    type Transaction: Send;
171
172    /// Begin a new database transaction.
173    async fn begin_transaction(&self) -> Result<Self::Transaction, BridgeError>;
174
175    /// Commit a database transaction.
176    async fn commit_transaction(&self, dbtx: Self::Transaction) -> Result<(), BridgeError>;
177
178    /// Save a debug message for a transaction submission error.
179    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    /// Get transactions that are ready to be sent.
187    async fn get_sendable_txs(
188        &self,
189        fee_rate: FeeRate,
190        current_tip_height: u32,
191    ) -> Result<Vec<u32>, BridgeError>;
192
193    /// Get details of a transaction to be sent.
194    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    /// Update the debug sending state of a transaction.
210    async fn update_tx_debug_sending_state(
211        &self,
212        id: u32,
213        state: &str,
214        is_error: bool,
215    ) -> Result<(), BridgeError>;
216
217    /// Get all unconfirmed fee payer transactions.
218    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    /// Get unconfirmed fee payer transactions for a specific parent transaction.
224    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    /// Mark a fee payer UTXO as evicted.
231    async fn mark_fee_payer_utxo_as_evicted(
232        &self,
233        dbtx: Option<&mut Self::Transaction>,
234        id: u32,
235    ) -> Result<(), BridgeError>;
236
237    /// Get confirmed fee payer UTXOs for a specific parent transaction.
238    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    /// Save a fee payer transaction.
245    #[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    /// Get the last RBF transaction ID for a specific send attempt.
258    async fn get_last_rbf_txid(
259        &self,
260        dbtx: Option<&mut Self::Transaction>,
261        id: u32,
262    ) -> Result<Option<Txid>, BridgeError>;
263
264    /// Save a new RBF transaction ID.
265    async fn save_rbf_txid(
266        &self,
267        dbtx: Option<&mut Self::Transaction>,
268        id: u32,
269        txid: Txid,
270    ) -> Result<(), BridgeError>;
271
272    /// Save a cancelled outpoint activation condition.
273    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    /// Save a cancelled transaction ID activation condition.
281    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    /// Save an activated transaction ID condition.
289    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    /// Save an activated outpoint condition.
297    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    /// Update the effective fee rate of a transaction.
305    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    /// Check if a transaction already exists in the transaction sender queue.
313    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    /// Save a transaction to the sending queue.
320    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    /// Returns debug information for a transaction.
331    async fn get_tx_debug_info(
332        &self,
333        dbtx: Option<&mut Self::Transaction>,
334        id: u32,
335    ) -> Result<Option<String>, BridgeError>;
336
337    /// Returns submission errors for a transaction.
338    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    /// Returns fee payer UTXOs for an attempt.
345    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    /// Fetch the next event from the Bitcoin syncer.
352    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    /// Get block hash and height from its ID.
359    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    /// Confirm transactions in a block.
366    async fn confirm_transactions(
367        &self,
368        dbtx: &mut Self::Transaction,
369        block_id: u32,
370    ) -> Result<(), BridgeError>;
371
372    /// Unconfirm transactions in a block (due to reorg).
373    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/// Manages the process of sending Bitcoin transactions, including handling fee bumping
387/// strategies like Replace-By-Fee (RBF) and Child-Pays-For-Parent (CPFP).
388///
389/// It interacts with a Bitcoin Core RPC endpoint (`ExtendedBitcoinRpc`) to query network state
390/// (like fee rates) and submit transactions. It uses a `Database` to persist transaction
391/// state, track confirmation status, and manage associated data like fee payer UTXOs.
392/// The `Actor` provides signing capabilities for transactions controlled by this service.
393///
394/// The `TxSenderTxBuilder` type parameter provides static methods for transaction building
395/// capabilities for CPFP child transactions, using `SpendableTxIn` and `TxHandler`.
396#[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    /// Phantom data to track the TxBuilder type.
412    /// B provides static methods for transaction building.
413    _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    /// Creates a new TxSender.
440    ///
441    /// The type parameter `B` provides static methods for CPFP child transaction creation
442    /// using SpendableTxIn and TxHandler from the core builder module.
443    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    /// Calculates the total fee required for a transaction package based on the fee bumping strategy.
480    ///
481    /// # Arguments
482    /// * `parent_tx_weight` - The weight of the main transaction being bumped.
483    /// * `num_fee_payer_utxos` - The number of fee payer UTXOs used (relevant for child tx size in CPFP).
484    /// * `fee_rate` - The target fee rate (sat/kwu or similar).
485    /// * `fee_paying_type` - The strategy being used (CPFP or RBF).
486    ///
487    /// # Calculation Logic
488    /// *   **CPFP:** Calculates the weight of the hypothetical child transaction based on the
489    ///     number of fee payer inputs and standard P2TR output sizes. It then calculates the
490    ///     fee based on the *combined virtual size* (vbytes) of the parent and child transactions,
491    ///     as miners evaluate the package deal.
492    /// *   **RBF:** Calculates the weight of the replacement transaction itself (assuming inputs
493    ///     and potentially outputs change slightly). The fee is calculated based on the weight
494    ///     of this single replacement transaction.
495    ///
496    /// Reference for weight estimates: <https://bitcoin.stackexchange.com/a/116959>
497    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        // Estimate the weight of the child transaction (for CPFP) or the RBF replacement.
508        // P2TR input witness adds ~57.5vbytes (230 WU). P2TR output adds 43 vbytes (172 WU).
509        // Base transaction overhead (version, locktime, input/output counts) ~ 10.5 vBytes (42 WU)
510        // Anchor input marker (OP_FALSE OP_RETURN ..) adds overhead. Exact WU TBD.
511        // For CPFP child: (N fee payer inputs) + (1 anchor input) + (1 change output)
512        // For RBF replacement: (N fee payer inputs) + (1 change output) - assuming it replaces a tx with an anchor.
513        let child_tx_weight = match fee_paying_type {
514            // CPFP Child: N fee payer inputs + 1 anchor input + 1 change output + base overhead.
515            // Approx WU: (230 * num_fee_payer_utxos) + 230 + 172 + base_overhead_wu
516            // Simplified calculation used here needs verification.
517            FeePayingType::CPFP => Weight::from_wu_usize(230 * num_fee_payer_utxos + 207 + 172),
518            // RBF Replacement: N fee payer inputs + 1 change output + base overhead.
519            // Assumes it replaces a tx of similar structure but potentially different inputs/fees.
520            // Simplified calculation used here needs verification.
521            FeePayingType::RBF => Weight::from_wu_usize(230 * num_fee_payer_utxos + 172),
522            FeePayingType::NoFunding => Weight::from_wu_usize(0),
523        };
524
525        // Calculate total weight for fee calculation.
526        // For CPFP, miners consider the effective fee rate over the combined *vbytes* of parent + child.
527        // For RBF, miners consider the fee rate of the single replacement transaction's weight.
528        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, // Should likely just be the RBF tx weight? Check RBF rules.
533            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    /// Fetches transactions that are eligible to be sent or bumped from
555    /// database based on the given fee rate and tip height. Then, places a send
556    /// transaction request to the Bitcoin based on the fee strategy.
557    ///
558    /// For each eligible transaction (`id`):
559    ///
560    /// 1.  **Send/Bump Main Tx:** Calls `send_tx` to either perform RBF or CPFP on the main
561    ///     transaction (`id`) using the `new_fee_rate`.
562    /// 2.  **Handle Errors:**
563    ///     - [`SendTxError::UnconfirmedFeePayerUTXOsLeft`]: Skips the current tx, waiting for fee
564    ///       payers to confirm.
565    ///     - [`SendTxError::InsufficientFeePayerAmount`]: Calls `create_fee_payer_utxo` to
566    ///       provision more funds for a future CPFP attempt.
567    ///     - Other errors are logged.
568    ///
569    /// # Arguments
570    /// * `new_fee_rate` - The current target fee rate based on network conditions.
571    /// * `current_tip_height` - The current blockchain height, used for time-lock checks.
572    /// * `is_tip_height_increased` - True if the tip height has increased since the last time we sent unconfirmed transactions.
573    #[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        // get_sendable_txs doesn't return txs that we already sent in the past with >= fee rate to the current fee rate
581        // but if we have a new block height, but the tx is still not confirmed, we want to send it again anyway in case
582        // some error occurred on our bitcoin rpc/our tx got evicted from mempool somehow (for ex: if a fee payer of cpfp tx was reorged,
583        // cpfp tx will get evicted as v3 cpfp cannot have unconfirmed ancestors)
584        // if block height is increased, we use a dummy high fee rate to get all sendable txs
585        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        // bump fees of fee payer transactions that are unconfirmed
597        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            // Update debug state
606            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            // Check if the transaction is already confirmed (only happens if it was confirmed after this loop started)
621            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                // Update sending state
629                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                // Send nonstandard transactions to testnet4 using the mempool.space accelerator.
639                // As mempool uses out of band payment, we don't need to do cpfp or rbf.
640                _ 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    /// Sends a transaction that is already fully funded and signed.
665    ///
666    /// This function is used for transactions that do not require fee bumping strategies
667    /// like RBF or CPFP. The transaction is submitted directly to the Bitcoin network
668    /// without any modifications.
669    ///
670    /// # Arguments
671    /// * `try_to_send_id` - The database ID tracking this send attempt.
672    /// * `tx` - The fully funded and signed transaction ready for broadcast.
673    /// * `tx_metadata` - Optional metadata associated with the transaction for debugging.
674    ///
675    /// # Behavior
676    /// 1. Attempts to broadcast the transaction using `send_raw_transaction` RPC.
677    /// 2. Updates the database with success/failure state for debugging purposes.
678    /// 3. Logs appropriate messages for monitoring and troubleshooting.
679    ///
680    /// # Returns
681    /// * `Ok(())` - If the transaction was successfully broadcast.
682    /// * `Err(SendTxError)` - If the broadcast failed.
683    #[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}