Skip to content

Getblockstats #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
12 changes: 12 additions & 0 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@ pub trait RpcApi: Sized {
self.call("getblockhash", &[height.into()])
}

fn get_block_stats(&self, height: u64) -> Result<json::GetBlockStatsResult> {
self.call("getblockstats", &[height.into()])
}

fn get_block_stats_fields(
&self,
height: u64,
fields: &[json::BlockStatsFields],
) -> Result<json::GetBlockStatsResultPartial> {
self.call("getblockstats", &[height.into(), fields.into()])
}

fn get_raw_transaction(
&self,
txid: &bitcoin::Txid,
Expand Down
1 change: 1 addition & 0 deletions integration_test/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ bitcoind -regtest $BLOCKFILTERARG $FALLBACKFEEARG \
-connect=127.0.0.1:12348 \
-rpcport=12349 \
-server=1 \
-txindex=1 \
-printtoconsole=0 &
PID2=$!

Expand Down
25 changes: 25 additions & 0 deletions integration_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use bitcoin::{
use bitcoincore_rpc::bitcoincore_rpc_json::{
GetBlockTemplateModes, GetBlockTemplateRules, ScanTxOutRequest,
};
use json::BlockStatsFields as BsFields;

lazy_static! {
static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
Expand Down Expand Up @@ -147,6 +148,8 @@ fn main() {
test_get_block_hash(&cl);
test_get_block(&cl);
test_get_block_header_get_block_header_info(&cl);
test_get_block_stats(&cl);
test_get_block_stats_fields(&cl);
test_get_address_info(&cl);
test_set_label(&cl);
test_send_to_address(&cl);
Expand Down Expand Up @@ -310,6 +313,28 @@ fn test_get_block_header_get_block_header_info(cl: &Client) {
assert!(info.previous_block_hash.is_some());
}

fn test_get_block_stats(cl: &Client) {
let tip = cl.get_block_count().unwrap();
let tip_hash = cl.get_best_block_hash().unwrap();
let header = cl.get_block_header(&tip_hash).unwrap();
let stats = cl.get_block_stats(tip).unwrap();
assert_eq!(header.block_hash(), stats.block_hash);
assert_eq!(header.time, stats.time as u32);
assert_eq!(tip, stats.height);
}

fn test_get_block_stats_fields(cl: &Client) {
let tip = cl.get_block_count().unwrap();
let tip_hash = cl.get_best_block_hash().unwrap();
let header = cl.get_block_header(&tip_hash).unwrap();
let fields = [BsFields::BlockHash, BsFields::Height, BsFields::TotalFee];
let stats = cl.get_block_stats_fields(tip, &fields).unwrap();
assert_eq!(header.block_hash(), stats.block_hash.unwrap());
assert_eq!(tip, stats.height.unwrap());
assert!(stats.total_fee.is_some());
assert!(stats.avg_fee.is_none());
}

fn test_get_address_info(cl: &Client) {
let addr = cl.get_new_address(None, Some(json::AddressType::Legacy)).unwrap();
let info = cl.get_address_info(&addr).unwrap();
Expand Down
260 changes: 259 additions & 1 deletion json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use bitcoin::util::{bip158, bip32};
use bitcoin::{Address, Amount, PrivateKey, PublicKey, Script, SignedAmount, Transaction};
use serde::de::Error as SerdeError;
use serde::{Deserialize, Serialize};
use std::fmt;

//TODO(stevenroose) consider using a Time type

Expand Down Expand Up @@ -225,6 +226,264 @@ pub struct GetBlockHeaderResult {
pub next_block_hash: Option<bitcoin::BlockHash>,
}

#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct GetBlockStatsResult {
#[serde(rename = "avgfee", with = "bitcoin::util::amount::serde::as_sat")]
pub avg_fee: Amount,
#[serde(rename = "avgfeerate", with = "bitcoin::util::amount::serde::as_sat")]
pub avg_fee_rate: Amount,
#[serde(rename = "avgtxsize")]
pub avg_tx_size: u32,
#[serde(rename = "blockhash")]
pub block_hash: bitcoin::BlockHash,
#[serde(rename = "feerate_percentiles")]
pub fee_rate_percentiles: FeeRatePercentiles,
pub height: u64,
pub ins: usize,
#[serde(rename = "maxfee", with = "bitcoin::util::amount::serde::as_sat")]
pub max_fee: Amount,
#[serde(rename = "maxfeerate", with = "bitcoin::util::amount::serde::as_sat")]
pub max_fee_rate: Amount,
#[serde(rename = "maxtxsize")]
pub max_tx_size: u32,
#[serde(rename = "medianfee", with = "bitcoin::util::amount::serde::as_sat")]
pub median_fee: Amount,
#[serde(rename = "mediantime")]
pub median_time: u64,
#[serde(rename = "mediantxsize")]
pub median_tx_size: u32,
#[serde(rename = "minfee", with = "bitcoin::util::amount::serde::as_sat")]
pub min_fee: Amount,
#[serde(rename = "minfeerate", with = "bitcoin::util::amount::serde::as_sat")]
pub min_fee_rate: Amount,
#[serde(rename = "mintxsize")]
pub min_tx_size: u32,
pub outs: usize,
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub subsidy: Amount,
#[serde(rename = "swtotal_size")]
pub sw_total_size: usize,
#[serde(rename = "swtotal_weight")]
pub sw_total_weight: usize,
#[serde(rename = "swtxs")]
pub sw_txs: usize,
pub time: u64,
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub total_out: Amount,
pub total_size: usize,
pub total_weight: usize,
#[serde(rename = "totalfee", with = "bitcoin::util::amount::serde::as_sat")]
pub total_fee: Amount,
pub txs: usize,
pub utxo_increase: i32,
pub utxo_size_inc: i32,
}

#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct GetBlockStatsResultPartial {
#[serde(
default,
rename = "avgfee",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub avg_fee: Option<Amount>,
#[serde(
default,
rename = "avgfeerate",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub avg_fee_rate: Option<Amount>,
#[serde(default, rename = "avgtxsize", skip_serializing_if = "Option::is_none")]
pub avg_tx_size: Option<u32>,
#[serde(default, rename = "blockhash", skip_serializing_if = "Option::is_none")]
pub block_hash: Option<bitcoin::BlockHash>,
#[serde(default, rename = "feerate_percentiles", skip_serializing_if = "Option::is_none")]
pub fee_rate_percentiles: Option<FeeRatePercentiles>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub height: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ins: Option<usize>,
#[serde(
default,
rename = "maxfee",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub max_fee: Option<Amount>,
#[serde(
default,
rename = "maxfeerate",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub max_fee_rate: Option<Amount>,
#[serde(default, rename = "maxtxsize", skip_serializing_if = "Option::is_none")]
pub max_tx_size: Option<u32>,
#[serde(
default,
rename = "medianfee",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub median_fee: Option<Amount>,
#[serde(default, rename = "mediantime", skip_serializing_if = "Option::is_none")]
pub median_time: Option<u64>,
#[serde(default, rename = "mediantxsize", skip_serializing_if = "Option::is_none")]
pub median_tx_size: Option<u32>,
#[serde(
default,
rename = "minfee",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub min_fee: Option<Amount>,
#[serde(
default,
rename = "minfeerate",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub min_fee_rate: Option<Amount>,
#[serde(default, rename = "mintxsize", skip_serializing_if = "Option::is_none")]
pub min_tx_size: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outs: Option<usize>,
#[serde(
default,
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub subsidy: Option<Amount>,
#[serde(default, rename = "swtotal_size", skip_serializing_if = "Option::is_none")]
pub sw_total_size: Option<usize>,
#[serde(default, rename = "swtotal_weight", skip_serializing_if = "Option::is_none")]
pub sw_total_weight: Option<usize>,
#[serde(default, rename = "swtxs", skip_serializing_if = "Option::is_none")]
pub sw_txs: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time: Option<u64>,
#[serde(
default,
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub total_out: Option<Amount>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total_weight: Option<usize>,
#[serde(
default,
rename = "totalfee",
with = "bitcoin::util::amount::serde::as_sat::opt",
skip_serializing_if = "Option::is_none"
)]
pub total_fee: Option<Amount>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub txs: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub utxo_increase: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub utxo_size_inc: Option<i32>,
}

#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct FeeRatePercentiles {
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub fr_10th: Amount,
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub fr_25th: Amount,
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub fr_50th: Amount,
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub fr_75th: Amount,
#[serde(with = "bitcoin::util::amount::serde::as_sat")]
pub fr_90th: Amount,
}

#[derive(Clone)]
pub enum BlockStatsFields {
AverageFee,
AverageFeeRate,
AverageTxSize,
BlockHash,
FeeRatePercentiles,
Height,
Ins,
MaxFee,
MaxFeeRate,
MaxTxSize,
MedianFee,
MedianTime,
MedianTxSize,
MinFee,
MinFeeRate,
MinTxSize,
Outs,
Subsidy,
SegWitTotalSize,
SegWitTotalWeight,
SegWitTxs,
Time,
TotalOut,
TotalSize,
TotalWeight,
TotalFee,
Txs,
UtxoIncrease,
UtxoSizeIncrease,
}

impl BlockStatsFields {
fn get_rpc_keyword(&self) -> &str {
match *self {
BlockStatsFields::AverageFee => "avgfee",
BlockStatsFields::AverageFeeRate => "avgfeerate",
BlockStatsFields::AverageTxSize => "avgtxsize",
BlockStatsFields::BlockHash => "blockhash",
BlockStatsFields::FeeRatePercentiles => "feerate_percentiles",
BlockStatsFields::Height => "height",
BlockStatsFields::Ins => "ins",
BlockStatsFields::MaxFee => "maxfee",
BlockStatsFields::MaxFeeRate => "maxfeerate",
BlockStatsFields::MaxTxSize => "maxtxsize",
BlockStatsFields::MedianFee => "medianfee",
BlockStatsFields::MedianTime => "mediantime",
BlockStatsFields::MedianTxSize => "mediantxsize",
BlockStatsFields::MinFee => "minfee",
BlockStatsFields::MinFeeRate => "minfeerate",
BlockStatsFields::MinTxSize => "minfeerate",
BlockStatsFields::Outs => "outs",
BlockStatsFields::Subsidy => "subsidy",
BlockStatsFields::SegWitTotalSize => "swtotal_size",
BlockStatsFields::SegWitTotalWeight => "swtotal_weight",
BlockStatsFields::SegWitTxs => "swtxs",
BlockStatsFields::Time => "time",
BlockStatsFields::TotalOut => "total_out",
BlockStatsFields::TotalSize => "total_size",
BlockStatsFields::TotalWeight => "total_weight",
BlockStatsFields::TotalFee => "totalfee",
BlockStatsFields::Txs => "txs",
BlockStatsFields::UtxoIncrease => "utxo_increase",
BlockStatsFields::UtxoSizeIncrease => "utxo_size_inc",
}
}
}

impl fmt::Display for BlockStatsFields {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_rpc_keyword())
}
}

impl From<BlockStatsFields> for serde_json::Value {
fn from(bsf: BlockStatsFields) -> Self {
Self::from(bsf.to_string())
}
}

#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GetMiningInfoResult {
Expand Down Expand Up @@ -912,7 +1171,6 @@ impl<'de> serde::Deserialize<'de> for ImportMultiRescanSince {
D: serde::Deserializer<'de>,
{
use serde::de;
use std::fmt;
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = ImportMultiRescanSince;
Expand Down