clementine_tx_sender/
nonstandard.rs

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