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}