From 35e6944a230e975884e9bfb8588cfa772d4237de Mon Sep 17 00:00:00 2001 From: raph Date: Mon, 11 Mar 2024 20:34:44 +0100 Subject: [PATCH] Add more fields to /r/blockinfo (#3260) --- Cargo.lock | 44 +++++++++++++- Cargo.toml | 1 + crates/test-bitcoincore-rpc/src/api.rs | 3 + crates/test-bitcoincore-rpc/src/lib.rs | 18 +++--- crates/test-bitcoincore-rpc/src/server.rs | 49 ++++++++++++++- docs/src/inscriptions/recursion.md | 20 +++++-- src/api.rs | 24 ++++++-- src/index.rs | 9 ++- src/subcommand/server.rs | 72 +++++++++++++++-------- 9 files changed, 195 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b68a02e63..8365e80b2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +dependencies = [ + "nodrop", +] + [[package]] name = "asn1-rs" version = "0.3.1" @@ -1862,6 +1871,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memchr" version = "2.7.1" @@ -1990,6 +2005,12 @@ dependencies = [ "libc", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.3" @@ -2196,6 +2217,7 @@ dependencies = [ "rustls 0.22.2", "rustls-acme", "serde", + "serde-hex", "serde_json", "serde_yaml", "sha3", @@ -2273,7 +2295,7 @@ dependencies = [ "instant", "libc", "redox_syscall 0.2.16", - "smallvec", + "smallvec 1.11.2", "winapi", ] @@ -2953,6 +2975,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" +dependencies = [ + "array-init", + "serde", + "smallvec 0.6.14", +] + [[package]] name = "serde_derive" version = "1.0.195" @@ -3052,6 +3085,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + [[package]] name = "smallvec" version = "1.11.2" diff --git a/Cargo.toml b/Cargo.toml index d18c0a946a..e949072d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ rust-embed = "8.0.0" rustls = "0.22.0" rustls-acme = { version = "0.8.1", features = ["axum"] } serde = { version = "1.0.137", features = ["derive"] } +serde-hex = "0.1.0" serde_json = { version = "1.0.81", features = ["preserve_order"] } serde_yaml = "0.9.17" sha3 = "0.10.8" diff --git a/crates/test-bitcoincore-rpc/src/api.rs b/crates/test-bitcoincore-rpc/src/api.rs index 4526fde229..8ca8f32a37 100644 --- a/crates/test-bitcoincore-rpc/src/api.rs +++ b/crates/test-bitcoincore-rpc/src/api.rs @@ -22,6 +22,9 @@ pub trait Api { verbose: bool, ) -> Result; + #[rpc(name = "getblockstats")] + fn get_block_stats(&self, height: usize) -> Result; + #[rpc(name = "getblock")] fn get_block(&self, blockhash: BlockHash, verbosity: u64) -> Result; diff --git a/crates/test-bitcoincore-rpc/src/lib.rs b/crates/test-bitcoincore-rpc/src/lib.rs index c5b11244b4..e13a1e0c81 100644 --- a/crates/test-bitcoincore-rpc/src/lib.rs +++ b/crates/test-bitcoincore-rpc/src/lib.rs @@ -17,15 +17,15 @@ use { Wtxid, }, bitcoincore_rpc::json::{ - Bip125Replaceable, CreateRawTransactionInput, Descriptor, EstimateMode, FinalizePsbtResult, - GetBalancesResult, GetBalancesResultEntry, GetBlockHeaderResult, GetBlockchainInfoResult, - GetDescriptorInfoResult, GetNetworkInfoResult, GetRawTransactionResult, - GetRawTransactionResultVout, GetRawTransactionResultVoutScriptPubKey, GetTransactionResult, - GetTransactionResultDetail, GetTransactionResultDetailCategory, GetTxOutResult, - GetWalletInfoResult, ImportDescriptors, ImportMultiResult, ListDescriptorsResult, - ListTransactionResult, ListUnspentResultEntry, ListWalletDirItem, ListWalletDirResult, - LoadWalletResult, SignRawTransactionInput, SignRawTransactionResult, Timestamp, - WalletProcessPsbtResult, WalletTxInfo, + Bip125Replaceable, CreateRawTransactionInput, Descriptor, EstimateMode, FeeRatePercentiles, + FinalizePsbtResult, GetBalancesResult, GetBalancesResultEntry, GetBlockHeaderResult, + GetBlockStatsResult, GetBlockchainInfoResult, GetDescriptorInfoResult, GetNetworkInfoResult, + GetRawTransactionResult, GetRawTransactionResultVout, GetRawTransactionResultVoutScriptPubKey, + GetTransactionResult, GetTransactionResultDetail, GetTransactionResultDetailCategory, + GetTxOutResult, GetWalletInfoResult, ImportDescriptors, ImportMultiResult, + ListDescriptorsResult, ListTransactionResult, ListUnspentResultEntry, ListWalletDirItem, + ListWalletDirResult, LoadWalletResult, SignRawTransactionInput, SignRawTransactionResult, + Timestamp, WalletProcessPsbtResult, WalletTxInfo, }, jsonrpc_core::{IoHandler, Value}, jsonrpc_http_server::{CloseHandle, ServerBuilder}, diff --git a/crates/test-bitcoincore-rpc/src/server.rs b/crates/test-bitcoincore-rpc/src/server.rs index 94b93a698a..23a7af2c50 100644 --- a/crates/test-bitcoincore-rpc/src/server.rs +++ b/crates/test-bitcoincore-rpc/src/server.rs @@ -119,7 +119,10 @@ impl Api for Server { Ok( serde_json::to_value(GetBlockHeaderResult { bits: String::new(), - chainwork: Vec::new(), + chainwork: hex::decode( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), confirmations: 0, difficulty: 0.0, hash: block_hash, @@ -144,6 +147,50 @@ impl Api for Server { } } + fn get_block_stats(&self, height: usize) -> Result { + let Some(block_hash) = self.state().hashes.get(height).cloned() else { + return Err(Self::not_found()); + }; + + Ok(GetBlockStatsResult { + avg_fee: Amount::ZERO, + avg_fee_rate: Amount::ZERO, + avg_tx_size: 0, + block_hash, + fee_rate_percentiles: FeeRatePercentiles { + fr_10th: Amount::ZERO, + fr_25th: Amount::ZERO, + fr_50th: Amount::ZERO, + fr_75th: Amount::ZERO, + fr_90th: Amount::ZERO, + }, + height: height.try_into().unwrap(), + ins: 0, + max_fee: Amount::ZERO, + max_fee_rate: Amount::ZERO, + max_tx_size: 0, + median_fee: Amount::ZERO, + median_time: 0, + median_tx_size: 0, + min_fee: Amount::ZERO, + min_fee_rate: Amount::ZERO, + min_tx_size: 0, + outs: 0, + subsidy: Amount::ZERO, + sw_total_size: 0, + sw_total_weight: 0, + sw_txs: 0, + time: 0, + total_out: Amount::ZERO, + total_size: 0, + total_weight: 0, + total_fee: Amount::ZERO, + txs: 0, + utxo_increase: 0, + utxo_size_inc: 0, + }) + } + fn get_block( &self, block_hash: BlockHash, diff --git a/docs/src/inscriptions/recursion.md b/docs/src/inscriptions/recursion.md index 1deab60581..2f013db560 100644 --- a/docs/src/inscriptions/recursion.md +++ b/docs/src/inscriptions/recursion.md @@ -67,20 +67,32 @@ Examples ```json { + "average_fee": 0, + "average_fee_rate": 0, "bits": 486604799, - "chainwork": 0, + "chainwork": "0000000000000000000000000000000000000000000000000000000100010001", "confirmations": 0, "difficulty": 0.0, "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "height": 0, - "median_time": null, + "max_fee": 0, + "max_fee_rate": 0, + "max_tx_size": 0, + "median_fee": 0, + "median_time": 1231006505, "merkle_root": "0000000000000000000000000000000000000000000000000000000000000000", + "min_fee": 0, + "min_fee_rate": 0, "next_block": null, "nonce": 0, "previous_block": null, + "subsidy": 5000000000, "target": "00000000ffff0000000000000000000000000000000000000000000000000000", - "timestamp": 0, - "transaction_count": 0, + "timestamp": 1231006505, + "total_fee": 0, + "total_size": 0, + "total_weight": 0, + "transaction_count": 1, "version": 1 } ``` diff --git a/src/api.rs b/src/api.rs index c918dd62a0..cceb717884 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,9 @@ -use super::{ - target_as_block_hash, BlockHash, Chain, Deserialize, Height, InscriptionId, OutPoint, Pile, - Rarity, SatPoint, Serialize, SpacedRune, TxMerkleNode, TxOut, +use { + super::{ + target_as_block_hash, BlockHash, Chain, Deserialize, Height, InscriptionId, OutPoint, Pile, + Rarity, SatPoint, Serialize, SpacedRune, TxMerkleNode, TxOut, + }, + serde_hex::{SerHex, Strict}, }; pub use crate::templates::{ @@ -36,19 +39,32 @@ impl Block { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct BlockInfo { + pub average_fee: u64, + pub average_fee_rate: u64, pub bits: u32, - pub chainwork: u128, + #[serde(with = "SerHex::")] + pub chainwork: [u8; 32], pub confirmations: i32, pub difficulty: f64, pub hash: BlockHash, pub height: u32, + pub max_fee: u64, + pub max_fee_rate: u64, + pub max_tx_size: u32, + pub median_fee: u64, pub median_time: Option, pub merkle_root: TxMerkleNode, + pub min_fee: u64, + pub min_fee_rate: u64, pub next_block: Option, pub nonce: u32, pub previous_block: Option, + pub subsidy: u64, pub target: BlockHash, pub timestamp: u64, + pub total_fee: u64, + pub total_size: usize, + pub total_weight: usize, pub transaction_count: u64, pub version: u32, } diff --git a/src/index.rs b/src/index.rs index dc4e687070..935ebe80da 100644 --- a/src/index.rs +++ b/src/index.rs @@ -15,7 +15,10 @@ use { templates::StatusHtml, }, bitcoin::block::Header, - bitcoincore_rpc::{json::GetBlockHeaderResult, Client}, + bitcoincore_rpc::{ + json::{GetBlockHeaderResult, GetBlockStatsResult}, + Client, + }, chrono::SubsecRound, indicatif::{ProgressBar, ProgressStyle}, log::log_enabled, @@ -992,6 +995,10 @@ impl Index { self.client.get_block_header_info(&hash).into_option() } + pub(crate) fn block_stats(&self, height: u64) -> Result> { + self.client.get_block_stats(height).into_option() + } + pub(crate) fn get_block_by_height(&self, height: u32) -> Result> { Ok( self diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index bb6449cc1f..2d8691b81c 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -79,15 +79,6 @@ impl Display for StaticHtml { } } -fn chainwork(chainwork: &[u8]) -> u128 { - chainwork - .iter() - .rev() - .enumerate() - .map(|(i, byte)| u128::from(*byte) * 256u128.pow(i.try_into().unwrap())) - .sum() -} - #[derive(Debug, Parser, Clone)] pub struct Server { #[arg( @@ -1130,30 +1121,46 @@ impl Server { .ok_or_not_found(|| format!("block {height}"))?, }; + let header = index + .block_header(hash)? + .ok_or_not_found(|| format!("block {hash}"))?; + let info = index .block_header_info(hash)? .ok_or_not_found(|| format!("block {hash}"))?; - let header = index - .block_header(hash)? + let stats = index + .block_stats(info.height.try_into().unwrap())? .ok_or_not_found(|| format!("block {hash}"))?; Ok(Json(api::BlockInfo { + average_fee: stats.avg_fee.to_sat(), + average_fee_rate: stats.avg_fee_rate.to_sat(), bits: header.bits.to_consensus(), - chainwork: chainwork(&info.chainwork), + chainwork: info.chainwork.try_into().unwrap(), confirmations: info.confirmations, difficulty: info.difficulty, hash, height: info.height.try_into().unwrap(), + max_fee: stats.max_fee.to_sat(), + max_fee_rate: stats.max_fee_rate.to_sat(), + max_tx_size: stats.max_tx_size, + median_fee: stats.median_fee.to_sat(), median_time: info .median_time .map(|median_time| median_time.try_into().unwrap()), merkle_root: info.merkle_root, + min_fee: stats.min_fee.to_sat(), + min_fee_rate: stats.min_fee_rate.to_sat(), next_block: info.next_block_hash, nonce: info.nonce, previous_block: info.previous_block_hash, + subsidy: stats.subsidy.to_sat(), target: target_as_block_hash(header.target()), timestamp: info.time.try_into().unwrap(), + total_fee: stats.total_fee.to_sat(), + total_size: stats.total_size, + total_weight: stats.total_weight, transaction_count: info.n_tx.try_into().unwrap(), #[allow(clippy::cast_sign_loss)] version: info.version.to_consensus() as u32, @@ -1988,6 +1995,7 @@ mod tests { Builder::default().build() } + #[track_caller] fn get(&self, path: impl AsRef) -> reqwest::blocking::Response { if let Err(error) = self.index.update() { log::error!("{error}"); @@ -1995,6 +2003,7 @@ mod tests { reqwest::blocking::get(self.join_url(path.as_ref())).unwrap() } + #[track_caller] pub(crate) fn get_json(&self, path: impl AsRef) -> T { if let Err(error) = self.index.update() { log::error!("{error}"); @@ -5549,17 +5558,6 @@ next server_with_proxy.assert_response(format!("/content/{id}"), StatusCode::OK, "foo"); } - #[test] - fn chainwork_conversion_to_integer() { - assert_eq!(chainwork(&[]), 0); - assert_eq!(chainwork(&[1]), 1); - assert_eq!(chainwork(&[1, 0]), 256); - assert_eq!(chainwork(&[1, 1]), 257); - assert_eq!(chainwork(&[1, 0, 0]), 65536); - assert_eq!(chainwork(&[1, 0, 1]), 65537); - assert_eq!(chainwork(&[1, 1, 1]), 65793); - } - #[test] fn block_info() { let server = TestServer::new(); @@ -5567,23 +5565,35 @@ next pretty_assert_eq!( server.get_json::("/r/blockinfo/0"), api::BlockInfo { + average_fee: 0, + average_fee_rate: 0, bits: 486604799, - chainwork: 0, + chainwork: [0; 32], confirmations: 0, difficulty: 0.0, hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" .parse() .unwrap(), height: 0, + max_fee: 0, + max_fee_rate: 0, + max_tx_size: 0, + median_fee: 0, median_time: None, merkle_root: TxMerkleNode::all_zeros(), + min_fee: 0, + min_fee_rate: 0, next_block: None, nonce: 0, previous_block: None, + subsidy: 0, target: "00000000ffff0000000000000000000000000000000000000000000000000000" .parse() .unwrap(), timestamp: 0, + total_fee: 0, + total_size: 0, + total_weight: 0, transaction_count: 0, version: 1, }, @@ -5594,21 +5604,33 @@ next pretty_assert_eq!( server.get_json::("/r/blockinfo/1"), api::BlockInfo { + average_fee: 0, + average_fee_rate: 0, bits: 0, - chainwork: 0, + chainwork: [0; 32], confirmations: 0, difficulty: 0.0, hash: "56d05060a0280d0712d113f25321158747310ece87ea9e299bde06cf385b8d85" .parse() .unwrap(), height: 1, + max_fee: 0, + max_fee_rate: 0, + max_tx_size: 0, + median_fee: 0, median_time: None, merkle_root: TxMerkleNode::all_zeros(), + min_fee: 0, + min_fee_rate: 0, next_block: None, nonce: 0, previous_block: None, + subsidy: 0, target: BlockHash::all_zeros(), timestamp: 0, + total_fee: 0, + total_size: 0, + total_weight: 0, transaction_count: 0, version: 1, },