Skip to content

Commit ec827c2

Browse files
committed
Update GetBlockStats fields to be optional
GetBlockStats takes a second optional argument `stats`, which defaults to all if not provided, but if provided, `getblockstats` returns only the value of the stats requested. This commit updates the GetBlockStats struct fields to be optional, to better model the RPC response, All rpc versions have been updated to reflect this specification after confirming v23 was merely a docs fix. Client macro `impl_client_v17__get_block_stats` has been updated to take `stats` as a second optional argument, which if provided, `getblockstats` only returns the value of the stats requested. `blockchain__get_block_stats__modelled` has been updated to better test `getblockstats` params and response via the following helper functions: - `get_block_stats_by_height` - `get_block_stats_by_block_hash` - `get_block_stats_with_stats`
1 parent 63145cd commit ec827c2

7 files changed

Lines changed: 211 additions & 171 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)