subcoin_rpc_bitcoind/
rawtx.rs

1//! Bitcoin Core compatible raw transaction RPC methods.
2//!
3//! Implements: getrawtransaction, sendrawtransaction, decoderawtransaction
4
5use crate::error::Error;
6use crate::types::{GetRawTransaction, ScriptPubKey, ScriptSig, Vin, Vout};
7use bitcoin::consensus::encode::{deserialize_hex, serialize_hex};
8use bitcoin::{Address, Amount, BlockHash, Network, OutPoint, Transaction, Txid};
9use jsonrpsee::proc_macros::rpc;
10use sc_client_api::{AuxStore, BlockBackend, HeaderBackend};
11use sp_runtime::traits::Block as BlockT;
12use std::marker::PhantomData;
13use std::sync::Arc;
14use subcoin_network::{NetworkApi, SendTransactionResult};
15use subcoin_primitives::{BitcoinTransactionAdapter, TransactionIndex, TxPosition};
16use subcoin_script::solve;
17
18/// Bitcoin Core compatible raw transaction RPC API.
19#[rpc(client, server)]
20pub trait RawTransactionApi {
21    /// Return the raw transaction data.
22    ///
23    /// By default, this call only returns a transaction if it is in the mempool.
24    /// If -txindex is enabled, it also returns the transaction if it is found in a block.
25    ///
26    /// If verbose is false (default), returns hex-encoded serialized transaction.
27    /// If verbose is true, returns a JSON object with transaction info.
28    #[method(name = "getrawtransaction")]
29    async fn get_raw_transaction(
30        &self,
31        txid: Txid,
32        verbose: Option<bool>,
33        blockhash: Option<BlockHash>,
34    ) -> Result<serde_json::Value, Error>;
35
36    /// Submit a raw transaction (serialized, hex-encoded) to local node and network.
37    ///
38    /// Returns the transaction hash in hex.
39    #[method(name = "sendrawtransaction")]
40    async fn send_raw_transaction(
41        &self,
42        hexstring: String,
43        maxfeerate: Option<f64>,
44    ) -> Result<SendTransactionResult, Error>;
45
46    /// Return a JSON object representing the serialized, hex-encoded transaction.
47    #[method(name = "decoderawtransaction", blocking)]
48    fn decode_raw_transaction(
49        &self,
50        hexstring: String,
51        iswitness: Option<bool>,
52    ) -> Result<serde_json::Value, Error>;
53}
54
55/// Bitcoin Core compatible raw transaction RPC implementation.
56pub struct RawTransaction<Block, Client, TransactionAdapter> {
57    client: Arc<Client>,
58    network_api: Arc<dyn NetworkApi>,
59    transaction_index: Arc<dyn TransactionIndex + Send + Sync>,
60    network: Network,
61    _phantom: PhantomData<(Block, TransactionAdapter)>,
62}
63
64impl<Block, Client, TransactionAdapter> RawTransaction<Block, Client, TransactionAdapter>
65where
66    Block: BlockT + 'static,
67    Client: HeaderBackend<Block> + BlockBackend<Block> + AuxStore + 'static,
68{
69    /// Creates a new instance of [`RawTransaction`].
70    pub fn new(
71        client: Arc<Client>,
72        network_api: Arc<dyn NetworkApi>,
73        transaction_index: Arc<dyn TransactionIndex + Send + Sync>,
74        network: Network,
75    ) -> Self {
76        Self {
77            client,
78            network_api,
79            transaction_index,
80            network,
81            _phantom: Default::default(),
82        }
83    }
84
85    fn best_number(&self) -> u32 {
86        use sp_runtime::traits::SaturatedConversion;
87        self.client.info().best_number.saturated_into()
88    }
89
90    fn calculate_confirmations(&self, block_height: u32) -> i32 {
91        let best = self.best_number();
92        if block_height > best {
93            0
94        } else {
95            (best - block_height + 1) as i32
96        }
97    }
98
99    fn tx_to_get_raw_transaction(
100        &self,
101        tx: &Transaction,
102        block_hash: Option<BlockHash>,
103        block_height: Option<u32>,
104        block_time: Option<u32>,
105    ) -> GetRawTransaction {
106        let txid = tx.compute_txid();
107        let wtxid = tx.compute_wtxid();
108
109        // Calculate sizes
110        let size = tx.total_size() as u32;
111        let vsize = tx.vsize() as u32;
112        let weight = tx.weight().to_wu() as u32;
113
114        // Convert inputs
115        let vin: Vec<Vin> = tx
116            .input
117            .iter()
118            .map(|input| {
119                if input.previous_output == OutPoint::null() {
120                    // Coinbase transaction
121                    Vin {
122                        txid: None,
123                        vout: None,
124                        script_sig: None,
125                        txinwitness: if !input.witness.is_empty() {
126                            Some(input.witness.iter().map(hex::encode).collect())
127                        } else {
128                            None
129                        },
130                        sequence: input.sequence.0,
131                        coinbase: Some(hex::encode(input.script_sig.as_bytes())),
132                    }
133                } else {
134                    Vin {
135                        txid: Some(input.previous_output.txid),
136                        vout: Some(input.previous_output.vout),
137                        script_sig: Some(ScriptSig {
138                            asm: input.script_sig.to_asm_string(),
139                            hex: hex::encode(input.script_sig.as_bytes()),
140                        }),
141                        txinwitness: if !input.witness.is_empty() {
142                            Some(input.witness.iter().map(hex::encode).collect())
143                        } else {
144                            None
145                        },
146                        sequence: input.sequence.0,
147                        coinbase: None,
148                    }
149                }
150            })
151            .collect();
152
153        // Convert outputs
154        let vout: Vec<Vout> = tx
155            .output
156            .iter()
157            .enumerate()
158            .map(|(n, output)| {
159                let script = &output.script_pubkey;
160                let address = Address::from_script(script, self.network)
161                    .ok()
162                    .map(|a| a.to_string());
163
164                let script_type = solve(script).script_type();
165
166                Vout {
167                    value: Amount::from_sat(output.value.to_sat()).to_btc(),
168                    n: n as u32,
169                    script_pub_key: ScriptPubKey {
170                        asm: script.to_asm_string(),
171                        hex: hex::encode(script.as_bytes()),
172                        script_type: script_type.to_string(),
173                        address,
174                    },
175                }
176            })
177            .collect();
178
179        let confirmations = block_height.map(|h| self.calculate_confirmations(h));
180
181        GetRawTransaction {
182            in_active_chain: block_hash.map(|_| true),
183            hex: serialize_hex(tx),
184            txid,
185            hash: Txid::from_raw_hash(*wtxid.as_raw_hash()),
186            size,
187            vsize,
188            weight,
189            version: tx.version.0,
190            locktime: tx.lock_time.to_consensus_u32(),
191            vin,
192            vout,
193            blockhash: block_hash,
194            confirmations,
195            blocktime: block_time,
196            time: block_time,
197        }
198    }
199}
200
201#[async_trait::async_trait]
202impl<Block, Client, TransactionAdapter> RawTransactionApiServer
203    for RawTransaction<Block, Client, TransactionAdapter>
204where
205    Block: BlockT + 'static,
206    Client: HeaderBackend<Block> + BlockBackend<Block> + AuxStore + 'static,
207    TransactionAdapter: BitcoinTransactionAdapter<Block> + Send + Sync + 'static,
208{
209    async fn get_raw_transaction(
210        &self,
211        txid: Txid,
212        verbose: Option<bool>,
213        _blockhash: Option<BlockHash>,
214    ) -> Result<serde_json::Value, Error> {
215        // Try to get from mempool first
216        if let Some(tx) = self.network_api.get_transaction(txid).await {
217            if verbose.unwrap_or(false) {
218                let result = self.tx_to_get_raw_transaction(&tx, None, None, None);
219                return Ok(serde_json::to_value(result)?);
220            } else {
221                return Ok(serde_json::Value::String(serialize_hex(&tx)));
222            }
223        }
224
225        // Try to get from blockchain using tx index
226        let Some(TxPosition {
227            block_number,
228            index,
229        }) = self.transaction_index.tx_index(txid)?
230        else {
231            return Err(Error::TransactionNotFound);
232        };
233
234        // Get the block
235        let substrate_block_hash = self
236            .client
237            .hash(block_number.into())?
238            .ok_or(Error::BlockNotFound)?;
239
240        let substrate_block = self
241            .client
242            .block(substrate_block_hash)?
243            .ok_or(Error::BlockNotFound)?
244            .block;
245
246        let bitcoin_block =
247            subcoin_primitives::convert_to_bitcoin_block::<Block, TransactionAdapter>(
248                substrate_block,
249            )
250            .map_err(Error::Header)?;
251
252        let tx = bitcoin_block
253            .txdata
254            .into_iter()
255            .nth(index as usize)
256            .ok_or(Error::TransactionNotFound)?;
257
258        let block_hash = bitcoin_block.header.block_hash();
259
260        if verbose.unwrap_or(false) {
261            let result = self.tx_to_get_raw_transaction(
262                &tx,
263                Some(block_hash),
264                Some(block_number),
265                Some(bitcoin_block.header.time),
266            );
267            Ok(serde_json::to_value(result)?)
268        } else {
269            Ok(serde_json::Value::String(serialize_hex(&tx)))
270        }
271    }
272
273    async fn send_raw_transaction(
274        &self,
275        hexstring: String,
276        _maxfeerate: Option<f64>,
277    ) -> Result<SendTransactionResult, Error> {
278        if !self.network_api.enabled() {
279            return Err(Error::NetworkUnavailable);
280        }
281
282        let tx: Transaction = deserialize_hex(&hexstring)?;
283        Ok(self.network_api.send_transaction(tx).await)
284    }
285
286    fn decode_raw_transaction(
287        &self,
288        hexstring: String,
289        _iswitness: Option<bool>,
290    ) -> Result<serde_json::Value, Error> {
291        let tx: Transaction = deserialize_hex(&hexstring)?;
292        let result = self.tx_to_get_raw_transaction(&tx, None, None, None);
293        Ok(serde_json::to_value(result)?)
294    }
295}