Skip to main content

btcnode_metrics/
node.rs

1use corepc_client::client_sync::{v28::Client, Auth};
2use corepc_client::types::v28::{
3    EstimateSmartFee, GetBlockStats, GetBlockchainInfo, GetChainTips, GetMempoolInfo, GetNetTotals,
4    GetNetworkInfo, GetPeerInfo,
5};
6use serde::Deserialize;
7
8use crate::Error;
9use crate::config::NodeConfig;
10
11/// Custom type for `getmininginfo` that fixes `network_hash_ps` from `i64` to `f64`.
12///
13/// Bitcoin Core returns `networkhashps` as a floating-point number (e.g. `1.02e+21`)
14/// but the upstream `corepc-types` crate incorrectly declares the field as `i64`,
15/// which causes deserialization to fail on mainnet.
16#[derive(Clone, Debug, Deserialize)]
17pub struct MiningInfo {
18    pub blocks: u64,
19    #[serde(rename = "currentblockweight")]
20    pub current_block_weight: Option<u64>,
21    #[serde(rename = "currentblocktx")]
22    pub current_block_tx: Option<i64>,
23    pub difficulty: f64,
24    #[serde(rename = "networkhashps")]
25    pub network_hash_ps: f64,
26    #[serde(rename = "pooledtx")]
27    pub pooled_tx: i64,
28    pub chain: String,
29    pub warnings: Vec<String>,
30}
31
32/// Custom type for `getchaintxstats` that fixes `tx_rate` from `Option<i64>` to `Option<f64>`.
33///
34/// Bitcoin Core returns `txrate` as a floating-point number (e.g. `4.56`)
35/// but the upstream `corepc-types` crate incorrectly declares the field as `Option<i64>`,
36/// which causes deserialization to fail.
37#[derive(Clone, Debug, Deserialize)]
38pub struct ChainTxStats {
39    pub time: i64,
40    #[serde(rename = "txcount")]
41    pub tx_count: i64,
42    pub window_final_block_hash: String,
43    pub window_final_block_height: i64,
44    pub window_block_count: i64,
45    pub window_tx_count: Option<i64>,
46    pub window_interval: Option<i64>,
47    #[serde(rename = "txrate")]
48    pub tx_rate: Option<f64>,
49}
50
51pub trait NodeClient: Send + Sync {
52    fn get_blockchain_info(&self) -> Result<GetBlockchainInfo, Error>;
53    fn get_mempool_info(&self) -> Result<GetMempoolInfo, Error>;
54    fn get_network_info(&self) -> Result<GetNetworkInfo, Error>;
55    fn get_peer_info(&self) -> Result<GetPeerInfo, Error>;
56    fn get_mining_info(&self) -> Result<MiningInfo, Error>;
57    fn get_chain_tx_stats(&self) -> Result<ChainTxStats, Error>;
58    fn get_net_totals(&self) -> Result<GetNetTotals, Error>;
59    fn estimate_smart_fee(&self, conf_target: u32) -> Result<EstimateSmartFee, Error>;
60    fn get_chain_tips(&self) -> Result<GetChainTips, Error>;
61    fn uptime(&self) -> Result<u32, Error>;
62    fn get_block_stats_by_height(&self, height: u32) -> Result<GetBlockStats, Error>;
63}
64
65pub struct BitcoinNode {
66    client: Client,
67}
68
69impl BitcoinNode {
70    pub fn new(config: &NodeConfig) -> Result<Self, Error> {
71        let auth = Auth::UserPass(config.rpc_user.clone(), config.rpc_password.clone());
72        let client = Client::new_with_auth(&config.rpc_url, auth)
73            .map_err(|e| Error::Config(format!("failed to create RPC client: {e}")))?;
74        Ok(Self { client })
75    }
76}
77
78impl NodeClient for BitcoinNode {
79    fn get_blockchain_info(&self) -> Result<GetBlockchainInfo, Error> {
80        Ok(self.client.get_blockchain_info()?)
81    }
82
83    fn get_mempool_info(&self) -> Result<GetMempoolInfo, Error> {
84        Ok(self.client.get_mempool_info()?)
85    }
86
87    fn get_network_info(&self) -> Result<GetNetworkInfo, Error> {
88        Ok(self.client.get_network_info()?)
89    }
90
91    fn get_peer_info(&self) -> Result<GetPeerInfo, Error> {
92        Ok(self.client.get_peer_info()?)
93    }
94
95    fn get_mining_info(&self) -> Result<MiningInfo, Error> {
96        // Bypass upstream GetMiningInfo (which declares network_hash_ps as i64)
97        // and deserialize directly into our corrected MiningInfo type.
98        Ok(self.client.call::<MiningInfo>("getmininginfo", &[])?)
99    }
100
101    fn get_chain_tx_stats(&self) -> Result<ChainTxStats, Error> {
102        // Bypass upstream GetChainTxStats (which declares tx_rate as Option<i64>)
103        // and deserialize directly into our corrected ChainTxStats type.
104        Ok(self.client.call::<ChainTxStats>("getchaintxstats", &[])?)
105    }
106
107    fn get_net_totals(&self) -> Result<GetNetTotals, Error> {
108        Ok(self.client.get_net_totals()?)
109    }
110
111    fn estimate_smart_fee(&self, conf_target: u32) -> Result<EstimateSmartFee, Error> {
112        Ok(self.client.estimate_smart_fee(conf_target)?)
113    }
114
115    fn get_chain_tips(&self) -> Result<GetChainTips, Error> {
116        Ok(self.client.get_chain_tips()?)
117    }
118
119    fn uptime(&self) -> Result<u32, Error> {
120        Ok(self.client.uptime()?)
121    }
122
123    fn get_block_stats_by_height(&self, height: u32) -> Result<GetBlockStats, Error> {
124        Ok(self.client.get_block_stats_by_height(height)?)
125    }
126}