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
6#[cfg(feature = "citrea")]
7pub mod citrea;
8pub mod client;
9pub mod config;
10mod confirmations;
11pub mod cpfp;
12pub mod db;
13#[cfg(feature = "json-rpc")]
14pub mod jsonrpc;
15pub mod nonstandard;
16pub mod rbf;
17mod rpc_errors;
18mod signer;
19pub mod task;
20#[cfg(feature = "testing")]
21pub mod test_utils;
22
23// Define a macro for logging errors and saving them to the database
24#[macro_export]
25macro_rules! log_error_for_tx {
26    ($db:expr, $try_to_send_id:expr, $err:expr) => {{
27        let db = $db.clone();
28        let try_to_send_id = $try_to_send_id;
29        let err = $err.to_string();
30        tracing::warn!(try_to_send_id, "{}", err);
31        tokio::spawn(async move {
32            let _ = db
33                .save_tx_debug_submission_error(None, try_to_send_id, &err)
34                .await;
35        });
36    }};
37}
38
39pub use clementine_errors::SendTxError;
40pub use client::TxSenderClient;
41pub use tx_sender_types::{ActivatedWithOutpoint, ActivatedWithTxid};
42
43use bitcoin::taproot::TaprootSpendInfo;
44use bitcoin::{Amount, OutPoint, Sequence, Transaction, Weight};
45use bitcoincore_rpc::RpcApi;
46use clementine_config::tx_sender::TxSenderLimits;
47use clementine_errors::{BridgeError, ResultExt};
48use clementine_primitives::FeeRateKvb;
49
50pub type Result<T, E = SendTxError> = std::result::Result<T, E>;
51
52use clementine_utils::{FeePayingType, TxMetadata};
53use eyre::OptionExt;
54use signer::TxSenderSigningKey;
55
56/// Default sequence for transactions.
57pub const DEFAULT_SEQUENCE: Sequence = Sequence(0xFFFFFFFD);
58
59/// Once a tx/outpoint has been observed confirmed/spent for at least this many
60/// blocks, we treat it as final and skip further RPC re-checks.
61///
62/// IMPORTANT: for observations with confirmations < FINALITY_DEPTH we
63/// must assume they can be reorged and therefore keep re-checking.
64pub const DEFAULT_FINALITY_DEPTH: u32 = 5;
65
66/// Represents a spendable UTXO.
67#[derive(Debug, Clone)]
68pub struct SpendableUtxo {
69    pub outpoint: OutPoint,
70    pub txout: bitcoin::TxOut,
71    pub spend_info: Option<TaprootSpendInfo>,
72}
73
74/// Serialize a transaction for `fund_raw_transaction`, working around Bitcoin Core's
75/// deserialization bug for 0-input segwit transactions. fund_raw_transaction RPC
76/// gives deserialization error for 0-input transactions with segwit flag.
77///
78/// For transactions with no inputs, this uses legacy-style serialization
79/// (version, inputs, outputs, locktime) without segwit markers. Core will
80/// then add inputs and return a proper segwit transaction.
81pub(crate) fn serialize_tx_for_fund_raw(tx: &Transaction) -> Vec<u8> {
82    if tx.input.is_empty() {
83        use bitcoin::consensus::Encodable;
84
85        let mut buf = Vec::new();
86        // Serialize version
87        tx.version
88            .consensus_encode(&mut buf)
89            .expect("Failed to serialize version");
90        // Serialize inputs
91        tx.input
92            .consensus_encode(&mut buf)
93            .expect("Failed to serialize inputs");
94        // Serialize outputs
95        tx.output
96            .consensus_encode(&mut buf)
97            .expect("Failed to serialize outputs");
98        // Serialize locktime
99        tx.lock_time
100            .consensus_encode(&mut buf)
101            .expect("Failed to serialize locktime");
102
103        buf
104    } else {
105        bitcoin::consensus::encode::serialize(tx)
106    }
107}
108
109pub use db::{TxSenderDb, TxSenderDbTx, TxSenderTransaction};
110
111#[derive(Clone, Debug, Default)]
112pub struct MempoolConfig {
113    pub host: Option<String>,
114    pub endpoint: Option<String>,
115}
116
117/// Manages the process of sending Bitcoin transactions, including handling fee bumping
118/// strategies like Replace-By-Fee (RBF) and Child-Pays-For-Parent (CPFP).
119///
120/// It interacts with a Bitcoin Core RPC endpoint (`ExtendedBitcoinRpc`) to query network state
121/// (like fee rates) and submit transactions. It uses a `Database` to persist transaction
122/// state, track confirmation status, and manage associated data like fee payer UTXOs.
123/// The `Actor` provides signing capabilities for transactions controlled by this service.
124///
125#[derive(Clone)]
126pub struct TxSender {
127    signer: TxSenderSigningKey,
128    #[cfg(feature = "citrea")]
129    da_signer: TxSenderSigningKey,
130    pub rpc: clementine_extended_rpc::ExtendedBitcoinRpc,
131    pub db: TxSenderDb,
132    client: TxSenderClient,
133    pub network: bitcoin::Network,
134    pub tx_sender_limits: TxSenderLimits,
135    pub finality_depth: u32,
136    pub http_client: reqwest::Client,
137    mempool_config: MempoolConfig,
138    /// Whether to include unsafe UTXOs when funding transactions.
139    include_unsafe: bool,
140}
141
142impl std::fmt::Debug for TxSender {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        let mut builder = f.debug_struct("TxSender");
145        builder.field("signer", &self.signer);
146        #[cfg(feature = "citrea")]
147        builder.field("da_signer", &self.da_signer);
148        builder
149            .field("db", &self.db)
150            .field("network", &self.network)
151            .field("tx_sender_limits", &self.tx_sender_limits)
152            .field("include_unsafe", &self.include_unsafe)
153            .finish()
154    }
155}
156
157impl TxSender {
158    pub fn address(&self) -> &bitcoin::Address {
159        self.signer.address()
160    }
161
162    pub fn xonly_public_key(&self) -> bitcoin::XOnlyPublicKey {
163        self.signer.xonly_public_key()
164    }
165
166    /// Creates a new TxSender.
167    pub async fn new(
168        tx_sender_config: crate::config::TxSenderConfig,
169    ) -> std::result::Result<Self, BridgeError> {
170        let secret_key = tx_sender_config.secret_key;
171        let signer = TxSenderSigningKey::new(secret_key, tx_sender_config.network);
172        #[cfg(feature = "citrea")]
173        let da_signer = {
174            let da_key = tx_sender_config.private_da_key.unwrap_or(secret_key);
175            TxSenderSigningKey::new(da_key, tx_sender_config.network)
176        };
177        let rpc = clementine_extended_rpc::ExtendedBitcoinRpc::connect(
178            tx_sender_config.bitcoin_rpc.url.clone(),
179            tx_sender_config.bitcoin_rpc.user.clone(),
180            tx_sender_config.bitcoin_rpc.password.clone(),
181            None,
182        )
183        .await
184        .map_err(|e| BridgeError::Eyre(e.into()))?;
185
186        let db = TxSenderDb::connect(&tx_sender_config.postgres).await?;
187        let client = TxSenderClient::new(db.clone());
188
189        Ok(Self {
190            signer,
191            #[cfg(feature = "citrea")]
192            da_signer,
193            rpc,
194            db,
195            client,
196            network: tx_sender_config.network,
197            tx_sender_limits: tx_sender_config.limits,
198            finality_depth: tx_sender_config.finality_depth,
199            http_client: reqwest::Client::new(),
200            mempool_config: tx_sender_config.mempool,
201            include_unsafe: tx_sender_config.include_unsafe,
202        })
203    }
204
205    pub async fn get_fee_rate(&self) -> Result<FeeRateKvb, BridgeError> {
206        self.rpc
207            .get_fee_rate_kvb(
208                self.network,
209                &self.mempool_config.host,
210                &self.mempool_config.endpoint,
211                self.tx_sender_limits.mempool_fee_rate_multiplier,
212                self.tx_sender_limits.mempool_fee_rate_offset_sat_kvb,
213                self.tx_sender_limits.fee_rate_hard_cap,
214            )
215            .await
216            .map_err(|e| BridgeError::Eyre(e.into()))
217    }
218
219    /// Calculates the total fee required for a transaction package based on the fee bumping strategy.
220    ///
221    /// # Arguments
222    /// * `parent_tx_weight` - The weight of the main transaction being bumped.
223    /// * `num_fee_payer_utxos` - The number of fee payer UTXOs used (relevant for child tx size in CPFP).
224    /// * `fee_rate` - The target fee rate (sat/kvB).
225    /// * `fee_paying_type` - The strategy being used (CPFP or RBF).
226    ///
227    /// # Calculation Logic
228    /// *   **CPFP:** Calculates the weight of the hypothetical child transaction based on the
229    ///     number of fee payer inputs and standard P2TR output sizes. It then calculates the
230    ///     fee based on the *combined virtual size* (vbytes) of the parent and child transactions,
231    ///     as miners evaluate the package deal.
232    /// *   **RBF:** Calculates the weight of the replacement transaction itself (assuming inputs
233    ///     and potentially outputs change slightly). The fee is calculated based on the weight
234    ///     of this single replacement transaction.
235    ///
236    /// Reference for weight estimates: <https://bitcoin.stackexchange.com/a/116959>
237    fn calculate_required_fee(
238        parent_tx_weight: Weight,
239        num_fee_payer_utxos: usize,
240        fee_rate: FeeRateKvb,
241        fee_paying_type: FeePayingType,
242    ) -> Result<Amount> {
243        tracing::info!(
244            "Calculating required fee for {} fee payer utxos",
245            num_fee_payer_utxos
246        );
247        // Estimate the weight of the child transaction (for CPFP) or the RBF replacement.
248        // P2TR input witness adds ~57.5vbytes (230 WU). P2TR output adds 43 vbytes (172 WU).
249        // Base transaction overhead (version, locktime, input/output counts) ~ 10.5 vBytes (42 WU)
250        // Anchor input marker (OP_FALSE OP_RETURN ..) adds overhead. Exact WU TBD.
251        // For CPFP child: (N fee payer inputs) + (1 anchor input) + (1 change output)
252        // For RBF replacement: (N fee payer inputs) + (1 change output) - assuming it replaces a tx with an anchor.
253        let child_tx_weight = match fee_paying_type {
254            // CPFP Child: N fee payer inputs + 1 anchor input + 1 change output + base overhead.
255            // Approx WU: (230 * num_fee_payer_utxos) + 230 + 172 + base_overhead_wu
256            // Simplified calculation used here needs verification.
257            FeePayingType::CPFP => Weight::from_wu_usize(230 * num_fee_payer_utxos + 207 + 172),
258            // RBF Replacement: N fee payer inputs + 1 change output + base overhead.
259            // Assumes it replaces a tx of similar structure but potentially different inputs/fees.
260            // Simplified calculation used here needs verification.
261            FeePayingType::RBF | FeePayingType::RbfWtxidGrind => {
262                Weight::from_wu_usize(230 * num_fee_payer_utxos + 172)
263            }
264            FeePayingType::NoFunding => Weight::from_wu_usize(0),
265        };
266
267        // Calculate total weight for fee calculation.
268        // For CPFP, miners consider the effective fee rate over the combined *vbytes* of parent + child.
269        // For RBF, miners consider the fee rate of the single replacement transaction's weight.
270        let total_weight = match fee_paying_type {
271            FeePayingType::CPFP => Weight::from_vb_unchecked(
272                child_tx_weight.to_vbytes_ceil() + parent_tx_weight.to_vbytes_ceil(),
273            ),
274            FeePayingType::RBF | FeePayingType::RbfWtxidGrind => {
275                child_tx_weight + parent_tx_weight // Should likely just be the RBF tx weight? Check RBF rules.
276            }
277            FeePayingType::NoFunding => parent_tx_weight,
278        };
279
280        fee_rate
281            .fee_wu(total_weight)
282            .ok_or_eyre("Fee calculation overflow")
283            .map_err(Into::into)
284    }
285
286    pub fn is_p2a_anchor(&self, output: &bitcoin::TxOut) -> bool {
287        clementine_utils::address::is_p2a_anchor(output)
288    }
289
290    pub fn find_p2a_vout(&self, tx: &Transaction) -> Result<usize, BridgeError> {
291        tx.output
292            .iter()
293            .position(|output| self.is_p2a_anchor(output))
294            .ok_or_eyre("P2A anchor output not found in transaction")
295            .map_err(BridgeError::Eyre)
296    }
297
298    /// Fetches transactions that are eligible to be sent or bumped from
299    /// database based on the given fee rate and tip height. Then, places a send
300    /// transaction request to the Bitcoin based on the fee strategy.
301    ///
302    /// For each eligible transaction (`id`):
303    ///
304    /// 1.  **Send/Bump Main Tx:** Calls `send_tx` to either perform RBF or CPFP on the main
305    ///     transaction (`id`) using the `new_fee_rate`.
306    /// 2.  **Handle Errors:**
307    ///     - [`SendTxError::UnconfirmedFeePayerUTXOsLeft`]: Skips the current tx, waiting for fee
308    ///       payers to confirm.
309    ///     - [`SendTxError::InsufficientFeePayerAmount`]: Calls `create_fee_payer_utxo` to
310    ///       provision more funds for a future CPFP attempt.
311    ///     - Other errors are logged.
312    ///
313    /// # Arguments
314    /// * `new_fee_rate` - The current target fee rate based on network conditions.
315    /// * `current_tip_height` - The current blockchain height, used for time-lock checks.
316    /// * `is_tip_height_increased` - True if the tip height has increased since the last time we sent unconfirmed transactions.
317    #[tracing::instrument(skip_all, fields(new_fee_rate, current_tip_height))]
318    async fn try_to_send_unconfirmed_txs(
319        &self,
320        new_fee_rate: FeeRateKvb,
321        current_tip_height: u32,
322        is_tip_height_increased: bool,
323    ) -> Result<()> {
324        // get_sendable_txs doesn't return txs that we already sent in the past with >= fee rate to the current fee rate
325        // but if we have a new block height, but the tx is still not confirmed, we want to send it again anyway in case
326        // 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,
327        // cpfp tx will get evicted as v3 cpfp cannot have unconfirmed ancestors)
328        // if block height is increased, we use a dummy high fee rate to get all sendable txs
329        let get_sendable_txs_fee_rate = if is_tip_height_increased {
330            FeeRateKvb::from_sat_per_kvb(u32::MAX as u64)
331        } else {
332            new_fee_rate
333        };
334        let txs = self
335            .db
336            .get_sendable_txs(None, get_sendable_txs_fee_rate, current_tip_height)
337            .await
338            .map_to_eyre()?;
339
340        // bump fees of fee payer transactions that are unconfirmed
341        self.bump_fees_of_unconfirmed_fee_payer_txs(new_fee_rate)
342            .await?;
343
344        if !txs.is_empty() {
345            tracing::debug!("Trying to send {} sendable txs ", txs.len());
346        }
347
348        if std::env::var("TXSENDER_DBG_INACTIVE_TXS").is_ok() {
349            self.db
350                .debug_inactive_txs(get_sendable_txs_fee_rate, current_tip_height)
351                .await;
352        }
353
354        for id in txs {
355            // Update debug state
356            tracing::debug!(
357                try_to_send_id = id,
358                "Processing TX in try_to_send_unconfirmed_txs with fee rate {new_fee_rate}",
359            );
360
361            let (tx_metadata, tx, fee_paying_type, seen_at_height, rbf_signing_info) =
362                match self.db.get_try_to_send_tx(None, id).await {
363                    Ok(res) => res,
364                    Err(e) => {
365                        log_error_for_tx!(self.db, id, format!("Failed to get tx details: {}", e));
366                        continue;
367                    }
368                };
369
370            // Check if the transaction is already confirmed (only happens if it was confirmed after this loop started)
371            if let Some(seen_at_height) = seen_at_height {
372                tracing::debug!(
373                    try_to_send_id = id,
374                    "Transaction already confirmed (first seen at height {})",
375                    seen_at_height
376                );
377
378                // Update sending state
379                let _ = self
380                    .db
381                    .update_tx_debug_sending_state(id, "confirmed", true)
382                    .await;
383
384                continue;
385            }
386
387            // Get effective fee rate and block height to calculate adjusted fee rate
388            let (previous_effective_fee_rate, last_bump_block_height) =
389                match self.db.get_effective_fee_rate(None, id).await {
390                    Ok(res) => res,
391                    Err(e) => {
392                        log_error_for_tx!(
393                            self.db,
394                            id,
395                            format!("Failed to get effective fee rate: {}", e)
396                        );
397                        continue;
398                    }
399                };
400
401            // Calculate adjusted fee rate considering:
402            // 1. If new_fee_rate > previous_effective_fee_rate + min_bump_kvb, use max(new_fee_rate, previous_effective_fee_rate + incremental_fee_rate)
403            // 2. If tx has been stuck for 10+ blocks, bump with incremental fee
404            let adjusted_fee_rate = match self
405                .calculate_target_fee_rate(
406                    previous_effective_fee_rate,
407                    new_fee_rate,
408                    last_bump_block_height,
409                    current_tip_height,
410                )
411                .await
412            {
413                Ok(rate) => rate,
414                Err(e) => {
415                    log_error_for_tx!(
416                        self.db,
417                        id,
418                        format!("Failed to calculate adjusted fee rate: {}", e)
419                    );
420                    continue;
421                }
422            };
423
424            let result = match fee_paying_type {
425                // Send nonstandard transactions to testnet4 using the mempool.space accelerator.
426                // As mempool uses out of band payment, we don't need to do cpfp or rbf.
427                _ if self.network == bitcoin::Network::Testnet4
428                    && self.is_bridge_tx_nonstandard(&tx) =>
429                {
430                    self.send_testnet4_nonstandard_tx(&tx, id).await
431                }
432                FeePayingType::CPFP => {
433                    self.send_cpfp_tx(id, tx, tx_metadata, adjusted_fee_rate, current_tip_height)
434                        .await
435                }
436                FeePayingType::RBF | FeePayingType::RbfWtxidGrind => {
437                    self.send_rbf_tx(
438                        id,
439                        tx,
440                        tx_metadata,
441                        adjusted_fee_rate,
442                        rbf_signing_info,
443                        current_tip_height,
444                        fee_paying_type == FeePayingType::RbfWtxidGrind,
445                    )
446                    .await
447                }
448                FeePayingType::NoFunding => self.send_no_funding_tx(id, tx, tx_metadata).await,
449            };
450
451            if let Err(e) = result {
452                log_error_for_tx!(self.db, id, format!("Failed to send tx: {:?}", e));
453            }
454        }
455
456        Ok(())
457    }
458    pub fn client(&self) -> TxSenderClient {
459        self.client.clone()
460    }
461
462    /// Calculates the effective fee rate for a transaction, considering previous effective fee rate
463    /// and minimum incremental fee requirements.
464    ///
465    /// This function implements the logic for fee bumping that ensures:
466    /// 1. If no previous effective fee rate exists, use the new fee rate
467    /// 2. If previous effective fee rate exists, use the maximum of:
468    ///    - The new fee rate
469    ///    - Previous effective fee rate + minimum incremental fee rate
470    ///
471    /// # Arguments
472    /// * `previous_effective_fee_rate` - The previous effective fee rate (if any)
473    /// * `new_fee_rate` - The target fee rate for the new attempt
474    /// * `last_bump_block_height` - The block height when the last fee bump was done (if any)
475    /// * `current_tip_height` - The current blockchain tip height
476    ///
477    /// # Returns
478    /// The effective fee rate to use (in sat/kvB), capped by the hard cap from config
479    pub async fn calculate_target_fee_rate(
480        &self,
481        previous_effective_fee_rate: Option<FeeRateKvb>,
482        new_fee_rate: FeeRateKvb,
483        last_bump_block_height: Option<u32>,
484        current_tip_height: u32,
485    ) -> Result<FeeRateKvb> {
486        // Hard cap from config (in sat/vB), convert to sat/kvB
487        let hard_cap = FeeRateKvb::from_sat_per_vb(self.tx_sender_limits.fee_rate_hard_cap)
488            .expect("fee_rate_hard_cap should be valid");
489
490        let Some(previous_rate) = previous_effective_fee_rate else {
491            // No previous effective fee rate, use the new fee rate (capped)
492            return Ok(std::cmp::min(new_fee_rate, hard_cap));
493        };
494
495        // Check if the tx has been stuck for 10+ blocks
496        let is_stuck = match last_bump_block_height {
497            Some(block_height) => {
498                current_tip_height.saturating_sub(block_height)
499                    >= self.tx_sender_limits.fee_bump_after_blocks
500            }
501            None => false,
502        };
503
504        // Get minimum fee increment rate from node for BIP125 compliance. Returned value is in BTC/kvB
505        let incremental_fee_rate = self
506            .rpc
507            .get_network_info()
508            .await
509            .map_err(|e| eyre::eyre!(e))?
510            .incremental_fee;
511        let incremental_fee_rate_sat_per_kvb = incremental_fee_rate.to_sat();
512        let incremental_fee_rate = FeeRateKvb::from_sat_per_kvb(incremental_fee_rate_sat_per_kvb);
513
514        // Minimum bump fee rate required by BIP125
515        let min_bump_feerate =
516            previous_rate.to_sat_per_kvb() + incremental_fee_rate.to_sat_per_kvb();
517        let effective_feerate = std::cmp::max(new_fee_rate.to_sat_per_kvb(), min_bump_feerate);
518
519        // If new fee rate is higher than previous, only bump when effective increment clears min_bump_kvb
520        if new_fee_rate.to_sat_per_kvb() > previous_rate.to_sat_per_kvb() && !is_stuck {
521            // Check the increment based on the requested fee rate, not the BIP125-adjusted one
522            let requested_increment = new_fee_rate
523                .to_sat_per_kvb()
524                .saturating_sub(previous_rate.to_sat_per_kvb());
525
526            // if the requested increment is less than min_bump_kvb, do not bump
527            if requested_increment < self.tx_sender_limits.min_bump_kvb {
528                return Ok(previous_rate);
529            }
530
531            let result = FeeRateKvb::from_sat_per_kvb(effective_feerate);
532            return Ok(std::cmp::min(result, hard_cap));
533        }
534        // If the tx is stuck for 10+ blocks, force a fee bump
535        else if is_stuck {
536            let result = FeeRateKvb::from_sat_per_kvb(effective_feerate);
537            let capped_result = std::cmp::min(result, hard_cap);
538
539            tracing::debug!(
540                "TX stuck for at least {} blocks, forcing fee bump from {} to {} sat/kvB (hard cap: {} sat/kvB)",
541                self.tx_sender_limits.fee_bump_after_blocks,
542                previous_rate.to_sat_per_kvb(),
543                capped_result.to_sat_per_kvb(),
544                hard_cap.to_sat_per_kvb()
545            );
546
547            return Ok(capped_result);
548        }
549        Ok(previous_rate)
550    }
551
552    /// Sends a transaction that is already fully funded and signed.
553    ///
554    /// This function is used for transactions that do not require fee bumping strategies
555    /// like RBF or CPFP. The transaction is submitted directly to the Bitcoin network
556    /// without any modifications.
557    ///
558    /// # Arguments
559    /// * `try_to_send_id` - The database ID tracking this send attempt.
560    /// * `tx` - The fully funded and signed transaction ready for broadcast.
561    /// * `tx_metadata` - Optional metadata associated with the transaction for debugging.
562    ///
563    /// # Behavior
564    /// 1. Attempts to broadcast the transaction using `send_raw_transaction` RPC.
565    /// 2. Updates the database with success/failure state for debugging purposes.
566    /// 3. Logs appropriate messages for monitoring and troubleshooting.
567    ///
568    /// # Returns
569    /// * `Ok(())` - If the transaction was successfully broadcast.
570    /// * `Err(SendTxError)` - If the broadcast failed.
571    #[tracing::instrument(skip_all, fields(try_to_send_id, tx_meta=?tx_metadata))]
572    pub async fn send_no_funding_tx(
573        &self,
574        try_to_send_id: u32,
575        tx: Transaction,
576        tx_metadata: Option<TxMetadata>,
577    ) -> Result<()> {
578        match self.rpc.send_raw_transaction(&tx).await {
579            Ok(sent_txid) => {
580                tracing::debug!(
581                    try_to_send_id,
582                    "Successfully sent no funding tx with txid {}",
583                    sent_txid
584                );
585                let _ = self
586                    .db
587                    .update_tx_debug_sending_state(try_to_send_id, "no_funding_send_success", true)
588                    .await;
589            }
590            Err(e) => {
591                let err_str = e.to_string();
592                if rpc_errors::is_rejecting_replacement_error(&err_str) {
593                    tracing::debug!(
594                        try_to_send_id,
595                        "No funding tx rejected (tx already in mempool): {err_str}"
596                    );
597                    return Ok(());
598                } else {
599                    tracing::error!(
600                        "Failed to send no funding tx with try_to_send_id: {try_to_send_id:?} and metadata: {tx_metadata:?}"
601                    );
602                    log_error_for_tx!(
603                        self.db,
604                        try_to_send_id,
605                        format!("send_raw_transaction error for no funding tx: {err_str}")
606                    );
607                }
608                let _ = self
609                    .db
610                    .update_tx_debug_sending_state(try_to_send_id, "no_funding_send_failed", true)
611                    .await;
612                return Err(SendTxError::Other(eyre::eyre!(e)));
613            }
614        };
615
616        Ok(())
617    }
618}