From 1a2aaafe17ceb1253d224895d0ab74fd69db9657 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Tue, 19 Jan 2021 12:48:12 +0100 Subject: [PATCH 01/14] Add rpc call getblockstats --- client/src/client.rs | 4 +++ integration_test/src/main.rs | 11 ++++++++ json/src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/client/src/client.rs b/client/src/client.rs index 651acd96..12d158f3 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -462,6 +462,10 @@ pub trait RpcApi: Sized { self.call("getblockhash", &[height.into()]) } + fn get_block_stats(&self, height: u64) -> Result { + self.call("getblockstats", &[height.into()]) + } + fn get_raw_transaction( &self, txid: &bitcoin::Txid, diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index 0e35ee79..39aceb8c 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -147,6 +147,7 @@ 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_address_info(&cl); test_set_label(&cl); test_send_to_address(&cl); @@ -310,6 +311,16 @@ 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); + assert_eq!(tip, stats.height); +} + 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(); diff --git a/json/src/lib.rs b/json/src/lib.rs index 9a79d5e8..87a6f0f6 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -225,6 +225,57 @@ pub struct GetBlockHeaderResult { pub next_block_hash: Option, } +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetBlockStatsResult { + #[serde(rename = "avgfee")] + pub avg_fee: u32, + #[serde(rename = "avgfeerate")] + pub avg_fee_rate: u32, + #[serde(rename = "avgtxsize")] + pub avg_tx_size: u32, + #[serde(rename = "blockhash")] + pub block_hash: bitcoin::BlockHash, + #[serde(rename = "feerate_percentiles")] + pub fee_rate_percentiles: [u32; 5], + pub height: u64, + pub ins: usize, + #[serde(rename = "maxfee")] + pub max_fee: u64, + #[serde(rename = "maxfeerate")] + pub max_fee_rate: u32, + #[serde(rename = "maxtxsize")] + pub max_tx_size: u32, + #[serde(rename = "medianfee")] + pub median_fee: u32, + #[serde(rename = "mediantime")] + pub median_time: u32, + #[serde(rename = "mediantxsize")] + pub median_tx_size: u32, + #[serde(rename = "minfee")] + pub min_fee: u32, + #[serde(rename = "minfeerate")] + pub min_fee_rate: u32, + #[serde(rename = "mintxsize")] + pub min_tx_size: u32, + pub outs: usize, + pub subsidy: u32, + #[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: u32, + pub total_out: usize, + pub total_size: usize, + pub total_weight: usize, + #[serde(rename = "totalfee")] + pub total_fee: u64, + pub txs: usize, + pub utxo_increase: i32, + pub utxo_size_inc: i32, +} + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetMiningInfoResult { From 241810f4fc326a46bdda8f226fb1445e5d3a2f80 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Tue, 19 Jan 2021 13:38:56 +0100 Subject: [PATCH 02/14] Replace array with tuple struct --- json/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/json/src/lib.rs b/json/src/lib.rs index 87a6f0f6..33640456 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -236,7 +236,7 @@ pub struct GetBlockStatsResult { #[serde(rename = "blockhash")] pub block_hash: bitcoin::BlockHash, #[serde(rename = "feerate_percentiles")] - pub fee_rate_percentiles: [u32; 5], + pub fee_rate_percentiles: FeeRatePercentiles, pub height: u64, pub ins: usize, #[serde(rename = "maxfee")] @@ -276,6 +276,15 @@ pub struct GetBlockStatsResult { pub utxo_size_inc: i32, } +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct FeeRatePercentiles { + pub fr_10th: u32, + pub fr_25th: u32, + pub fr_50th: u32, + pub fr_75th: u32, + pub fr_90th: u32, +} + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetMiningInfoResult { From e14214f70523a6eca2237f9c6c1ce983ff019a5a Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Mon, 25 Jan 2021 20:51:04 +0100 Subject: [PATCH 03/14] getblockstats requires txindex --- integration_test/run.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/integration_test/run.sh b/integration_test/run.sh index b40aa78a..0b51564a 100755 --- a/integration_test/run.sh +++ b/integration_test/run.sh @@ -9,6 +9,7 @@ mkdir -p ${TESTDIR}/1 ${TESTDIR}/2 killall -9 bitcoind bitcoind -regtest \ + -txindex=1 \ -datadir=${TESTDIR}/1 \ -port=12348 \ -server=0 \ From 871629438472378b659435c5658839fcc2702126 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Wed, 27 Jan 2021 20:54:09 +0100 Subject: [PATCH 04/14] Add per-field querying for getblockstats --- client/src/client.rs | 8 +++ integration_test/src/main.rs | 14 +++++ json/src/lib.rs | 119 +++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/client/src/client.rs b/client/src/client.rs index 12d158f3..0a3b73e2 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -466,6 +466,14 @@ pub trait RpcApi: Sized { self.call("getblockstats", &[height.into()]) } + fn get_block_stats_fields(&self, height: u64, fields: &Vec) -> Result { + let fields: Vec<&str> = fields.iter() + .map(|field| field.get_rpc_keyword()) + .collect(); + + self.call("getblockstats", &[height.into(), fields.into()]) + } + fn get_raw_transaction( &self, txid: &bitcoin::Txid, diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index 39aceb8c..be55aea6 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -148,6 +148,7 @@ fn main() { test_get_block(&cl); test_get_block_header_get_block_header_info(&cl); test_get_block_stats(&cl); + test_get_block_stats_partial(&cl); test_get_address_info(&cl); test_set_label(&cl); test_send_to_address(&cl); @@ -321,6 +322,19 @@ fn test_get_block_stats(cl: &Client) { assert_eq!(tip, stats.height); } +fn test_get_block_stats_partial(cl: &Client) { + use bitcoincore_rpc::bitcoincore_rpc_json::BlockStatsFields; + 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 = vec![BlockStatsFields::BlockHash, BlockStatsFields::Height, BlockStatsFields::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(); diff --git a/json/src/lib.rs b/json/src/lib.rs index 33640456..21cfdacc 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -276,6 +276,57 @@ pub struct GetBlockStatsResult { pub utxo_size_inc: i32, } +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetBlockStatsResultPartial { + #[serde(rename = "avgfee")] + pub avg_fee: Option, + #[serde(rename = "avgfeerate")] + pub avg_fee_rate: Option, + #[serde(rename = "avgtxsize")] + pub avg_tx_size: Option, + #[serde(rename = "blockhash")] + pub block_hash: Option, + #[serde(rename = "feerate_percentiles")] + pub fee_rate_percentiles: Option, + pub height: Option, + pub ins: Option, + #[serde(rename = "maxfee")] + pub max_fee: Option, + #[serde(rename = "maxfeerate")] + pub max_fee_rate: Option, + #[serde(rename = "maxtxsize")] + pub max_tx_size: Option, + #[serde(rename = "medianfee")] + pub median_fee: Option, + #[serde(rename = "mediantime")] + pub median_time: Option, + #[serde(rename = "mediantxsize")] + pub median_tx_size: Option, + #[serde(rename = "minfee")] + pub min_fee: Option, + #[serde(rename = "minfeerate")] + pub min_fee_rate: Option, + #[serde(rename = "mintxsize")] + pub min_tx_size: Option, + pub outs: Option, + pub subsidy: Option, + #[serde(rename = "swtotal_size")] + pub sw_total_size: Option, + #[serde(rename = "swtotal_weight")] + pub sw_total_weight: Option, + #[serde(rename = "swtxs")] + pub sw_txs: Option, + pub time: Option, + pub total_out: Option, + pub total_size: Option, + pub total_weight: Option, + #[serde(rename = "totalfee")] + pub total_fee: Option, + pub txs: Option, + pub utxo_increase: Option, + pub utxo_size_inc: Option, +} + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct FeeRatePercentiles { pub fr_10th: u32, @@ -285,6 +336,74 @@ pub struct FeeRatePercentiles { pub fr_90th: u32, } +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 { + pub 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", + } + } +} + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetMiningInfoResult { From dd4aa0015f31a9b1cc8b9094ca94ca18ddb63953 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 28 Jan 2021 08:14:18 +0100 Subject: [PATCH 05/14] Use alias for bitcoincore_rpc_json --- client/src/client.rs | 2 +- integration_test/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 0a3b73e2..aa3fcbb4 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -466,7 +466,7 @@ pub trait RpcApi: Sized { self.call("getblockstats", &[height.into()]) } - fn get_block_stats_fields(&self, height: u64, fields: &Vec) -> Result { + fn get_block_stats_fields(&self, height: u64, fields: &Vec) -> Result { let fields: Vec<&str> = fields.iter() .map(|field| field.get_rpc_keyword()) .collect(); diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index be55aea6..dc1591bb 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -323,7 +323,7 @@ fn test_get_block_stats(cl: &Client) { } fn test_get_block_stats_partial(cl: &Client) { - use bitcoincore_rpc::bitcoincore_rpc_json::BlockStatsFields; + use json::BlockStatsFields; 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(); From aab047650d299e72514722a21865489f5c8f1f16 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 28 Jan 2021 08:53:51 +0100 Subject: [PATCH 06/14] Rename test method --- integration_test/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index dc1591bb..f2b23802 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -148,7 +148,7 @@ fn main() { test_get_block(&cl); test_get_block_header_get_block_header_info(&cl); test_get_block_stats(&cl); - test_get_block_stats_partial(&cl); + test_get_block_stats_fields(&cl); test_get_address_info(&cl); test_set_label(&cl); test_send_to_address(&cl); @@ -322,7 +322,7 @@ fn test_get_block_stats(cl: &Client) { assert_eq!(tip, stats.height); } -fn test_get_block_stats_partial(cl: &Client) { +fn test_get_block_stats_fields(cl: &Client) { use json::BlockStatsFields; let tip = cl.get_block_count().unwrap(); let tip_hash = cl.get_best_block_hash().unwrap(); From bde4fe32edff046831fc14a6f7846f054173790c Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 28 Jan 2021 09:02:09 +0100 Subject: [PATCH 07/14] Use borrowed array instead of vector as param --- client/src/client.rs | 2 +- integration_test/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index aa3fcbb4..6e22ec79 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -466,7 +466,7 @@ pub trait RpcApi: Sized { self.call("getblockstats", &[height.into()]) } - fn get_block_stats_fields(&self, height: u64, fields: &Vec) -> Result { + fn get_block_stats_fields(&self, height: u64, fields: &[json::BlockStatsFields]) -> Result { let fields: Vec<&str> = fields.iter() .map(|field| field.get_rpc_keyword()) .collect(); diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index f2b23802..c3cfdb45 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -327,7 +327,7 @@ 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 = vec![BlockStatsFields::BlockHash, BlockStatsFields::Height, BlockStatsFields::TotalFee]; + let fields = [BlockStatsFields::BlockHash, BlockStatsFields::Height, BlockStatsFields::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()); From 5391864131bf0bcc237bf33f436b9add248a1223 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Sun, 7 Feb 2021 11:29:31 +0100 Subject: [PATCH 08/14] Use Amounts, fix absent values --- json/src/lib.rs | 208 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 157 insertions(+), 51 deletions(-) diff --git a/json/src/lib.rs b/json/src/lib.rs index 21cfdacc..e33010be 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -227,10 +227,10 @@ pub struct GetBlockHeaderResult { #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct GetBlockStatsResult { - #[serde(rename = "avgfee")] - pub avg_fee: u32, - #[serde(rename = "avgfeerate")] - pub avg_fee_rate: u32, + #[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")] @@ -239,26 +239,27 @@ pub struct GetBlockStatsResult { pub fee_rate_percentiles: FeeRatePercentiles, pub height: u64, pub ins: usize, - #[serde(rename = "maxfee")] - pub max_fee: u64, - #[serde(rename = "maxfeerate")] - pub max_fee_rate: u32, + #[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")] - pub median_fee: u32, + #[serde(rename = "medianfee", with = "bitcoin::util::amount::serde::as_sat")] + pub median_fee: Amount, #[serde(rename = "mediantime")] pub median_time: u32, #[serde(rename = "mediantxsize")] pub median_tx_size: u32, - #[serde(rename = "minfee")] - pub min_fee: u32, - #[serde(rename = "minfeerate")] - pub min_fee_rate: 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, - pub subsidy: u32, + #[serde(with = "bitcoin::util::amount::serde::as_sat")] + pub subsidy: Amount, #[serde(rename = "swtotal_size")] pub sw_total_size: usize, #[serde(rename = "swtotal_weight")] @@ -266,11 +267,12 @@ pub struct GetBlockStatsResult { #[serde(rename = "swtxs")] pub sw_txs: usize, pub time: u32, - pub total_out: usize, + #[serde (with = "bitcoin::util::amount::serde::as_sat")] + pub total_out: Amount, pub total_size: usize, pub total_weight: usize, - #[serde(rename = "totalfee")] - pub total_fee: u64, + #[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, @@ -278,62 +280,166 @@ pub struct GetBlockStatsResult { #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct GetBlockStatsResultPartial { - #[serde(rename = "avgfee")] - pub avg_fee: Option, - #[serde(rename = "avgfeerate")] - pub avg_fee_rate: Option, - #[serde(rename = "avgtxsize")] + #[serde( + default, + rename = "avgfee", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub avg_fee: Option, + #[serde( + default, + rename = "avgfeerate", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub avg_fee_rate: Option, + #[serde( + default, + rename = "avgtxsize", + skip_serializing_if = "Option::is_none" + )] pub avg_tx_size: Option, - #[serde(rename = "blockhash")] + #[serde( + default, + rename = "blockhash", + skip_serializing_if = "Option::is_none" + )] pub block_hash: Option, - #[serde(rename = "feerate_percentiles")] + #[serde( + default, + rename = "feerate_percentiles", + skip_serializing_if = "Option::is_none" + )] pub fee_rate_percentiles: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub height: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub ins: Option, - #[serde(rename = "maxfee")] - pub max_fee: Option, - #[serde(rename = "maxfeerate")] - pub max_fee_rate: Option, - #[serde(rename = "maxtxsize")] + #[serde( + default, + rename = "maxfee", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub max_fee: Option, + #[serde( + default, + rename = "maxfeerate", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub max_fee_rate: Option, + #[serde( + default, + rename = "maxtxsize", + skip_serializing_if = "Option::is_none" + )] pub max_tx_size: Option, - #[serde(rename = "medianfee")] - pub median_fee: Option, - #[serde(rename = "mediantime")] + #[serde( + default, + rename = "medianfee", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub median_fee: Option, + #[serde( + default, + rename = "mediantime", + skip_serializing_if = "Option::is_none" + )] pub median_time: Option, - #[serde(rename = "mediantxsize")] + #[serde( + default, + rename = "mediantxsize", + skip_serializing_if = "Option::is_none" + )] pub median_tx_size: Option, - #[serde(rename = "minfee")] - pub min_fee: Option, - #[serde(rename = "minfeerate")] - pub min_fee_rate: Option, - #[serde(rename = "mintxsize")] + #[serde( + default, + rename = "minfee", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub min_fee: Option, + #[serde( + default, + rename = "minfeerate", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub min_fee_rate: Option, + #[serde( + default, + rename = "mintxsize", + skip_serializing_if = "Option::is_none" + )] pub min_tx_size: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub outs: Option, - pub subsidy: Option, - #[serde(rename = "swtotal_size")] + #[serde( + default, + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub subsidy: Option, + #[serde( + default, + rename = "swtotal_size", + skip_serializing_if = "Option::is_none" + )] pub sw_total_size: Option, - #[serde(rename = "swtotal_weight")] + #[serde( + default, + rename = "swtotal_weight", + skip_serializing_if = "Option::is_none" + )] pub sw_total_weight: Option, - #[serde(rename = "swtxs")] + #[serde( + default, + rename = "swtxs", + skip_serializing_if = "Option::is_none" + )] pub sw_txs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub time: Option, - pub total_out: Option, + #[serde( + default, + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub total_out: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub total_size: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub total_weight: Option, - #[serde(rename = "totalfee")] - pub total_fee: Option, + #[serde( + default, + rename = "totalfee", + with = "bitcoin::util::amount::serde::as_sat::opt", + skip_serializing_if = "Option::is_none" + )] + pub total_fee: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub txs: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub utxo_increase: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub utxo_size_inc: Option, } #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct FeeRatePercentiles { - pub fr_10th: u32, - pub fr_25th: u32, - pub fr_50th: u32, - pub fr_75th: u32, - pub fr_90th: u32, + #[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, } pub enum BlockStatsFields { From d7c2f9b5cbaab583af23cae5921ff56ed61ef7bd Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Sun, 14 Mar 2021 11:51:03 +0100 Subject: [PATCH 09/14] Use u64 for timestamps --- json/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/json/src/lib.rs b/json/src/lib.rs index e33010be..5cfe49e9 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -248,7 +248,7 @@ pub struct GetBlockStatsResult { #[serde(rename = "medianfee", with = "bitcoin::util::amount::serde::as_sat")] pub median_fee: Amount, #[serde(rename = "mediantime")] - pub median_time: u32, + pub median_time: u64, #[serde(rename = "mediantxsize")] pub median_tx_size: u32, #[serde(rename = "minfee", with = "bitcoin::util::amount::serde::as_sat")] @@ -266,7 +266,7 @@ pub struct GetBlockStatsResult { pub sw_total_weight: usize, #[serde(rename = "swtxs")] pub sw_txs: usize, - pub time: u32, + pub time: u64, #[serde (with = "bitcoin::util::amount::serde::as_sat")] pub total_out: Amount, pub total_size: usize, @@ -348,7 +348,7 @@ pub struct GetBlockStatsResultPartial { rename = "mediantime", skip_serializing_if = "Option::is_none" )] - pub median_time: Option, + pub median_time: Option, #[serde( default, rename = "mediantxsize", @@ -402,7 +402,7 @@ pub struct GetBlockStatsResultPartial { )] pub sw_txs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub time: Option, + pub time: Option, #[serde( default, with = "bitcoin::util::amount::serde::as_sat::opt", From c46f14a5423460c2617c9e91f8b2980334aae627 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Sun, 14 Mar 2021 12:17:24 +0100 Subject: [PATCH 10/14] Fix getblockstats test --- integration_test/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index c3cfdb45..70e8d1af 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -318,7 +318,7 @@ fn test_get_block_stats(cl: &Client) { 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); + assert_eq!(header.time, stats.time as u32); assert_eq!(tip, stats.height); } From 175f30fa4abb82f75e7d05aed8c7ffa16a6f43b0 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Mon, 15 Mar 2021 05:24:24 +0100 Subject: [PATCH 11/14] Fix int tests for getblockstats call --- integration_test/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test/run.sh b/integration_test/run.sh index 0b51564a..a442ae47 100755 --- a/integration_test/run.sh +++ b/integration_test/run.sh @@ -9,7 +9,6 @@ mkdir -p ${TESTDIR}/1 ${TESTDIR}/2 killall -9 bitcoind bitcoind -regtest \ - -txindex=1 \ -datadir=${TESTDIR}/1 \ -port=12348 \ -server=0 \ @@ -34,6 +33,7 @@ bitcoind -regtest $BLOCKFILTERARG $FALLBACKFEEARG \ -connect=127.0.0.1:12348 \ -rpcport=12349 \ -server=1 \ + -txindex=1 \ -printtoconsole=0 & PID2=$! From d34c7e393fed20ec652050b9a1d27cdd16d4fbe3 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Mon, 15 Mar 2021 06:06:07 +0100 Subject: [PATCH 12/14] Fix code formatting --- client/src/client.rs | 10 +++--- integration_test/src/main.rs | 4 +-- json/src/lib.rs | 62 +++++++----------------------------- 3 files changed, 19 insertions(+), 57 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 6e22ec79..e5d75ccc 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -466,10 +466,12 @@ pub trait RpcApi: Sized { self.call("getblockstats", &[height.into()]) } - fn get_block_stats_fields(&self, height: u64, fields: &[json::BlockStatsFields]) -> Result { - let fields: Vec<&str> = fields.iter() - .map(|field| field.get_rpc_keyword()) - .collect(); + fn get_block_stats_fields( + &self, + height: u64, + fields: &[json::BlockStatsFields], + ) -> Result { + let fields: Vec<&str> = fields.iter().map(|field| field.get_rpc_keyword()).collect(); self.call("getblockstats", &[height.into(), fields.into()]) } diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index 70e8d1af..d9229683 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -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::Secp256k1::new(); @@ -323,11 +324,10 @@ fn test_get_block_stats(cl: &Client) { } fn test_get_block_stats_fields(cl: &Client) { - use json::BlockStatsFields; 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 = [BlockStatsFields::BlockHash, BlockStatsFields::Height, BlockStatsFields::TotalFee]; + 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()); diff --git a/json/src/lib.rs b/json/src/lib.rs index 5cfe49e9..12963ca8 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -267,7 +267,7 @@ pub struct GetBlockStatsResult { #[serde(rename = "swtxs")] pub sw_txs: usize, pub time: u64, - #[serde (with = "bitcoin::util::amount::serde::as_sat")] + #[serde(with = "bitcoin::util::amount::serde::as_sat")] pub total_out: Amount, pub total_size: usize, pub total_weight: usize, @@ -294,23 +294,11 @@ pub struct GetBlockStatsResultPartial { skip_serializing_if = "Option::is_none" )] pub avg_fee_rate: Option, - #[serde( - default, - rename = "avgtxsize", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "avgtxsize", skip_serializing_if = "Option::is_none")] pub avg_tx_size: Option, - #[serde( - default, - rename = "blockhash", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "blockhash", skip_serializing_if = "Option::is_none")] pub block_hash: Option, - #[serde( - default, - rename = "feerate_percentiles", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "feerate_percentiles", skip_serializing_if = "Option::is_none")] pub fee_rate_percentiles: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub height: Option, @@ -330,11 +318,7 @@ pub struct GetBlockStatsResultPartial { skip_serializing_if = "Option::is_none" )] pub max_fee_rate: Option, - #[serde( - default, - rename = "maxtxsize", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "maxtxsize", skip_serializing_if = "Option::is_none")] pub max_tx_size: Option, #[serde( default, @@ -343,17 +327,9 @@ pub struct GetBlockStatsResultPartial { skip_serializing_if = "Option::is_none" )] pub median_fee: Option, - #[serde( - default, - rename = "mediantime", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "mediantime", skip_serializing_if = "Option::is_none")] pub median_time: Option, - #[serde( - default, - rename = "mediantxsize", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "mediantxsize", skip_serializing_if = "Option::is_none")] pub median_tx_size: Option, #[serde( default, @@ -369,11 +345,7 @@ pub struct GetBlockStatsResultPartial { skip_serializing_if = "Option::is_none" )] pub min_fee_rate: Option, - #[serde( - default, - rename = "mintxsize", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "mintxsize", skip_serializing_if = "Option::is_none")] pub min_tx_size: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub outs: Option, @@ -383,23 +355,11 @@ pub struct GetBlockStatsResultPartial { skip_serializing_if = "Option::is_none" )] pub subsidy: Option, - #[serde( - default, - rename = "swtotal_size", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "swtotal_size", skip_serializing_if = "Option::is_none")] pub sw_total_size: Option, - #[serde( - default, - rename = "swtotal_weight", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "swtotal_weight", skip_serializing_if = "Option::is_none")] pub sw_total_weight: Option, - #[serde( - default, - rename = "swtxs", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "swtxs", skip_serializing_if = "Option::is_none")] pub sw_txs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub time: Option, From 40fa36692d9e1e03a557319c4aafc7333f5adc38 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Wed, 21 Jul 2021 10:13:18 +0200 Subject: [PATCH 13/14] Implement Display trait for BlockStatsFields --- client/src/client.rs | 2 +- json/src/lib.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index e5d75ccc..5471e041 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -471,7 +471,7 @@ pub trait RpcApi: Sized { height: u64, fields: &[json::BlockStatsFields], ) -> Result { - let fields: Vec<&str> = fields.iter().map(|field| field.get_rpc_keyword()).collect(); + let fields: Vec = fields.iter().map(|field| field.to_string()).collect(); self.call("getblockstats", &[height.into(), fields.into()]) } diff --git a/json/src/lib.rs b/json/src/lib.rs index 12963ca8..b1da1651 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -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 @@ -435,7 +436,7 @@ pub enum BlockStatsFields { } impl BlockStatsFields { - pub fn get_rpc_keyword(&self) -> &str { + fn get_rpc_keyword(&self) -> &str { match *self { BlockStatsFields::AverageFee => "avgfee", BlockStatsFields::AverageFeeRate => "avgfeerate", @@ -470,6 +471,12 @@ impl BlockStatsFields { } } +impl fmt::Display for BlockStatsFields { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.get_rpc_keyword()) + } +} + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetMiningInfoResult { @@ -1157,7 +1164,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; From d83b8fe724c5df78ab0ec1e61c3d786155d447ac Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Wed, 21 Jul 2021 10:58:37 +0200 Subject: [PATCH 14/14] Implement serde_json::Value for BlockStatsFields --- client/src/client.rs | 2 -- json/src/lib.rs | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 5471e041..4f20eb0b 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -471,8 +471,6 @@ pub trait RpcApi: Sized { height: u64, fields: &[json::BlockStatsFields], ) -> Result { - let fields: Vec = fields.iter().map(|field| field.to_string()).collect(); - self.call("getblockstats", &[height.into(), fields.into()]) } diff --git a/json/src/lib.rs b/json/src/lib.rs index b1da1651..6495a7f0 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -403,6 +403,7 @@ pub struct FeeRatePercentiles { pub fr_90th: Amount, } +#[derive(Clone)] pub enum BlockStatsFields { AverageFee, AverageFeeRate, @@ -477,6 +478,12 @@ impl fmt::Display for BlockStatsFields { } } +impl From 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 {