subcoin_rpc_bitcoind/
blockchain.rs

1//! Bitcoin Core compatible blockchain RPC methods.
2//!
3//! Implements: getblock, getblockhash, getblockheader, getblockchaininfo, getbestblockhash
4
5use crate::error::Error;
6use crate::types::{GetBlock, GetBlockHeader, GetBlockchainInfo};
7use bitcoin::consensus::Encodable;
8use bitcoin::{Block as BitcoinBlock, BlockHash};
9use jsonrpsee::proc_macros::rpc;
10use sc_client_api::{AuxStore, BlockBackend, HeaderBackend};
11use sp_runtime::traits::{Block as BlockT, SaturatedConversion};
12use std::marker::PhantomData;
13use std::sync::Arc;
14use subcoin_primitives::{BackendExt, BitcoinTransactionAdapter, convert_to_bitcoin_block};
15
16/// Bitcoin Core compatible blockchain RPC API.
17#[rpc(client, server)]
18pub trait BlockchainApi {
19    /// Returns the hash of the best (tip) block in the most-work fully-validated chain.
20    #[method(name = "getbestblockhash", blocking)]
21    fn get_best_block_hash(&self) -> Result<BlockHash, Error>;
22
23    /// Returns an object containing various state info regarding blockchain processing.
24    #[method(name = "getblockchaininfo", blocking)]
25    fn get_blockchain_info(&self) -> Result<GetBlockchainInfo, Error>;
26
27    /// Returns the height of the most-work fully-validated chain.
28    #[method(name = "getblockcount", blocking)]
29    fn get_block_count(&self) -> Result<u32, Error>;
30
31    /// Returns hash of block in best-block-chain at height provided.
32    #[method(name = "getblockhash", blocking)]
33    fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error>;
34
35    /// Returns block data.
36    ///
37    /// If verbosity is 0, returns a hex-encoded serialized block.
38    /// If verbosity is 1 (default), returns a JSON object with block info and txids.
39    /// If verbosity is 2, returns a JSON object with block info and full tx data.
40    #[method(name = "getblock", blocking)]
41    fn get_block(
42        &self,
43        blockhash: BlockHash,
44        verbosity: Option<u8>,
45    ) -> Result<serde_json::Value, Error>;
46
47    /// Returns information about a block header.
48    ///
49    /// If verbose is false (default=true), returns a hex-encoded serialized header.
50    /// If verbose is true, returns a JSON object with header info.
51    #[method(name = "getblockheader", blocking)]
52    fn get_block_header(
53        &self,
54        blockhash: BlockHash,
55        verbose: Option<bool>,
56    ) -> Result<serde_json::Value, Error>;
57}
58
59/// Calculate difficulty from compact bits representation.
60fn difficulty_from_bits(bits: u32) -> f64 {
61    // Bitcoin difficulty calculation from compact bits
62    // Difficulty 1 target is 0x00000000FFFF... (256-bit), but we use a simplified formula
63    let mantissa = bits & 0x00ff_ffff;
64    let exponent = (bits >> 24) as i32;
65
66    if mantissa == 0 {
67        return 0.0;
68    }
69
70    // difficulty = difficulty_1_target / current_target
71    // Using the simplified formula from Bitcoin Core
72    let shift = 8 * (exponent - 3);
73    let diff = (0x0000ffff_u64 as f64) / (mantissa as f64) * 2f64.powi(-shift);
74    if diff > 0.0 { diff } else { 0.0 }
75}
76
77/// Bitcoin Core compatible blockchain RPC implementation.
78pub struct Blockchain<Block, Client, TransactionAdapter> {
79    client: Arc<Client>,
80    network: bitcoin::Network,
81    _phantom: PhantomData<(Block, TransactionAdapter)>,
82}
83
84impl<Block, Client, TransactionAdapter> Blockchain<Block, Client, TransactionAdapter>
85where
86    Block: BlockT + 'static,
87    Client: HeaderBackend<Block> + BlockBackend<Block> + AuxStore + 'static,
88{
89    /// Creates a new instance of [`Blockchain`].
90    pub fn new(client: Arc<Client>, network: bitcoin::Network) -> Self {
91        Self {
92            client,
93            network,
94            _phantom: Default::default(),
95        }
96    }
97
98    fn get_bitcoin_block(&self, block_hash: BlockHash) -> Result<BitcoinBlock, Error>
99    where
100        TransactionAdapter: BitcoinTransactionAdapter<Block>,
101    {
102        let substrate_block_hash = self
103            .client
104            .substrate_block_hash_for(block_hash)
105            .ok_or(Error::BlockNotFound)?;
106
107        let substrate_block = self
108            .client
109            .block(substrate_block_hash)?
110            .ok_or(Error::BlockNotFound)?
111            .block;
112
113        convert_to_bitcoin_block::<Block, TransactionAdapter>(substrate_block)
114            .map_err(Error::Header)
115    }
116
117    fn get_block_number(&self, block_hash: BlockHash) -> Result<u32, Error> {
118        self.client
119            .block_number(block_hash)
120            .ok_or(Error::BlockNotFound)
121    }
122
123    fn best_number(&self) -> u32 {
124        self.client.info().best_number.saturated_into()
125    }
126
127    fn calculate_confirmations(&self, block_height: u32) -> i32 {
128        let best = self.best_number();
129        if block_height > best {
130            0
131        } else {
132            (best - block_height + 1) as i32
133        }
134    }
135
136    fn get_next_block_hash(&self, block_height: u32) -> Option<BlockHash> {
137        self.client.block_hash(block_height + 1)
138    }
139
140    fn block_to_get_block(&self, block: &BitcoinBlock, height: u32) -> GetBlock {
141        let header = &block.header;
142        let block_hash = header.block_hash();
143        let txids = block.txdata.iter().map(|tx| tx.compute_txid()).collect();
144
145        // Calculate block size and weight
146        let mut size_data = Vec::new();
147        block
148            .consensus_encode(&mut size_data)
149            .expect("encoding should not fail");
150        let size = size_data.len() as u32;
151
152        // Stripped size (without witness) - calculate from weight
153        let weight = block.weight().to_wu() as u32;
154        // weight = base_size * 3 + total_size, so base_size = (weight - size) / 3 + (size - size) ... simplified
155        // Actually: weight = (size - witness_size) * 4 + witness_size = size * 4 - witness_size * 3
156        // So stripped_size = (4 * size - weight) / 3
157        let strippedsize = (4 * size).saturating_sub(weight) / 3;
158
159        GetBlock {
160            hash: block_hash,
161            confirmations: self.calculate_confirmations(height),
162            size,
163            strippedsize,
164            weight,
165            height,
166            version: header.version.to_consensus(),
167            version_hex: format!("{:08x}", header.version.to_consensus()),
168            merkleroot: header.merkle_root.to_string(),
169            tx: txids,
170            time: header.time,
171            mediantime: header.time, // TODO: Calculate actual median time
172            nonce: header.nonce,
173            bits: format!("{:08x}", header.bits.to_consensus()),
174            difficulty: difficulty_from_bits(header.bits.to_consensus()),
175            chainwork: "0".to_string(), // TODO: Calculate chainwork
176            n_tx: block.txdata.len() as u32,
177            previousblockhash: if height > 0 {
178                Some(header.prev_blockhash)
179            } else {
180                None
181            },
182            nextblockhash: self.get_next_block_hash(height),
183        }
184    }
185
186    fn header_to_get_block_header(
187        &self,
188        header: &bitcoin::block::Header,
189        height: u32,
190        n_tx: u32,
191    ) -> GetBlockHeader {
192        let block_hash = header.block_hash();
193
194        GetBlockHeader {
195            hash: block_hash,
196            confirmations: self.calculate_confirmations(height),
197            height,
198            version: header.version.to_consensus(),
199            version_hex: format!("{:08x}", header.version.to_consensus()),
200            merkleroot: header.merkle_root.to_string(),
201            time: header.time,
202            mediantime: header.time, // TODO: Calculate actual median time
203            nonce: header.nonce,
204            bits: format!("{:08x}", header.bits.to_consensus()),
205            difficulty: difficulty_from_bits(header.bits.to_consensus()),
206            chainwork: "0".to_string(), // TODO: Calculate chainwork
207            n_tx,
208            previousblockhash: if height > 0 {
209                Some(header.prev_blockhash)
210            } else {
211                None
212            },
213            nextblockhash: self.get_next_block_hash(height),
214        }
215    }
216}
217
218#[async_trait::async_trait]
219impl<Block, Client, TransactionAdapter> BlockchainApiServer
220    for Blockchain<Block, Client, TransactionAdapter>
221where
222    Block: BlockT + 'static,
223    Client: HeaderBackend<Block> + BlockBackend<Block> + AuxStore + 'static,
224    TransactionAdapter: BitcoinTransactionAdapter<Block> + Send + Sync + 'static,
225{
226    fn get_best_block_hash(&self) -> Result<BlockHash, Error> {
227        let best_substrate_hash = self.client.info().best_hash;
228        self.client
229            .bitcoin_block_hash_for(best_substrate_hash)
230            .ok_or_else(|| Error::Other("Best block hash not found".to_string()))
231    }
232
233    fn get_blockchain_info(&self) -> Result<GetBlockchainInfo, Error> {
234        let best_hash = self.get_best_block_hash()?;
235        let best_number = self.best_number();
236
237        // Get difficulty from best block header
238        let block = self.get_bitcoin_block(best_hash)?;
239        let difficulty = difficulty_from_bits(block.header.bits.to_consensus());
240
241        Ok(GetBlockchainInfo {
242            chain: self.network.to_core_arg().to_string(),
243            blocks: best_number,
244            headers: best_number, // Same as blocks for full node
245            bestblockhash: best_hash,
246            difficulty,
247            verificationprogress: 1.0, // Fully synced
248            initialblockdownload: false,
249            chainwork: "0".to_string(), // TODO: Calculate chainwork
250            size_on_disk: 0,            // TODO: Get actual disk usage
251            pruned: false,
252            warnings: vec![],
253        })
254    }
255
256    fn get_block_count(&self) -> Result<u32, Error> {
257        Ok(self.best_number())
258    }
259
260    fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
261        self.client
262            .block_hash(height)
263            .ok_or(Error::BlockHeightOutOfRange)
264    }
265
266    fn get_block(
267        &self,
268        blockhash: BlockHash,
269        verbosity: Option<u8>,
270    ) -> Result<serde_json::Value, Error> {
271        let block = self.get_bitcoin_block(blockhash)?;
272        let height = self.get_block_number(blockhash)?;
273
274        match verbosity.unwrap_or(1) {
275            0 => {
276                // Return hex-encoded serialized block
277                let mut data = Vec::new();
278                block
279                    .consensus_encode(&mut data)
280                    .map_err(|e| Error::Other(format!("Failed to encode block: {e}")))?;
281                Ok(serde_json::Value::String(hex::encode(data)))
282            }
283            1 => {
284                // Return JSON with txids
285                let get_block = self.block_to_get_block(&block, height);
286                Ok(serde_json::to_value(get_block)?)
287            }
288            2 => {
289                // Return JSON with full transaction data
290                // TODO: Implement full transaction decoding
291                let get_block = self.block_to_get_block(&block, height);
292                Ok(serde_json::to_value(get_block)?)
293            }
294            _ => Err(Error::Other("Invalid verbosity level".to_string())),
295        }
296    }
297
298    fn get_block_header(
299        &self,
300        blockhash: BlockHash,
301        verbose: Option<bool>,
302    ) -> Result<serde_json::Value, Error> {
303        let block = self.get_bitcoin_block(blockhash)?;
304        let height = self.get_block_number(blockhash)?;
305
306        if verbose.unwrap_or(true) {
307            // Return JSON object
308            let header_info =
309                self.header_to_get_block_header(&block.header, height, block.txdata.len() as u32);
310            Ok(serde_json::to_value(header_info)?)
311        } else {
312            // Return hex-encoded serialized header
313            let mut data = Vec::new();
314            block
315                .header
316                .consensus_encode(&mut data)
317                .map_err(|e| Error::Other(format!("Failed to encode header: {e}")))?;
318            Ok(serde_json::Value::String(hex::encode(data)))
319        }
320    }
321}