diff --git a/crates/pallet-bitcoin/src/lib.rs b/crates/pallet-bitcoin/src/lib.rs index 3c46b8584b2a2..e80e924cfdd81 100644 --- a/crates/pallet-bitcoin/src/lib.rs +++ b/crates/pallet-bitcoin/src/lib.rs @@ -49,9 +49,10 @@ impl Txid { Self(H256::from(d)) } + /// Converts the runtime [`Txid`] to a `bitcoin::Txid`. pub fn into_bitcoin_txid(self) -> bitcoin::Txid { bitcoin::consensus::Decodable::consensus_decode(&mut self.encode().as_slice()) - .expect("txid must be encoded correctly; qed") + .expect("Decode must succeed as txid was ensured to be encoded correctly; qed") } } @@ -98,6 +99,7 @@ pub mod pallet { #[pallet::call(weight(::WeightInfo))] impl Pallet { + /// An internal unsigned extrinsic for including a Bitcoin transaction into the block. #[pallet::call_index(0)] #[pallet::weight(Weight::zero())] pub fn transact(origin: OriginFor, btc_tx: Vec) -> DispatchResult { @@ -183,6 +185,7 @@ pub fn coin_storage_key(bitcoin_txid: bitcoin::Txid, index: Vout) -> Coins::::storage_double_map_final_key(txid, index) } +/// Returns the final storage prefix for the storage item `Coins`. pub fn coin_storage_prefix() -> [u8; 32] { use frame_support::StoragePrefixedMap; diff --git a/crates/sc-consensus-nakamoto/src/block_executor.rs b/crates/sc-consensus-nakamoto/src/block_executor.rs index 6e93e97261412..82bcc28321e68 100644 --- a/crates/sc-consensus-nakamoto/src/block_executor.rs +++ b/crates/sc-consensus-nakamoto/src/block_executor.rs @@ -14,18 +14,28 @@ use subcoin_primitives::{BitcoinTransactionAdapter, CoinStorageKey}; /// A simply way to track the overall execution info for optimization purpose. #[derive(Debug, Default)] pub struct ExecutionInfo { + /// Number of transactions in the block. + pub transactions_count: usize, /// Time taken by `runtime_api.execute_block` in nanoseconds. - pub execute_block: u128, + pub execute_block_time: u128, /// Time taken by `client.state_at` in nanoseconds. - pub fetch_state: u128, + pub fetch_state_time: u128, /// Time taken by `runtime_api.into_storage_changes` in nanoseconds. - pub into_storage_changes: u128, + pub into_storage_changes_time: u128, } impl ExecutionInfo { + /// Constructs a new instance of [`ExecutionInfo`] with given transactions count. + pub fn new(transactions_count: usize) -> Self { + Self { + transactions_count, + ..Default::default() + } + } + /// Returns the total execution time in nanoseconds. pub fn total(&self) -> u128 { - self.execute_block + self.fetch_state + self.into_storage_changes + self.execute_block_time + self.fetch_state_time + self.into_storage_changes_time } } @@ -194,21 +204,21 @@ where let mut runtime_api = self.client.runtime_api(); runtime_api.set_call_context(CallContext::Onchain); - let mut exec_info = ExecutionInfo::default(); + let mut exec_info = ExecutionInfo::new(block.extrinsics().len()); let now = std::time::Instant::now(); runtime_api.execute_block_without_state_root_check(parent_hash, block)?; - exec_info.execute_block = now.elapsed().as_nanos(); + exec_info.execute_block_time = now.elapsed().as_nanos(); let now = std::time::Instant::now(); let state = self.client.state_at(parent_hash)?; - exec_info.fetch_state = now.elapsed().as_nanos(); + exec_info.fetch_state_time = now.elapsed().as_nanos(); let now = std::time::Instant::now(); let storage_changes = runtime_api .into_storage_changes(&state, parent_hash) .map_err(sp_blockchain::Error::StorageChanges)?; - exec_info.into_storage_changes = now.elapsed().as_nanos(); + exec_info.into_storage_changes_time = now.elapsed().as_nanos(); let state_root = storage_changes.transaction_storage_root; @@ -411,7 +421,7 @@ where let (header, extrinsics) = block.deconstruct(); - let mut exec_info = ExecutionInfo::default(); + let mut exec_info = ExecutionInfo::new(extrinsics.len()); let now = std::time::Instant::now(); @@ -450,17 +460,17 @@ where tracing::debug!("off_runtime({:?}): {exec_details:?}", self.client_context); - exec_info.execute_block = now.elapsed().as_nanos(); + exec_info.execute_block_time = now.elapsed().as_nanos(); let now = std::time::Instant::now(); let state = self.client.state_at(parent_hash)?; - exec_info.fetch_state = now.elapsed().as_nanos(); + exec_info.fetch_state_time = now.elapsed().as_nanos(); let now = std::time::Instant::now(); let storage_changes = runtime_api .into_storage_changes(&state, parent_hash) .map_err(sp_blockchain::Error::StorageChanges)?; - exec_info.into_storage_changes = now.elapsed().as_nanos(); + exec_info.into_storage_changes_time = now.elapsed().as_nanos(); tracing::debug!( "off_runtime({:?}): {exec_info:?}, total: {}", diff --git a/crates/sc-consensus-nakamoto/src/chain_params.rs b/crates/sc-consensus-nakamoto/src/chain_params.rs index 7a63f20307199..9a4f1849aa231 100644 --- a/crates/sc-consensus-nakamoto/src/chain_params.rs +++ b/crates/sc-consensus-nakamoto/src/chain_params.rs @@ -2,6 +2,7 @@ use bitcoin::consensus::Params; use bitcoin::{BlockHash, Network}; use std::collections::HashMap; +/// bip-0113 defines the median of the last 11 blocks instead of the block's timestamp for lock-time calculations. pub const MEDIAN_TIME_SPAN: usize = 11; /// Extended [`Params`]. @@ -13,6 +14,11 @@ pub struct ChainParams { pub csv_height: u32, /// Block height at which Segwit becomes active. pub segwit_height: u32, + /// A map of block hashes to script verification flag exceptions. + /// + /// This allows for certain blocks to have specific script verification flags, overriding + /// the default rules. For example, exceptions may be made for blocks that activated + /// BIP16 (P2SH) or Taproot under special conditions. pub script_flag_exceptions: HashMap, } diff --git a/crates/sc-consensus-nakamoto/src/import_queue.rs b/crates/sc-consensus-nakamoto/src/import_queue.rs index feef266b8c352..aacd3e101e44b 100644 --- a/crates/sc-consensus-nakamoto/src/import_queue.rs +++ b/crates/sc-consensus-nakamoto/src/import_queue.rs @@ -15,27 +15,34 @@ use sp_core::traits::SpawnEssentialNamed; use sp_runtime::traits::Block as BlockT; use std::pin::Pin; +/// Represents a batch of Bitcoin blocks that are to be imported. #[derive(Debug, Clone)] pub struct ImportBlocks { + /// The source from which the blocks were obtained. pub origin: BlockOrigin, + /// A vector containing the Bitcoin blocks to be imported. pub blocks: Vec, } /// Import queue for processing Bitcoin blocks. #[derive(Debug)] pub struct BlockImportQueue { - pub block_import_sender: TracingUnboundedSender, - pub import_result_receiver: TracingUnboundedReceiver, + block_import_sender: TracingUnboundedSender, + import_result_receiver: TracingUnboundedReceiver, } impl BlockImportQueue { - /// Send blocks to the actual worker of import queue. + /// Sends a batch of blocks to the worker of import queue for processing. pub fn import_blocks(&self, incoming_blocks: ImportBlocks) { let _ = self.block_import_sender.unbounded_send(incoming_blocks); } - pub async fn next_import_results(&mut self) -> Option { - self.import_result_receiver.next().await + /// Retrieves the results of the block import operations. + /// + /// This asynchronous function waits for and returns the results of the block import process. + /// It consumes the next available result from the import queue. + pub async fn block_import_results(&mut self) -> ImportManyBlocksResult { + self.import_result_receiver.select_next_some().await } } @@ -90,6 +97,7 @@ where } } +/// A dummy verifier that verifies nothing against the block. pub struct VerifyNothing; #[async_trait::async_trait] diff --git a/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs b/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs index 3ec16b63b075e..b5c8cad73a612 100644 --- a/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs +++ b/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs @@ -34,6 +34,7 @@ pub enum Error { PreviousOutputNull, } +/// Checks whether the transaction is final at the given height and block time. pub fn is_final(tx: &Transaction, height: u32, block_time: u32) -> bool { if tx.lock_time == LockTime::ZERO { return true; @@ -107,6 +108,7 @@ pub fn check_transaction_sanity(tx: &Transaction) -> Result<(), Error> { Ok(()) } +/// Counts the sigops for this transaction using legacy counting. pub fn get_legacy_sig_op_count(tx: &Transaction) -> usize { tx.input .iter() diff --git a/crates/subcoin-network/src/address_book.rs b/crates/subcoin-network/src/address_book.rs index a172ac66f8517..1a99ff070603d 100644 --- a/crates/subcoin-network/src/address_book.rs +++ b/crates/subcoin-network/src/address_book.rs @@ -6,11 +6,17 @@ use std::net::IpAddr; /// Manages the addresses discovered in the network. #[derive(Debug)] pub struct AddressBook { + /// Addresses available for establishing new connections. discovered_addresses: HashSet, + /// Peers that currently have an active connection or are being communicated with. active_addresses: HashSet, + /// Addresses that failed to establish a connection. failed_addresses: HashSet, + /// Indicates whether only IPv4 addresses should be stored. ipv4_only: bool, + /// Maximum number of discovered addresses. max_addresses: usize, + /// Random number generator for selecting peers. rng: fastrand::Rng, } @@ -38,14 +44,12 @@ impl AddressBook { /// Pops a random address from the discovered addresses and marks it as active. pub fn pop(&mut self) -> Option { - let maybe_peer = self.rng.choice(self.discovered_addresses.clone()); - - if let Some(peer) = maybe_peer { + if let Some(peer) = self.rng.choice(self.discovered_addresses.iter()).copied() { self.discovered_addresses.remove(&peer); self.active_addresses.insert(peer); + return Some(peer); } - - maybe_peer + None } pub fn note_failed_address(&mut self, peer_addr: PeerId) { @@ -53,6 +57,10 @@ impl AddressBook { self.failed_addresses.insert(peer_addr); } + pub fn mark_disconnected(&mut self, peer_addr: &PeerId) { + self.active_addresses.remove(peer_addr); + } + /// Adds multiple addresses (`Address`) to the address book. pub fn add_many(&mut self, from: PeerId, addresses: Vec<(u32, Address)>) -> usize { let mut added = 0; diff --git a/crates/subcoin-network/src/block_downloader.rs b/crates/subcoin-network/src/block_downloader.rs index d40c0d3e8ed2b..7c43a78abe617 100644 --- a/crates/subcoin-network/src/block_downloader.rs +++ b/crates/subcoin-network/src/block_downloader.rs @@ -11,6 +11,9 @@ use sc_consensus_nakamoto::ImportManyBlocksResult; use std::collections::{HashMap, HashSet}; use std::time::{Duration, Instant}; +/// Interval for logging when the block import queue is too busy. +const BUSY_QUEUE_LOG_INTERVAL: Duration = Duration::from_secs(5); + /// Manages queued blocks. #[derive(Default, Debug, Clone)] pub(crate) struct QueuedBlocks { @@ -75,6 +78,10 @@ pub(crate) struct BlockDownloadManager { /// Orphan blocks orphan_blocks_pool: OrphanBlocksPool, /// Last time at which the block was received or imported. + /// + /// This is updated whenever a block is received from the network or + /// when the results of processed blocks are notified. It helps track + /// the most recent activity related to block processing. last_progress_time: Instant, /// Whether there are too many blocks in the queue. import_queue_is_overloaded: bool, @@ -97,12 +104,14 @@ impl BlockDownloadManager { } } + // Determine if the downloader is stalled based on the time elapsed since the last progress. fn is_stalled(&self) -> bool { - // The downloader is considered as stalled if no progress for some time. + // The timeout (in seconds) is extended when the chain exceeds a certain size, as block + // execution times increase significantly with chain growth. let stall_timeout = if self.best_queued_number > 300_000 { - 120 + 120 // Extended timeout for larger chains } else { - 60 + 60 // Standard timeout for smaller chains }; self.last_progress_time.elapsed().as_secs() > stall_timeout @@ -133,27 +142,24 @@ impl BlockDownloadManager { let max_queued_blocks = match best_number { 0..=100_000 => 8192, 100_001..=200_000 => 4096, - 200_001..=300_000 => 1024, + 200_001..=300_000 => 2048, _ => 512, }; let import_queue_is_overloaded = self.best_queued_number - best_number > max_queued_blocks; - if import_queue_is_overloaded { - const INTERVAL: Duration = Duration::from_secs(5); - - if self + if import_queue_is_overloaded + && self .last_overloaded_queue_log_time - .map(|last_time| last_time.elapsed() > INTERVAL) + .map(|last_time| last_time.elapsed() > BUSY_QUEUE_LOG_INTERVAL) .unwrap_or(true) - { - tracing::debug!( - best_number, - best_queued_number = self.best_queued_number, - "⏸️ Pausing download: too many blocks in the queue", - ); - self.last_overloaded_queue_log_time.replace(Instant::now()); - } + { + tracing::debug!( + best_number, + best_queued_number = self.best_queued_number, + "⏸️ Pausing download: too many blocks in the queue", + ); + self.last_overloaded_queue_log_time.replace(Instant::now()); } self.import_queue_is_overloaded = import_queue_is_overloaded; diff --git a/crates/subcoin-network/src/block_downloader/headers_first.rs b/crates/subcoin-network/src/block_downloader/headers_first.rs index 524aff16af77b..2b69c1a5eb855 100644 --- a/crates/subcoin-network/src/block_downloader/headers_first.rs +++ b/crates/subcoin-network/src/block_downloader/headers_first.rs @@ -15,6 +15,9 @@ use std::net::IpAddr; use std::sync::Arc; use subcoin_primitives::{BackendExt, BlockLocatorProvider, ClientExt, IndexedBlock}; +// https://developer.bitcoin.org/reference/p2p_networking.html#headers +const MAX_HEADERS_SIZE: usize = 2000; + /// Represents the range of blocks to be downloaded during the headers-first sync. /// /// # Note @@ -269,9 +272,6 @@ where // // `headers` are expected to contain at most 2000 entries, in ascending order. [b1, b2, b3, ..., b2000]. pub(crate) fn on_headers(&mut self, headers: Vec, from: PeerId) -> SyncAction { - // https://developer.bitcoin.org/reference/p2p_networking.html#headers - const MAX_HEADERS_SIZE: usize = 2000; - if headers.len() > MAX_HEADERS_SIZE { return SyncAction::Disconnect(from, Error::TooManyHeaders); } @@ -307,19 +307,13 @@ where "Cannot find the parent of the first header in headers, disconnecting" ); self.download_state = DownloadState::Disconnecting; - return SyncAction::Disconnect( - self.peer_id, - Error::Other("Cannot find the parent of the first header".to_string()), - ); + return SyncAction::Disconnect(self.peer_id, Error::ParentOfFirstHeaderEntryNotFound); }; for header in headers { if header.prev_blockhash != prev_hash { self.download_state = DownloadState::Disconnecting; - return SyncAction::Disconnect( - self.peer_id, - Error::Other("Invalid headers: not in ascending order".to_string()), - ); + return SyncAction::Disconnect(self.peer_id, Error::HeadersNotInAscendingOrder); } // TODO: Verify header? @@ -342,7 +336,7 @@ where if final_block_number == target_block_number { self.start_block_download(start, end) } else { - tracing::debug!("πŸ“„ Downloading headers ({final_block_number}/{target_block_number})"); + tracing::debug!("πŸ“„ Downloaded headers ({final_block_number}/{target_block_number})"); SyncAction::Request(SyncRequest::Headers(LocatorRequest { locator_hashes: vec![prev_hash], @@ -358,7 +352,6 @@ where let best_number = self.client.best_number(); - let downloaded_headers = self.downloaded_headers.len(); let missing_blocks = self.downloaded_headers .iter() @@ -387,7 +380,7 @@ where best_number, best_queued_number = self.download_manager.best_queued_number, requested_blocks_count = get_data_msg.len(), - downloaded_headers, + downloaded_headers = self.downloaded_headers.len(), "Headers from {start} to {end} downloaded, requesting blocks", ); @@ -420,7 +413,7 @@ where tracing::debug!( best_number, best_queued_number = self.download_manager.best_queued_number, - downloaded_headers, + downloaded_headers = self.downloaded_headers.len(), "Headers downloaded, requesting {} blocks in batches (1/{total_batches})", get_data_msg.len(), ); @@ -502,7 +495,7 @@ where tracing::debug!( best_number = self.client.best_number(), best_queued_number = self.download_manager.best_queued_number, - "πŸ“¦ Downloading {} blocks in batches ({}/{})", + "πŸ“¦ Downloaded {} blocks in batches ({}/{})", next_batch.len(), *downloaded_batch + 1, *downloaded_batch + 1 + waiting.len() @@ -601,7 +594,7 @@ fn prepare_ordered_block_data_request( .map(|block_hash| { let block_number = downloaded_headers .get(&block_hash) - .expect("Header must exist before downloading blocks in headers-first sync; qed"); + .expect("Header must exist before downloading blocks in headers-first mode; qed"); (block_number, block_hash) }) .collect::>(); diff --git a/crates/subcoin-network/src/lib.rs b/crates/subcoin-network/src/lib.rs index cc7c1cb8724c4..57bc3a27a0a84 100644 --- a/crates/subcoin-network/src/lib.rs +++ b/crates/subcoin-network/src/lib.rs @@ -94,8 +94,10 @@ pub enum Error { ProtocolVersionTooLow, #[error("Too many block entries in inv message")] TooManyBlockEntries, - #[error("Too many headers (> 2000)")] + #[error("Too many entries (> 2000) in headers message")] TooManyHeaders, + #[error("Entries in headers message are not in ascending order")] + HeadersNotInAscendingOrder, #[error("Too many inventory items")] TooManyInventoryItems, #[error("Ping timeout")] @@ -110,6 +112,8 @@ pub enum Error { BadPong, #[error("Received an unrequested block: {0:?}")] UnrequestedBlock(BlockHash), + #[error("Cannot find the parent of the first header in headers message")] + ParentOfFirstHeaderEntryNotFound, #[error("Other: {0}")] Other(String), #[error(transparent)] diff --git a/crates/subcoin-network/src/peer_manager.rs b/crates/subcoin-network/src/peer_manager.rs index db09ee7f5a15a..cbef1864692f0 100644 --- a/crates/subcoin-network/src/peer_manager.rs +++ b/crates/subcoin-network/src/peer_manager.rs @@ -447,6 +447,7 @@ where } } + self.address_book.mark_disconnected(&peer_id); self.handshaking_peers.remove(&peer_id); self.connected_peers.remove(&peer_id); self.connection_latencies.remove(&peer_id); diff --git a/crates/subcoin-network/src/sync.rs b/crates/subcoin-network/src/sync.rs index d23295fc2e539..438a4152c47c9 100644 --- a/crates/subcoin-network/src/sync.rs +++ b/crates/subcoin-network/src/sync.rs @@ -4,7 +4,6 @@ use crate::{Error, Latency, PeerId, SyncStatus, SyncStrategy}; use bitcoin::blockdata::block::Header as BitcoinHeader; use bitcoin::p2p::message_blockdata::Inventory; use bitcoin::{Block as BitcoinBlock, BlockHash}; -use futures::StreamExt; use sc_client_api::{AuxStore, HeaderBackend}; use sc_consensus_nakamoto::{BlockImportQueue, ImportBlocks, ImportManyBlocksResult}; use serde::{Deserialize, Serialize}; @@ -200,10 +199,7 @@ where } pub(super) async fn wait_for_block_import_results(&mut self) -> ImportManyBlocksResult { - self.import_queue - .import_result_receiver - .select_next_some() - .await + self.import_queue.block_import_results().await } /// Attempts to restart the sync due to the stalled peer. diff --git a/crates/subcoin-node/src/commands/blockchain.rs b/crates/subcoin-node/src/commands/blockchain.rs index 7145b0ac2090f..5d3dd2a779e30 100644 --- a/crates/subcoin-node/src/commands/blockchain.rs +++ b/crates/subcoin-node/src/commands/blockchain.rs @@ -6,6 +6,7 @@ use sc_consensus_nakamoto::BlockExecutionStrategy; use sp_core::storage::StorageKey; use sp_core::Decode; use std::sync::Arc; +use std::time::{Duration, Instant}; use subcoin_primitives::runtime::Coin; use subcoin_primitives::{BackendExt, CoinStorageKey}; use subcoin_service::FullClient; @@ -19,9 +20,16 @@ pub enum Blockchain { #[clap(long)] height: Option, + #[clap(short, long)] + verbose: bool, + #[allow(missing_docs)] #[clap(flatten)] common_params: CommonParams, + + #[allow(missing_docs)] + #[clap(flatten)] + import_params: ImportParams, }, } @@ -37,6 +45,8 @@ pub enum BlockchainCmd { GetTxOutSetInfo { height: Option, shared_params: SharedParams, + import_params: ImportParams, + verbose: bool, }, } @@ -47,9 +57,13 @@ impl BlockchainCmd { Blockchain::GetTxOutSetInfo { height, common_params, + import_params, + verbose, } => Self::GetTxOutSetInfo { height, shared_params: common_params.as_shared_params(), + import_params, + verbose, }, } } @@ -62,7 +76,9 @@ impl BlockchainCmd { pub async fn run(self, client: Arc) -> sc_cli::Result<()> { match self { - Self::GetTxOutSetInfo { height, .. } => gettxoutsetinfo(&client, height).await, + Self::GetTxOutSetInfo { + height, verbose, .. + } => gettxoutsetinfo(&client, height, verbose).await, } } } @@ -73,7 +89,9 @@ impl sc_cli::CliConfiguration for BlockchainCmd { } fn import_params(&self) -> Option<&ImportParams> { - None + match self { + Self::GetTxOutSetInfo { import_params, .. } => Some(import_params), + } } fn node_key_params(&self) -> Option<&NodeKeyParams> { @@ -81,7 +99,11 @@ impl sc_cli::CliConfiguration for BlockchainCmd { } } -async fn gettxoutsetinfo(client: &Arc, height: Option) -> sc_cli::Result<()> { +async fn gettxoutsetinfo( + client: &Arc, + height: Option, + verbose: bool, +) -> sc_cli::Result<()> { const FINAL_PREFIX_LEN: usize = 32; let storage_prefix = subcoin_service::CoinStorageKey.storage_prefix(); @@ -92,44 +114,69 @@ async fn gettxoutsetinfo(client: &Arc, height: Option) -> sc_cl .storage_pairs(block_hash, Some(&storage_key), None)? .map(|(key, data)| (key.0, data.0)); - let mut txouts = 0; - let mut bogosize = 0; - let mut total_amount = 0; - let genesis_txid: bitcoin::Txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" .parse() .expect("Genesis txid must be correct; qed"); + const INTERVAL: Duration = Duration::from_secs(5); + + let bitcoin_block_hash = client + .block_hash(block_number) + .expect("Bitcoin block hash missing"); + + println!("Fetching state info at block_number: #{block_number}, {bitcoin_block_hash}"); + + let mut txouts = 0; + let mut bogosize = 0; + let mut total_amount = 0; + + let mut state_size = 0; + let mut script_pubkey_size = 0; + + let mut last_update = Instant::now(); + for (key, value) in pairs_iter { let (txid, _vout) = <(pallet_bitcoin::Txid, u32)>::decode(&mut &key.as_slice()[FINAL_PREFIX_LEN..]) .expect("Key type must be correct; qed"); let txid = txid.into_bitcoin_txid(); + // output in genesis tx is excluded in gettxoutsetinfo. if txid == genesis_txid { continue; } + let coin = Coin::decode(&mut value.as_slice()) .expect("Coin read from DB must be decoded successfully; qed"); + txouts += 1; total_amount += coin.amount; // https://github.com/bitcoin/bitcoin/blob/33af14e31b9fa436029a2bb8c2b11de8feb32f86/src/kernel/coinstats.cpp#L40 bogosize += 50 + coin.script_pubkey.len(); + state_size += key.len() + value.len(); + script_pubkey_size += coin.script_pubkey.len(); + + if verbose && last_update.elapsed() > INTERVAL { + println!( + "Progress: Unspent Transaction Outputs: {txouts}, State Size: {state_size} bytes, \ + ScriptPubkey Size: {script_pubkey_size} bytes, Coin ScriptPubkey Length: {} bytes", + coin.script_pubkey.len() + ); + last_update = Instant::now(); + } + // Yield here allows to make the process interruptible by ctrl_c. Yield::new().await; } - let bitcoin_block_hash = client - .block_hash(block_number) - .expect("Bitcoin block hash missing"); - - println!("block_number: {block_number}"); - println!("block_hash: {bitcoin_block_hash}"); + println!("===================="); println!("txouts: {txouts}"); println!("bogosize: {bogosize}"); println!("total_amount: {:.8}", total_amount as f64 / 100_000_000.0); + println!("state_size: {state_size} bytes"); + println!("script_pubkey_size: {script_pubkey_size} bytes"); Ok(()) }