clementine_tx_sender/
nonstandard.rs

1use crate::{log_error_for_tx, TxSender};
2use bitcoin::consensus::serialize;
3use bitcoin::Transaction;
4use clementine_errors::SendTxError;
5use std::collections::HashMap;
6
7impl TxSender {
8    /// Checks if a bridge transaction is nonstandard. Keep in mind that these are not all cases where a transaction is nonstandard.
9    /// We only check non-standard types that clementine generates by default in non-standard mode.
10    /// Currently checks these cases:
11    /// 1. The transaction contains 0 sat non-anchor (only checks our specific anchor address)
12    ///    and non-op return output.
13    /// 2. The transaction weight is bigger than 400k
14    ///
15    /// Arguments:
16    /// * `tx` - The transaction to check.
17    ///
18    /// Returns:
19    /// * `true` if the transaction is nonstandard, `false` otherwise.
20    pub fn is_bridge_tx_nonstandard(&self, tx: &Transaction) -> bool {
21        tx.output.iter().any(|output| {
22            output.value.to_sat() == 0
23                && !self.is_p2a_anchor(output)
24                && !output.script_pubkey.is_op_return()
25        }) || tx.weight().to_wu() > 400_000
26    }
27
28    /// Sends a nonstandard transaction to testnet4 using the mempool.space accelerator.
29    ///
30    /// Arguments:
31    /// * `tx` - The transaction to send.
32    ///
33    /// Returns:
34    /// * `Ok(())` if the transaction is sent successfully to the accelerator.
35    /// * `Err(SendTxError)` if the transaction is not sent successfully to the accelerator.
36    ///
37    /// Note: Mempool.space accelerator doesn't accept transactions if:
38    ///     - At least one of the transaction's inputs is signed with either the SIGHASH_NONE or SIGHASH_ANYONECANPAY flag, which may allow a third party to replace the transaction.
39    ///     - The number of signature operations multiplied by 20 exceeds the transaction's weight.
40    /// [Mempool Space API docs](https://mempool.space/docs/api/rest)
41    /// [Mempool Space Accelerator FAQ](https://mempool.space/accelerator/faq)
42    pub async fn send_testnet4_nonstandard_tx(
43        &self,
44        tx: &Transaction,
45        try_to_send_id: u32,
46    ) -> Result<(), SendTxError> {
47        // Get API key from environment variable
48        let api_key = std::env::var("MEMPOOL_SPACE_API_KEY").map_err(|_| {
49            SendTxError::Other(eyre::eyre!(
50                "MEMPOOL_SPACE_API_KEY environment variable not set, cannot send nonstandard transactions to testnet4"
51            ))
52        })?;
53
54        // first check if the transaction is already submitted to the accelerator
55        let txid = tx.compute_txid();
56        let response = self
57            .http_client
58            .get("https://mempool.space/api/v1/services/accelerator/testnet4/accelerations")
59            .header("X-Mempool-Auth", api_key.clone())
60            .send()
61            .await
62            .map_err(|e| {
63                SendTxError::NetworkError(format!(
64                    "Failed to get transaction history from mempool.space accelerator: {e}"
65                ))
66            })?;
67        if response.status().is_success() {
68            // Try to parse the response, if for some reason response can't be parsed,
69            // don't return errors, so we continue with sending the transaction to the accelerator.
70            let text = response.text().await.unwrap_or_default();
71            let previously_sent_txs: serde_json::Value =
72                serde_json::from_str(&text).unwrap_or_else(|_| serde_json::json!([]));
73
74            // try to parse the response
75            for tx in previously_sent_txs.as_array().unwrap_or(&vec![]) {
76                let Some(response_txid) = tx.get("txid").and_then(|v| v.as_str()) else {
77                    continue;
78                };
79
80                if response_txid == txid.to_string() && tx["status"] != "failed" {
81                    tracing::debug!(
82                        "Found {:?} with status {:?} in accelerator transaction history",
83                        txid,
84                        tx["status"]
85                    );
86                    let _ = self
87                        .db
88                        .update_tx_debug_sending_state(
89                            try_to_send_id,
90                            "nonstandard_testnet4_send_submitted",
91                            false,
92                        )
93                        .await;
94                    return Ok(()); // Already submitted
95                }
96            }
97        } else {
98            let status = response.status();
99            let error_text = response.text().await.unwrap_or_default();
100            return Err(SendTxError::NetworkError(format!(
101                "Accelerator returned HTTP {status}: {error_text}"
102            )));
103        }
104
105        // Serialize transaction to hex
106        let tx_hex = hex::encode(serialize(tx));
107
108        // Prepare form data
109        let mut form_data = HashMap::new();
110        form_data.insert("txInput", tx_hex);
111        form_data.insert("label", format!("clementine-{}", tx.compute_txid()));
112
113        // Make the API request
114        let response = self
115            .http_client
116            .post("https://mempool.space/api/v1/services/accelerator/testnet4/accelerate/hex")
117            .header("X-Mempool-Auth", api_key)
118            .form(&form_data)
119            .send()
120            .await
121            .map_err(|e| {
122                SendTxError::NetworkError(format!(
123                    "Failed to submit transaction to mempool.space accelerator: {e}"
124                ))
125            })?;
126
127        // Check if the request was successful
128        if response.status().is_success() {
129            let response_text = response
130                .text()
131                .await
132                .map_err(|e| SendTxError::NetworkError(format!("Failed to read response: {e}")))?;
133
134            tracing::info!(
135                "Successfully submitted nonstandard transaction {:?} to mempool.space testnet4 accelerator: {}",
136                txid,
137                response_text
138            );
139
140            let _ = self
141                .db
142                .update_tx_debug_sending_state(
143                    try_to_send_id,
144                    "nonstandard_testnet4_send_success",
145                    true,
146                )
147                .await;
148
149            Ok(())
150        } else {
151            let status = response.status();
152            let error_text = response
153                .text()
154                .await
155                .unwrap_or_else(|_| "Unknown error".to_string());
156
157            log_error_for_tx!(
158                self.db,
159                try_to_send_id,
160                format!(
161                    "Failed to submit transaction to mempool.space. Status: {}, Error: {}",
162                    status, error_text
163                )
164            );
165            let _ = self
166                .db
167                .update_tx_debug_sending_state(
168                    try_to_send_id,
169                    "nonstandard_testnet4_send_failed",
170                    true,
171                )
172                .await;
173
174            Err(SendTxError::NetworkError(format!(
175                "Failed to submit transaction to mempool.space. Status: {status}, Error: {error_text}"
176            )))
177        }
178    }
179}