Skip to content

Commit 0b8bea9

Browse files
committed
Merge #534: Update GetBlockStats fields to be optional
94f6a4f Update the docs for GetBlockStats (Abeeujah) ec827c2 Update GetBlockStats fields to be optional (Abeeujah) Pull request description: This PR aims to update the `GetBlockStats` model/response to match the core's spec for [Docs fix](https://bitcoincore.org/en/doc/23.0.0/rpc/blockchain/getblockstats/) the rpc spec for getblockstats specifies that it takes in two arguments 1. hash_or_height (string or numeric, required) The block hash or height of the target block 2. stats (json array, optional, default=all values) If the stats arg is provided, only the value for the stats is returned, if not provided it defaults to all, and all the fields values are returned; indicating that all the fields for `GetBlockStats` is optional. To better model and represent this, all the fields in v17, v25, and `model::GetBlockStats` are now wrapped in `Option`. - The docs has been updated to include the optional stats argument - Tests has also been updated to accept the stats argument, and confirm it behaves as specified for all versions of core. ACKs for top commit: jamillambert: ACK 94f6a4f tcharding: ACK 94f6a4f Tree-SHA512: 8154b831897f85ffdceedb02a28dd00f7980d08d3daf1fcd7d793b46d266aa3c2e962a8f3b072a5f14339cc3b94ccd5d4af05fa1ceee3d3753baebb54ca05c56
2 parents 979a2d8 + 94f6a4f commit 0b8bea9

7 files changed

Lines changed: 223 additions & 177 deletions

File tree

client/src/client_sync/v17/blockchain.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,20 @@ macro_rules! impl_client_v17__get_block_header {
112112
macro_rules! impl_client_v17__get_block_stats {
113113
() => {
114114
impl Client {
115-
pub fn get_block_stats_by_height(&self, height: u32) -> Result<GetBlockStats> {
116-
self.call("getblockstats", &[into_json(height)?])
115+
pub fn get_block_stats_by_height(
116+
&self,
117+
height: u32,
118+
stats: Option<&[&str]>,
119+
) -> Result<GetBlockStats> {
120+
self.call("getblockstats", &[into_json(height)?, into_json(stats)?])
117121
}
118122

119-
pub fn get_block_stats_by_block_hash(&self, hash: &BlockHash) -> Result<GetBlockStats> {
120-
self.call("getblockstats", &[into_json(hash)?])
123+
pub fn get_block_stats_by_block_hash(
124+
&self,
125+
hash: &BlockHash,
126+
stats: Option<&[&str]>,
127+
) -> Result<GetBlockStats> {
128+
self.call("getblockstats", &[into_json(hash)?, into_json(stats)?])
121129
}
122130
}
123131
};

integration_test/tests/blockchain.rs

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -197,45 +197,53 @@ fn blockchain__get_block_header__modelled() {
197197
#[test]
198198
fn blockchain__get_block_stats__modelled() {
199199
// Version 17 and 18 cannot call `getblockstats` if `-txindex` is not enabled.
200-
#[cfg(not(feature = "v18_and_below"))]
201-
getblockstats();
200+
// newer versions do not.
201+
let node = if cfg!(feature = "v18_and_below") {
202+
Node::with_wallet(Wallet::Default, &["-txindex"])
203+
} else {
204+
Node::with_wallet(Wallet::Default, &[])
205+
};
206+
node.fund_wallet();
202207

203-
// All versions including 17 and 18 can `getblockstats` if `-txindex` is enabled.
204-
getblockstats_txindex();
208+
get_block_stats_by_height(&node);
209+
get_block_stats_by_block_hash(&node);
210+
get_block_stats_with_stats(&node);
205211
}
206212

207-
#[cfg(not(feature = "v18_and_below"))]
208-
fn getblockstats() {
209-
let node = Node::with_wallet(Wallet::Default, &[]);
210-
node.fund_wallet();
213+
fn get_block_stats_by_height(node: &Node) {
214+
let json: GetBlockStats =
215+
node.client.get_block_stats_by_height(101, None).expect("getblockstats");
211216

212-
let json: GetBlockStats = node.client.get_block_stats_by_height(1).expect("getblockstats");
213217
let model: Result<mtype::GetBlockStats, GetBlockStatsError> = json.into_model();
214-
model.unwrap();
218+
let model = model.unwrap();
215219

216-
// No need for explicit types, used explicitly in test below.
220+
assert_eq!(model.height, Some(101));
221+
assert!(model.block_hash.is_some());
222+
}
223+
224+
fn get_block_stats_by_block_hash(node: &Node) {
217225
let block_hash = node.client.best_block_hash().expect("best_block_hash failed");
218226
let json: GetBlockStats =
219-
node.client.get_block_stats_by_block_hash(&block_hash).expect("getblockstats");
220-
let model: Result<mtype::GetBlockStats, GetBlockStatsError> = json.into_model();
221-
model.unwrap();
227+
node.client.get_block_stats_by_block_hash(&block_hash, None).expect("getblockstats");
228+
229+
let model = json.into_model().unwrap(); // Explicit error type already used above.
230+
231+
assert_eq!(model.block_hash, Some(block_hash));
232+
assert!(model.height.is_some());
222233
}
223234

224-
fn getblockstats_txindex() {
225-
let node = Node::with_wallet(Wallet::Default, &["-txindex"]);
226-
node.fund_wallet();
235+
fn get_block_stats_with_stats(node: &Node) {
236+
let json: GetBlockStats = node
237+
.client
238+
.get_block_stats_by_height(101, Some(&["minfeerate", "avgfeerate"]))
239+
.expect("getblockstats");
227240

228-
// Get block stats by height.
229-
let json: GetBlockStats = node.client.get_block_stats_by_height(101).expect("getblockstats");
230-
let model: Result<mtype::GetBlockStats, GetBlockStatsError> = json.into_model();
231-
model.unwrap();
241+
let model = json.into_model().unwrap(); // Explicit error type already used above.
232242

233-
// Get block stats by block hash.
234-
let block_hash = node.client.best_block_hash().expect("best_block_hash failed");
235-
let json: GetBlockStats =
236-
node.client.get_block_stats_by_block_hash(&block_hash).expect("getblockstats");
237-
let model: Result<mtype::GetBlockStats, GetBlockStatsError> = json.into_model();
238-
model.unwrap();
243+
assert!(model.minimum_fee_rate.is_some());
244+
assert!(model.average_fee_rate.is_some());
245+
assert!(model.block_hash.is_none());
246+
assert!(model.height.is_none());
239247
}
240248

241249
#[test]

types/src/model/blockchain.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -384,66 +384,68 @@ pub struct GetBlockHeaderVerbose {
384384
}
385385

386386
/// Models the result of JSON-RPC method `getblockstats`.
387+
///
388+
/// All fields are optional because the caller can select which stats to compute.
387389
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
388390
pub struct GetBlockStats {
389391
/// Average fee in the block.
390-
pub average_fee: Amount,
392+
pub average_fee: Option<Amount>,
391393
/// Average feerate.
392394
pub average_fee_rate: Option<FeeRate>,
393395
/// Average transaction size.
394-
pub average_tx_size: u32,
396+
pub average_tx_size: Option<u32>,
395397
/// The block hash (to check for potential reorgs).
396-
pub block_hash: BlockHash,
398+
pub block_hash: Option<BlockHash>,
397399
/// Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte).
398-
pub fee_rate_percentiles: Vec<Option<FeeRate>>,
400+
pub fee_rate_percentiles: Option<Vec<Option<FeeRate>>>,
399401
/// The height of the block.
400-
pub height: u32,
402+
pub height: Option<u32>,
401403
/// The number of inputs (excluding coinbase).
402-
pub inputs: u32,
404+
pub inputs: Option<u32>,
403405
/// Maximum fee in the block.
404-
pub max_fee: Amount,
406+
pub max_fee: Option<Amount>,
405407
/// Maximum feerate (in satoshis per virtual byte).
406408
pub max_fee_rate: Option<FeeRate>,
407409
/// Maximum transaction size.
408-
pub max_tx_size: u32,
410+
pub max_tx_size: Option<u32>,
409411
/// Truncated median fee in the block.
410-
pub median_fee: Amount,
412+
pub median_fee: Option<Amount>,
411413
/// The block median time past.
412-
pub median_time: u32,
414+
pub median_time: Option<u32>,
413415
/// Truncated median transaction size
414-
pub median_tx_size: u32,
416+
pub median_tx_size: Option<u32>,
415417
/// Minimum fee in the block.
416-
pub minimum_fee: Amount,
418+
pub minimum_fee: Option<Amount>,
417419
/// Minimum feerate (in satoshis per virtual byte).
418420
pub minimum_fee_rate: Option<FeeRate>,
419421
/// Minimum transaction size.
420-
pub minimum_tx_size: u32,
422+
pub minimum_tx_size: Option<u32>,
421423
/// The number of outputs.
422-
pub outputs: u32,
424+
pub outputs: Option<u32>,
423425
/// The block subsidy.
424-
pub subsidy: Amount,
426+
pub subsidy: Option<Amount>,
425427
/// Total size of all segwit transactions.
426-
pub segwit_total_size: u32,
428+
pub segwit_total_size: Option<u32>,
427429
/// Total weight of all segwit transactions divided by segwit scale factor (4).
428430
pub segwit_total_weight: Option<Weight>,
429431
/// The number of segwit transactions.
430-
pub segwit_txs: u32,
432+
pub segwit_txs: Option<u32>,
431433
/// The block time.
432-
pub time: u32,
434+
pub time: Option<u32>,
433435
/// Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee]).
434-
pub total_out: Amount,
436+
pub total_out: Option<Amount>,
435437
/// Total size of all non-coinbase transactions.
436-
pub total_size: u32,
438+
pub total_size: Option<u32>,
437439
/// Total weight of all non-coinbase transactions divided by segwit scale factor (4).
438440
pub total_weight: Option<Weight>,
439441
/// The fee total.
440-
pub total_fee: Amount,
442+
pub total_fee: Option<Amount>,
441443
/// The number of transactions (excluding coinbase).
442-
pub txs: u32,
444+
pub txs: Option<u32>,
443445
/// The increase/decrease in the number of unspent outputs.
444-
pub utxo_increase: i32,
446+
pub utxo_increase: Option<i32>,
445447
/// The increase/decrease in size for the utxo index (not discounting op_return and similar).
446-
pub utxo_size_increase: i32,
448+
pub utxo_size_increase: Option<i32>,
447449
/// The increase/decrease in the number of unspent outputs, not counting unspendables.
448450
/// v25 and later only.
449451
pub utxo_increase_actual: Option<i32>,

types/src/v17/blockchain/into.rs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -222,48 +222,59 @@ impl GetBlockStats {
222222
use GetBlockStatsError as E;
223223

224224
// `FeeRate::sat_per_vb` returns an option if value overflows.
225-
let average_fee_rate = FeeRate::from_sat_per_vb(self.average_fee_rate);
226-
let block_hash = self.block_hash.parse::<BlockHash>().map_err(E::BlockHash)?;
225+
let average_fee_rate = self.average_fee_rate.and_then(FeeRate::from_sat_per_vb);
226+
let block_hash =
227+
self.block_hash.map(|h| h.parse::<BlockHash>()).transpose().map_err(E::BlockHash)?;
227228
let fee_rate_percentiles = self
228229
.fee_rate_percentiles
229-
.iter()
230-
.map(|vb| FeeRate::from_sat_per_vb(*vb))
231-
.collect::<Vec<Option<FeeRate>>>();
232-
let max_fee_rate = FeeRate::from_sat_per_vb(self.max_fee_rate);
233-
let minimum_fee_rate = FeeRate::from_sat_per_vb(self.minimum_fee_rate);
230+
.map(|arr| arr.iter().map(|vb| FeeRate::from_sat_per_vb(*vb)).collect());
231+
let max_fee_rate = self.max_fee_rate.and_then(FeeRate::from_sat_per_vb);
232+
let minimum_fee_rate = self.minimum_fee_rate.and_then(FeeRate::from_sat_per_vb);
234233

235234
// FIXME: Double check that these values are virtual bytes and not weight units.
236-
let segwit_total_weight = Weight::from_vb(self.segwit_total_weight);
237-
let total_weight = Weight::from_vb(self.total_weight);
235+
let segwit_total_weight = self.segwit_total_weight.and_then(Weight::from_vb);
236+
let total_weight = self.total_weight.and_then(Weight::from_vb);
238237

239238
Ok(model::GetBlockStats {
240-
average_fee: Amount::from_sat(self.average_fee),
239+
average_fee: self.average_fee.map(Amount::from_sat),
241240
average_fee_rate,
242-
average_tx_size: crate::to_u32(self.average_tx_size, "average_tx_size")?,
241+
average_tx_size: self
242+
.average_tx_size
243+
.map(|v| crate::to_u32(v, "average_tx_size"))
244+
.transpose()?,
243245
block_hash,
244246
fee_rate_percentiles,
245-
height: crate::to_u32(self.height, "height")?,
246-
inputs: crate::to_u32(self.inputs, "inputs")?,
247-
max_fee: Amount::from_sat(self.max_fee),
247+
height: self.height.map(|v| crate::to_u32(v, "height")).transpose()?,
248+
inputs: self.inputs.map(|v| crate::to_u32(v, "inputs")).transpose()?,
249+
max_fee: self.max_fee.map(Amount::from_sat),
248250
max_fee_rate,
249-
max_tx_size: crate::to_u32(self.max_tx_size, "max_tx_size")?,
250-
median_fee: Amount::from_sat(self.median_fee),
251-
median_time: crate::to_u32(self.median_time, "median_time")?,
252-
median_tx_size: crate::to_u32(self.median_tx_size, "median_tx_size")?,
253-
minimum_fee: Amount::from_sat(self.minimum_fee),
251+
max_tx_size: self.max_tx_size.map(|v| crate::to_u32(v, "max_tx_size")).transpose()?,
252+
median_fee: self.median_fee.map(Amount::from_sat),
253+
median_time: self.median_time.map(|v| crate::to_u32(v, "median_time")).transpose()?,
254+
median_tx_size: self
255+
.median_tx_size
256+
.map(|v| crate::to_u32(v, "median_tx_size"))
257+
.transpose()?,
258+
minimum_fee: self.minimum_fee.map(Amount::from_sat),
254259
minimum_fee_rate,
255-
minimum_tx_size: crate::to_u32(self.minimum_tx_size, "minimum_tx_size")?,
256-
outputs: crate::to_u32(self.outputs, "outputs")?,
257-
subsidy: Amount::from_sat(self.subsidy),
258-
segwit_total_size: crate::to_u32(self.segwit_total_size, "segwit_total_size")?,
260+
minimum_tx_size: self
261+
.minimum_tx_size
262+
.map(|v| crate::to_u32(v, "minimum_tx_size"))
263+
.transpose()?,
264+
outputs: self.outputs.map(|v| crate::to_u32(v, "outputs")).transpose()?,
265+
subsidy: self.subsidy.map(Amount::from_sat),
266+
segwit_total_size: self
267+
.segwit_total_size
268+
.map(|v| crate::to_u32(v, "segwit_total_size"))
269+
.transpose()?,
259270
segwit_total_weight,
260-
segwit_txs: crate::to_u32(self.segwit_txs, "segwit_txs")?,
261-
time: crate::to_u32(self.time, "time")?,
262-
total_out: Amount::from_sat(self.total_out),
263-
total_size: crate::to_u32(self.total_size, "total_size")?,
271+
segwit_txs: self.segwit_txs.map(|v| crate::to_u32(v, "segwit_txs")).transpose()?,
272+
time: self.time.map(|v| crate::to_u32(v, "time")).transpose()?,
273+
total_out: self.total_out.map(Amount::from_sat),
274+
total_size: self.total_size.map(|v| crate::to_u32(v, "total_size")).transpose()?,
264275
total_weight,
265-
total_fee: Amount::from_sat(self.total_fee),
266-
txs: crate::to_u32(self.txs, "txs")?,
276+
total_fee: self.total_fee.map(Amount::from_sat),
277+
txs: self.txs.map(|v| crate::to_u32(v, "txs")).transpose()?,
267278
utxo_increase: self.utxo_increase,
268279
utxo_size_increase: self.utxo_size_increase,
269280
utxo_increase_actual: None, // v25 and later only.

0 commit comments

Comments
 (0)