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;
29use clementine_primitives::BitcoinSyncerEvent;
30pub use client::TxSenderClient;
31
32use async_trait::async_trait;
33use bitcoin::secp256k1::schnorr;
34use bitcoin::taproot::TaprootSpendInfo;
35use bitcoin::{
36    Address, Amount, FeeRate, OutPoint, Sequence, Transaction, Txid, Weight, XOnlyPublicKey,
37};
38use bitcoincore_rpc::RpcApi;
39use clementine_config::protocol::ProtocolParamset;
40use clementine_config::tx_sender::TxSenderLimits;
41use clementine_errors::{BridgeError, ResultExt};
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        bumped_id: u32,
250        fee_payer_txid: Txid,
251        vout: u32,
252        amount: Amount,
253        replacement_of_id: Option<u32>,
254    ) -> Result<(), BridgeError>;
255
256    /// Get the last RBF transaction ID for a specific send attempt.
257    async fn get_last_rbf_txid(
258        &self,
259        dbtx: Option<&mut Self::Transaction>,
260        id: u32,
261    ) -> Result<Option<Txid>, BridgeError>;
262
263    /// Save a new RBF transaction ID.
264    async fn save_rbf_txid(
265        &self,
266        dbtx: Option<&mut Self::Transaction>,
267        id: u32,
268        txid: Txid,
269    ) -> Result<(), BridgeError>;
270
271    /// Save a cancelled outpoint activation condition.
272    async fn save_cancelled_outpoint(
273        &self,
274        dbtx: Option<&mut Self::Transaction>,
275        cancelled_id: u32,
276        outpoint: OutPoint,
277    ) -> Result<(), BridgeError>;
278
279    /// Save a cancelled transaction ID activation condition.
280    async fn save_cancelled_txid(
281        &self,
282        dbtx: Option<&mut Self::Transaction>,
283        cancelled_id: u32,
284        txid: Txid,
285    ) -> Result<(), BridgeError>;
286
287    /// Save an activated transaction ID condition.
288    async fn save_activated_txid(
289        &self,
290        dbtx: Option<&mut Self::Transaction>,
291        activated_id: u32,
292        prerequisite_tx: &ActivatedWithTxid,
293    ) -> Result<(), BridgeError>;
294
295    /// Save an activated outpoint condition.
296    async fn save_activated_outpoint(
297        &self,
298        dbtx: Option<&mut Self::Transaction>,
299        activated_id: u32,
300        activated_outpoint: &ActivatedWithOutpoint,
301    ) -> Result<(), BridgeError>;
302
303    /// Update the effective fee rate of a transaction.
304    async fn update_effective_fee_rate(
305        &self,
306        dbtx: Option<&mut Self::Transaction>,
307        id: u32,
308        effective_fee_rate: FeeRate,
309        current_tip_height: u32,
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    /// Debug method to log information about inactive transactions.
352    async fn debug_inactive_txs(&self, _fee_rate: FeeRate, _current_tip_height: u32) {}
353
354    /// Fetch the next event from the Bitcoin syncer.
355    async fn fetch_next_bitcoin_syncer_evt(
356        &self,
357        dbtx: &mut Self::Transaction,
358        consumer_id: &str,
359    ) -> Result<Option<BitcoinSyncerEvent>, BridgeError>;
360
361    /// Synchronize transaction confirmations based on canonical block status.
362    /// Confirms transactions in canonical blocks and unconfirms transactions in
363    /// non-canonical blocks.
364    async fn sync_transaction_confirmations(
365        &self,
366        dbtx: Option<&mut Self::Transaction>,
367    ) -> Result<(), BridgeError>;
368
369    /// Get the maximum height of canonical blocks in the database.
370    async fn get_max_height(
371        &self,
372        dbtx: Option<&mut Self::Transaction>,
373    ) -> Result<Option<u32>, BridgeError>;
374
375    /// Get the effective fee rate of a transaction and the block height when it was set.
376    /// Returns (None, None) if no effective fee rate has been set yet.
377    async fn get_effective_fee_rate(
378        &self,
379        dbtx: Option<&mut Self::Transaction>,
380        id: u32,
381    ) -> Result<(Option<FeeRate>, Option<u32>), BridgeError>;
382}
383
384#[derive(Clone, Debug)]
385pub struct MempoolConfig {
386    pub host: Option<String>,
387    pub endpoint: Option<String>,
388}
389
390/// Manages the process of sending Bitcoin transactions, including handling fee bumping
391/// strategies like Replace-By-Fee (RBF) and Child-Pays-For-Parent (CPFP).
392///
393/// It interacts with a Bitcoin Core RPC endpoint (`ExtendedBitcoinRpc`) to query network state
394/// (like fee rates) and submit transactions. It uses a `Database` to persist transaction
395/// state, track confirmation status, and manage associated data like fee payer UTXOs.
396/// The `Actor` provides signing capabilities for transactions controlled by this service.
397///
398/// The `TxSenderTxBuilder` type parameter provides static methods for transaction building
399/// capabilities for CPFP child transactions, using `SpendableTxIn` and `TxHandler`.
400#[derive(Clone)]
401pub struct TxSender<S, D, B>
402where
403    S: TxSenderSigner + 'static,
404    D: TxSenderDatabase + 'static,
405    B: TxSenderTxBuilder + 'static,
406{
407    pub signer: S,
408    pub rpc: clementine_extended_rpc::ExtendedBitcoinRpc,
409    pub db: D,
410    pub protocol_paramset: &'static ProtocolParamset,
411    pub tx_sender_limits: TxSenderLimits,
412    pub http_client: reqwest::Client,
413    mempool_config: MempoolConfig,
414    /// Phantom data to track the TxBuilder type.
415    /// B provides static methods for transaction building.
416    _tx_builder: std::marker::PhantomData<B>,
417}
418
419impl<S, D, B> std::fmt::Debug for TxSender<S, D, B>
420where
421    S: TxSenderSigner + std::fmt::Debug + 'static,
422    D: TxSenderDatabase + std::fmt::Debug + 'static,
423    B: TxSenderTxBuilder + 'static,
424{
425    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426        f.debug_struct("TxSender")
427            .field("signer", &self.signer)
428            .field("db", &self.db)
429            .field("protocol_paramset", &self.protocol_paramset)
430            .field("tx_sender_limits", &self.tx_sender_limits)
431            .finish()
432    }
433}
434
435impl<S, D, B> TxSender<S, D, B>
436where
437    S: TxSenderSigner + 'static,
438    D: TxSenderDatabase + 'static,
439    B: TxSenderTxBuilder + 'static,
440{
441    /// Creates a new TxSender.
442    ///
443    /// The type parameter `B` provides static methods for CPFP child transaction creation
444    /// using SpendableTxIn and TxHandler from the core builder module.
445    pub fn new(
446        signer: S,
447        rpc: clementine_extended_rpc::ExtendedBitcoinRpc,
448        db: D,
449        protocol_paramset: &'static ProtocolParamset,
450        tx_sender_limits: TxSenderLimits,
451        mempool_config: MempoolConfig,
452    ) -> Self {
453        Self {
454            signer,
455            rpc,
456            db,
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(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        if std::env::var("TXSENDER_DBG_INACTIVE_TXS").is_ok() {
605            self.db
606                .debug_inactive_txs(get_sendable_txs_fee_rate, current_tip_height)
607                .await;
608        }
609
610        for id in txs {
611            // Update debug state
612            tracing::debug!(
613                try_to_send_id = id,
614                "Processing TX in try_to_send_unconfirmed_txs with fee rate {new_fee_rate}",
615            );
616
617            let (tx_metadata, tx, fee_paying_type, seen_block_id, rbf_signing_info) =
618                match self.db.get_try_to_send_tx(None, id).await {
619                    Ok(res) => res,
620                    Err(e) => {
621                        log_error_for_tx!(self.db, id, format!("Failed to get tx details: {}", e));
622                        continue;
623                    }
624                };
625
626            // Check if the transaction is already confirmed (only happens if it was confirmed after this loop started)
627            if let Some(block_id) = seen_block_id {
628                tracing::debug!(
629                    try_to_send_id = id,
630                    "Transaction already confirmed in block with block id of {}",
631                    block_id
632                );
633
634                // Update sending state
635                let _ = self
636                    .db
637                    .update_tx_debug_sending_state(id, "confirmed", true)
638                    .await;
639
640                continue;
641            }
642
643            // Get effective fee rate and block height to calculate adjusted fee rate
644            let (previous_effective_fee_rate, last_bump_block_height) =
645                match self.db.get_effective_fee_rate(None, id).await {
646                    Ok(res) => res,
647                    Err(e) => {
648                        log_error_for_tx!(
649                            self.db,
650                            id,
651                            format!("Failed to get effective fee rate: {}", e)
652                        );
653                        continue;
654                    }
655                };
656
657            // Calculate adjusted fee rate considering:
658            // 1. If new_fee_rate > previous_effective_fee_rate, use max(new_fee_rate, previous_effective_fee_rate + incremental_fee_rate)
659            // 2. If tx has been stuck for 10+ blocks, bump with incremental fee
660            let adjusted_fee_rate = match self
661                .calculate_target_fee_rate(
662                    previous_effective_fee_rate,
663                    new_fee_rate,
664                    last_bump_block_height,
665                    current_tip_height,
666                )
667                .await
668            {
669                Ok(rate) => rate,
670                Err(e) => {
671                    log_error_for_tx!(
672                        self.db,
673                        id,
674                        format!("Failed to calculate adjusted fee rate: {}", e)
675                    );
676                    continue;
677                }
678            };
679
680            let result = match fee_paying_type {
681                // Send nonstandard transactions to testnet4 using the mempool.space accelerator.
682                // As mempool uses out of band payment, we don't need to do cpfp or rbf.
683                _ if self.protocol_paramset.network == bitcoin::Network::Testnet4
684                    && self.is_bridge_tx_nonstandard(&tx) =>
685                {
686                    self.send_testnet4_nonstandard_tx(&tx, id).await
687                }
688                FeePayingType::CPFP => {
689                    self.send_cpfp_tx(id, tx, tx_metadata, adjusted_fee_rate, current_tip_height)
690                        .await
691                }
692                FeePayingType::RBF => {
693                    self.send_rbf_tx(
694                        id,
695                        tx,
696                        tx_metadata,
697                        adjusted_fee_rate,
698                        rbf_signing_info,
699                        current_tip_height,
700                    )
701                    .await
702                }
703                FeePayingType::NoFunding => self.send_no_funding_tx(id, tx, tx_metadata).await,
704            };
705
706            if let Err(e) = result {
707                log_error_for_tx!(self.db, id, format!("Failed to send tx: {:?}", e));
708            }
709        }
710
711        Ok(())
712    }
713    pub fn client(&self) -> TxSenderClient<D> {
714        TxSenderClient::new(self.db.clone())
715    }
716
717    /// Calculates the effective fee rate for a transaction, considering previous effective fee rate
718    /// and minimum incremental fee requirements.
719    ///
720    /// This function implements the logic for fee bumping that ensures:
721    /// 1. If no previous effective fee rate exists, use the new fee rate
722    /// 2. If previous effective fee rate exists, use the maximum of:
723    ///    - The new fee rate
724    ///    - Previous effective fee rate + minimum incremental fee rate
725    ///
726    /// # Arguments
727    /// * `previous_effective_fee_rate` - The previous effective fee rate (if any)
728    /// * `new_fee_rate` - The target fee rate for the new attempt
729    /// * `last_bump_block_height` - The block height when the last fee bump was done (if any)
730    /// * `current_tip_height` - The current blockchain tip height
731    ///
732    /// # Returns
733    /// The effective fee rate to use (in sat/kwu), capped by the hard cap from config
734    pub async fn calculate_target_fee_rate(
735        &self,
736        previous_effective_fee_rate: Option<FeeRate>,
737        new_fee_rate: FeeRate,
738        last_bump_block_height: Option<u32>,
739        current_tip_height: u32,
740    ) -> Result<FeeRate> {
741        // Hard cap from config (in sat/vB), convert to sat/kwu
742        let hard_cap = FeeRate::from_sat_per_vb(self.tx_sender_limits.fee_rate_hard_cap)
743            .expect("fee_rate_hard_cap should be valid");
744
745        let Some(previous_rate) = previous_effective_fee_rate else {
746            // No previous effective fee rate, use the new fee rate (capped)
747            return Ok(std::cmp::min(new_fee_rate, hard_cap));
748        };
749
750        // Check if the tx has been stuck for 10+ blocks
751        let is_stuck = match last_bump_block_height {
752            Some(block_height) => {
753                current_tip_height.saturating_sub(block_height)
754                    >= self.tx_sender_limits.fee_bump_after_blocks
755            }
756            None => false,
757        };
758
759        // Get minimum fee increment rate from node for BIP125 compliance. Returned value is in BTC/kvB
760        let incremental_fee_rate = self
761            .rpc
762            .get_network_info()
763            .await
764            .map_err(|e| eyre::eyre!(e))?
765            .incremental_fee;
766        let incremental_fee_rate_sat_per_kvb = incremental_fee_rate.to_sat();
767        let incremental_fee_rate =
768            FeeRate::from_sat_per_kwu(incremental_fee_rate_sat_per_kvb.div_ceil(4));
769
770        // Minimum bump fee rate required by BIP125
771        let min_bump_feerate =
772            previous_rate.to_sat_per_kwu() + incremental_fee_rate.to_sat_per_kwu();
773
774        // If new fee rate is higher than previous, use max of new_fee_rate and min_bump_feerate
775        if new_fee_rate.to_sat_per_kwu() > previous_rate.to_sat_per_kwu() {
776            let effective_feerate = std::cmp::max(new_fee_rate.to_sat_per_kwu(), min_bump_feerate);
777            let result = FeeRate::from_sat_per_kwu(effective_feerate);
778            return Ok(std::cmp::min(result, hard_cap));
779        }
780
781        // If the tx is stuck for 10+ blocks, force a fee bump (previous + incremental)
782        if is_stuck {
783            let result = FeeRate::from_sat_per_kwu(min_bump_feerate);
784            let capped_result = std::cmp::min(result, hard_cap);
785
786            tracing::debug!(
787                "TX stuck for at least {} blocks, forcing fee bump from {} to {} sat/kwu (hard cap: {} sat/kwu)",
788                self.tx_sender_limits.fee_bump_after_blocks,
789                previous_rate.to_sat_per_kwu(),
790                capped_result.to_sat_per_kwu(),
791                hard_cap.to_sat_per_kwu()
792            );
793
794            return Ok(capped_result);
795        }
796
797        // Neither higher fee rate nor stuck, use previous rate (no change needed)
798        Ok(previous_rate)
799    }
800
801    /// Sends a transaction that is already fully funded and signed.
802    ///
803    /// This function is used for transactions that do not require fee bumping strategies
804    /// like RBF or CPFP. The transaction is submitted directly to the Bitcoin network
805    /// without any modifications.
806    ///
807    /// # Arguments
808    /// * `try_to_send_id` - The database ID tracking this send attempt.
809    /// * `tx` - The fully funded and signed transaction ready for broadcast.
810    /// * `tx_metadata` - Optional metadata associated with the transaction for debugging.
811    ///
812    /// # Behavior
813    /// 1. Attempts to broadcast the transaction using `send_raw_transaction` RPC.
814    /// 2. Updates the database with success/failure state for debugging purposes.
815    /// 3. Logs appropriate messages for monitoring and troubleshooting.
816    ///
817    /// # Returns
818    /// * `Ok(())` - If the transaction was successfully broadcast.
819    /// * `Err(SendTxError)` - If the broadcast failed.
820    #[tracing::instrument(skip_all, fields(try_to_send_id, tx_meta=?tx_metadata))]
821    pub async fn send_no_funding_tx(
822        &self,
823        try_to_send_id: u32,
824        tx: Transaction,
825        tx_metadata: Option<TxMetadata>,
826    ) -> Result<()> {
827        match self.rpc.send_raw_transaction(&tx).await {
828            Ok(sent_txid) => {
829                tracing::debug!(
830                    try_to_send_id,
831                    "Successfully sent no funding tx with txid {}",
832                    sent_txid
833                );
834                let _ = self
835                    .db
836                    .update_tx_debug_sending_state(try_to_send_id, "no_funding_send_success", true)
837                    .await;
838            }
839            Err(e) => {
840                tracing::error!(
841                    "Failed to send no funding tx with try_to_send_id: {try_to_send_id:?} and metadata: {tx_metadata:?}"
842                );
843                let err_msg = format!("send_raw_transaction error for no funding tx: {e}");
844                log_error_for_tx!(self.db, try_to_send_id, err_msg);
845                let _ = self
846                    .db
847                    .update_tx_debug_sending_state(try_to_send_id, "no_funding_send_failed", true)
848                    .await;
849                return Err(SendTxError::Other(eyre::eyre!(e)));
850            }
851        };
852
853        Ok(())
854    }
855}