Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "btcnode-metrics"
edition = "2024"
version = "2.0.7"
version = "2.29.0"
description = "The Bitcoin node metrics exporter for Prometheus based on Rust Bitcoin Community's crate for Bitcoin JSON-RPC."
license-file = "LICENSE"
homepage = "https://github.com/AltaModaTech/btcnode-metrics"
Expand Down
28 changes: 19 additions & 9 deletions src/btcnode_metrics_gatherer/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ impl<N: NodeClient> MetricsCollector<N> {
mod tests {
use super::*;
use crate::btcnode_metrics_gatherer::Error;
use crate::btcnode_metrics_gatherer::node::{ChainTxStats, MiningInfo};
use corepc_client::types::v28::*;
use corepc_client::types::v29::*;

struct MockNode;

Expand All @@ -270,6 +269,8 @@ mod tests {
best_block_hash: String::from(
"0000000000000000000000000000000000000000000000000000000000000000"
),
bits: String::from("1703255b"),
target: String::from("00000000000000000003255b0000000000000000000000000000000000000000"),
difficulty: 53_911_173_001_054.59,
time: 1_700_000_000,
median_time: 1_699_999_000,
Expand All @@ -281,7 +282,7 @@ mod tests {
prune_height: None,
automatic_pruning: None,
prune_target_size: None,
softforks: Default::default(),
signet_challenge: None,
warnings: vec![],
})
}
Expand Down Expand Up @@ -416,21 +417,30 @@ mod tests {
]))
}

fn get_mining_info(&self) -> Result<MiningInfo, Error> {
Ok(MiningInfo {
fn get_mining_info(&self) -> Result<GetMiningInfo, Error> {
Ok(GetMiningInfo {
blocks: 800_000,
current_block_weight: Some(3_993_000),
current_block_tx: Some(2_500),
bits: String::from("1703255b"),
difficulty: 53_911_173_001_054.59,
target: String::from("00000000000000000003255b0000000000000000000000000000000000000000"),
network_hash_ps: 4.5e17,
pooled_tx: 5000,
chain: "main".into(),
signet_challenge: None,
next: NextBlockInfo {
height: 800_001,
bits: String::from("1703255b"),
difficulty: 53_911_173_001_054.59,
target: String::from("00000000000000000003255b0000000000000000000000000000000000000000"),
},
warnings: vec![],
})
}

fn get_chain_tx_stats(&self) -> Result<ChainTxStats, Error> {
Ok(ChainTxStats {
fn get_chain_tx_stats(&self) -> Result<GetChainTxStats, Error> {
Ok(GetChainTxStats {
time: 1_700_000_000,
tx_count: 900_000_000,
window_final_block_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
Expand Down Expand Up @@ -645,11 +655,11 @@ mod tests {
MockNode.get_peer_info()
}

fn get_mining_info(&self) -> Result<MiningInfo, Error> {
fn get_mining_info(&self) -> Result<GetMiningInfo, Error> {
MockNode.get_mining_info()
}

fn get_chain_tx_stats(&self) -> Result<ChainTxStats, Error> {
fn get_chain_tx_stats(&self) -> Result<GetChainTxStats, Error> {
MockNode.get_chain_tx_stats()
}

Expand Down
65 changes: 10 additions & 55 deletions src/btcnode_metrics_gatherer/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,22 @@
// SPDX-FileCopyrightText: 2026 AltaModa Technologies, LLC
// SPDX-FileCopyrightText: Contributors to the btcnode-metrics project.

use corepc_client::client_sync::{v28::Client, Auth};
use corepc_client::types::v28::{
EstimateSmartFee, GetBlockStats, GetBlockchainInfo, GetChainTips, GetMempoolInfo, GetNetTotals,
GetNetworkInfo, GetPeerInfo,
use corepc_client::client_sync::{v29::Client, Auth};
use corepc_client::types::v29::{
EstimateSmartFee, GetBlockStats, GetBlockchainInfo, GetChainTips, GetChainTxStats,
GetMempoolInfo, GetMiningInfo, GetNetTotals, GetNetworkInfo, GetPeerInfo,
};
use serde::Deserialize;

use super::Error;
use super::config::NodeConfig;

/// Custom type for `getmininginfo` that fixes `network_hash_ps` from `i64` to `f64`.
///
/// Bitcoin Core returns `networkhashps` as a floating-point number (e.g. `1.02e+21`)
/// but the upstream `corepc-types` crate incorrectly declares the field as `i64`,
/// which causes deserialization to fail on mainnet.
#[derive(Clone, Debug, Deserialize)]
pub struct MiningInfo {
pub blocks: u64,
#[serde(rename = "currentblockweight")]
pub current_block_weight: Option<u64>,
#[serde(rename = "currentblocktx")]
pub current_block_tx: Option<i64>,
pub difficulty: f64,
#[serde(rename = "networkhashps")]
pub network_hash_ps: f64,
#[serde(rename = "pooledtx")]
pub pooled_tx: i64,
pub chain: String,
pub warnings: Vec<String>,
}

/// Custom type for `getchaintxstats` that fixes `tx_rate` from `Option<i64>` to `Option<f64>`.
///
/// Bitcoin Core returns `txrate` as a floating-point number (e.g. `4.56`)
/// but the upstream `corepc-types` crate incorrectly declares the field as `Option<i64>`,
/// which causes deserialization to fail.
#[derive(Clone, Debug, Deserialize)]
pub struct ChainTxStats {
pub time: i64,
#[serde(rename = "txcount")]
pub tx_count: i64,
pub window_final_block_hash: String,
pub window_final_block_height: i64,
pub window_block_count: i64,
pub window_tx_count: Option<i64>,
pub window_interval: Option<i64>,
#[serde(rename = "txrate")]
pub tx_rate: Option<f64>,
}

pub trait NodeClient: Send + Sync {
fn get_blockchain_info(&self) -> Result<GetBlockchainInfo, Error>;
fn get_mempool_info(&self) -> Result<GetMempoolInfo, Error>;
fn get_network_info(&self) -> Result<GetNetworkInfo, Error>;
fn get_peer_info(&self) -> Result<GetPeerInfo, Error>;
fn get_mining_info(&self) -> Result<MiningInfo, Error>;
fn get_chain_tx_stats(&self) -> Result<ChainTxStats, Error>;
fn get_mining_info(&self) -> Result<GetMiningInfo, Error>;
fn get_chain_tx_stats(&self) -> Result<GetChainTxStats, Error>;
fn get_net_totals(&self) -> Result<GetNetTotals, Error>;
fn estimate_smart_fee(&self, conf_target: u32) -> Result<EstimateSmartFee, Error>;
fn get_chain_tips(&self) -> Result<GetChainTips, Error>;
Expand Down Expand Up @@ -96,16 +55,12 @@ impl NodeClient for BitcoinNode {
Ok(self.client.get_peer_info()?)
}

fn get_mining_info(&self) -> Result<MiningInfo, Error> {
// Bypass upstream GetMiningInfo (which declares network_hash_ps as i64)
// and deserialize directly into our corrected MiningInfo type.
Ok(self.client.call::<MiningInfo>("getmininginfo", &[])?)
fn get_mining_info(&self) -> Result<GetMiningInfo, Error> {
Ok(self.client.get_mining_info()?)
}

fn get_chain_tx_stats(&self) -> Result<ChainTxStats, Error> {
// Bypass upstream GetChainTxStats (which declares tx_rate as Option<i64>)
// and deserialize directly into our corrected ChainTxStats type.
Ok(self.client.call::<ChainTxStats>("getchaintxstats", &[])?)
fn get_chain_tx_stats(&self) -> Result<GetChainTxStats, Error> {
Ok(self.client.get_chain_tx_stats()?)
}

fn get_net_totals(&self) -> Result<GetNetTotals, Error> {
Expand Down