diff --git a/chain/chain/src/chain_update.rs b/chain/chain/src/chain_update.rs index d59af7a6e1d..983727036bc 100644 --- a/chain/chain/src/chain_update.rs +++ b/chain/chain/src/chain_update.rs @@ -145,6 +145,11 @@ impl<'a> ChainUpdate<'a> { apply_result.contract_updates, ); } + self.chain_store_update.save_chunk_apply_stats( + *block_hash, + shard_id, + apply_result.stats, + ); } ShardUpdateResult::OldChunk(OldChunkResult { shard_uid, apply_result }) => { // The chunk is missing but some fields may need to be updated @@ -175,6 +180,11 @@ impl<'a> ChainUpdate<'a> { apply_result.contract_updates, ); } + self.chain_store_update.save_chunk_apply_stats( + *block_hash, + shard_uid.shard_id(), + apply_result.stats, + ); } }; Ok(()) diff --git a/chain/chain/src/garbage_collection.rs b/chain/chain/src/garbage_collection.rs index 5feaa7c7644..ea7927cfc8c 100644 --- a/chain/chain/src/garbage_collection.rs +++ b/chain/chain/src/garbage_collection.rs @@ -648,6 +648,7 @@ impl<'a> ChainStoreUpdate<'a> { self.gc_outgoing_receipts(&block_hash, shard_id); self.gc_col(DBCol::IncomingReceipts, &block_shard_id); self.gc_col(DBCol::StateTransitionData, &block_shard_id); + self.gc_col(DBCol::ChunkApplyStats, &block_shard_id); // For incoming State Parts it's done in chain.clear_downloaded_parts() // The following code is mostly for outgoing State Parts. @@ -1016,6 +1017,9 @@ impl<'a> ChainStoreUpdate<'a> { DBCol::StateSyncNewChunks => { store_update.delete(col, key); } + DBCol::ChunkApplyStats => { + store_update.delete(col, key); + } DBCol::DbVersion | DBCol::BlockMisc | DBCol::_GCCount diff --git a/chain/chain/src/runtime/mod.rs b/chain/chain/src/runtime/mod.rs index dcd401fdbe2..858bbf2e53b 100644 --- a/chain/chain/src/runtime/mod.rs +++ b/chain/chain/src/runtime/mod.rs @@ -354,9 +354,10 @@ impl NightshadeRuntime { let total_balance_burnt = apply_result .stats + .balance .tx_burnt_amount - .checked_add(apply_result.stats.other_burnt_amount) - .and_then(|result| result.checked_add(apply_result.stats.slashed_burnt_amount)) + .checked_add(apply_result.stats.balance.other_burnt_amount) + .and_then(|result| result.checked_add(apply_result.stats.balance.slashed_burnt_amount)) .ok_or_else(|| { Error::Other("Integer overflow during burnt balance summation".to_string()) })?; @@ -386,6 +387,7 @@ impl NightshadeRuntime { bandwidth_requests: apply_result.bandwidth_requests, bandwidth_scheduler_state_hash: apply_result.bandwidth_scheduler_state_hash, contract_updates: apply_result.contract_updates, + stats: apply_result.stats, }; Ok(result) diff --git a/chain/chain/src/store/mod.rs b/chain/chain/src/store/mod.rs index b170aaa6ccf..2d1a0d5e0b5 100644 --- a/chain/chain/src/store/mod.rs +++ b/chain/chain/src/store/mod.rs @@ -10,6 +10,7 @@ use near_chain_primitives::error::Error; use near_epoch_manager::EpochManagerAdapter; use near_primitives::block::Tip; use near_primitives::checked_feature; +use near_primitives::chunk_apply_stats::{ChunkApplyStats, ChunkApplyStatsV0}; use near_primitives::errors::InvalidTxError; use near_primitives::hash::CryptoHash; use near_primitives::merkle::{MerklePath, PartialMerkleTree}; @@ -133,6 +134,11 @@ pub trait ChainStoreAccess { block_hash: &CryptoHash, shard_uid: &ShardUId, ) -> Result, Error>; + fn get_chunk_apply_stats( + &self, + block_hash: &CryptoHash, + shard_id: &ShardId, + ) -> Result, Error>; /// Get block header. fn get_block_header(&self, h: &CryptoHash) -> Result; /// Returns hash of the block on the main chain for given height. @@ -1068,6 +1074,14 @@ impl ChainStoreAccess for ChainStore { ChainStoreAdapter::get_chunk_extra(self, block_hash, shard_uid) } + fn get_chunk_apply_stats( + &self, + block_hash: &CryptoHash, + shard_id: &ShardId, + ) -> Result, Error> { + ChainStoreAdapter::get_chunk_apply_stats(&self, block_hash, shard_id) + } + /// Get block header. fn get_block_header(&self, h: &CryptoHash) -> Result { ChainStoreAdapter::get_block_header(self, h) @@ -1211,6 +1225,7 @@ pub struct ChainStoreUpdate<'a> { add_state_sync_infos: Vec, remove_state_sync_infos: Vec, challenged_blocks: HashSet, + chunk_apply_stats: HashMap<(CryptoHash, ShardId), ChunkApplyStats>, } impl<'a> ChainStoreUpdate<'a> { @@ -1234,6 +1249,7 @@ impl<'a> ChainStoreUpdate<'a> { add_state_sync_infos: vec![], remove_state_sync_infos: vec![], challenged_blocks: HashSet::default(), + chunk_apply_stats: HashMap::default(), } } } @@ -1361,6 +1377,18 @@ impl<'a> ChainStoreAccess for ChainStoreUpdate<'a> { } } + fn get_chunk_apply_stats( + &self, + block_hash: &CryptoHash, + shard_id: &ShardId, + ) -> Result, Error> { + if let Some(stats) = self.chunk_apply_stats.get(&(*block_hash, *shard_id)) { + Ok(Some(stats.clone())) + } else { + self.chain_store.get_chunk_apply_stats(block_hash, shard_id) + } + } + /// Get block header. fn get_block_header(&self, hash: &CryptoHash) -> Result { if let Some(header) = self.chain_store_cache_update.headers.get(hash).cloned() { @@ -1881,6 +1909,15 @@ impl<'a> ChainStoreUpdate<'a> { self.chain_store_cache_update.processed_block_heights.insert(height); } + pub fn save_chunk_apply_stats( + &mut self, + block_hash: CryptoHash, + shard_id: ShardId, + stats: ChunkApplyStatsV0, + ) { + self.chunk_apply_stats.insert((block_hash, shard_id), ChunkApplyStats::V0(stats)); + } + pub fn inc_block_refcount(&mut self, block_hash: &CryptoHash) -> Result<(), Error> { let refcount = match self.get_block_refcount(block_hash) { Ok(refcount) => refcount, @@ -2409,6 +2446,13 @@ impl<'a> ChainStoreUpdate<'a> { &(), )?; } + for ((block_hash, shard_id), stats) in self.chunk_apply_stats.iter() { + store_update.set_ser( + DBCol::ChunkApplyStats, + &get_block_shard_id(block_hash, *shard_id), + stats, + )?; + } for other in self.store_updates.drain(..) { store_update.merge(other); } diff --git a/chain/chain/src/test_utils/kv_runtime.rs b/chain/chain/src/test_utils/kv_runtime.rs index 47e10c8f9c9..85fb8f633ea 100644 --- a/chain/chain/src/test_utils/kv_runtime.rs +++ b/chain/chain/src/test_utils/kv_runtime.rs @@ -18,6 +18,7 @@ use near_primitives::account::{AccessKey, Account}; use near_primitives::apply::ApplyChunkReason; use near_primitives::bandwidth_scheduler::BandwidthRequests; use near_primitives::block::Tip; +use near_primitives::chunk_apply_stats::ChunkApplyStatsV0; use near_primitives::congestion_info::{CongestionInfo, ExtendedCongestionInfo}; use near_primitives::epoch_block_info::BlockInfo; use near_primitives::epoch_info::EpochInfo; @@ -1280,6 +1281,7 @@ impl RuntimeAdapter for KeyValueRuntime { bandwidth_requests: BandwidthRequests::default_for_protocol_version(PROTOCOL_VERSION), bandwidth_scheduler_state_hash: CryptoHash::default(), contract_updates: Default::default(), + stats: ChunkApplyStatsV0::dummy(), }) } diff --git a/chain/chain/src/types.rs b/chain/chain/src/types.rs index 7307750f5ba..586f480ad67 100644 --- a/chain/chain/src/types.rs +++ b/chain/chain/src/types.rs @@ -14,6 +14,7 @@ use near_primitives::bandwidth_scheduler::BlockBandwidthRequests; pub use near_primitives::block::{Block, BlockHeader, Tip}; use near_primitives::challenge::{ChallengesResult, PartialState}; use near_primitives::checked_feature; +use near_primitives::chunk_apply_stats::ChunkApplyStatsV0; use near_primitives::congestion_info::BlockCongestionInfo; use near_primitives::congestion_info::CongestionInfo; use near_primitives::congestion_info::ExtendedCongestionInfo; @@ -114,6 +115,8 @@ pub struct ApplyChunkResult { pub bandwidth_scheduler_state_hash: CryptoHash, /// Contracts accessed and deployed while applying the chunk. pub contract_updates: ContractUpdates, + /// Extra information gathered during chunk application. + pub stats: ChunkApplyStatsV0, } impl ApplyChunkResult { diff --git a/core/primitives/src/bandwidth_scheduler.rs b/core/primitives/src/bandwidth_scheduler.rs index ad47d82dadf..4c8238662d7 100644 --- a/core/primitives/src/bandwidth_scheduler.rs +++ b/core/primitives/src/bandwidth_scheduler.rs @@ -286,7 +286,7 @@ pub struct LinkAllowance { } /// Parameters used in the bandwidth scheduler algorithm. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct BandwidthSchedulerParams { /// This much bandwidth is granted by default. /// base_bandwidth = (max_shard_bandwidth - max_single_grant) / (num_shards - 1) diff --git a/core/primitives/src/chunk_apply_stats.rs b/core/primitives/src/chunk_apply_stats.rs new file mode 100644 index 00000000000..8cad3fc5682 --- /dev/null +++ b/core/primitives/src/chunk_apply_stats.rs @@ -0,0 +1,224 @@ +use std::collections::BTreeMap; + +use borsh::{BorshDeserialize, BorshSerialize}; +use near_primitives_core::types::{Balance, BlockHeight, Gas, ShardId}; + +use crate::bandwidth_scheduler::{ + Bandwidth, BandwidthRequest, BandwidthRequestValues, BandwidthRequests, + BandwidthSchedulerParams, BlockBandwidthRequests, +}; + +/// Information gathered during chunk application. +/// Provides insight into what happened when the chunk was applied. +/// How many transactions and receipts were processed, buffered, forwarded, etc. +/// Useful for debugging, metrics and sanity checks. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub enum ChunkApplyStats { + V0(ChunkApplyStatsV0), +} + +/// Information gathered during chunk application. +/// This feature is still in development. Consider V0 as unstable, fields might be added or removed +/// from it at any time. We will do proper versioning after stabilization when there will be other +/// services depending on this structure. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct ChunkApplyStatsV0 { + /// Height at which the chunk was applied + pub height: BlockHeight, + /// Shard ID of the chunk + pub shard_id: ShardId, + /// Was this chunk applied as a new (non-missing) chunk or a missing one (apply_old_chunk)? + pub is_new_chunk: bool, + /// Number of new transactions in this chunk + pub transactions_num: u64, + /// Number of incoming receipts to this chunk + pub incoming_receipts_num: u64, + + /// Receipt sink stats - forwarded receipts, buffered receipts, outgoing limits + pub receipt_sink: ReceiptSinkStats, + /// Bandwidth scheduler stats + pub bandwidth_scheduler: BandwidthSchedulerStats, + /// Balance stats - used in balance checker. + pub balance: BalanceStats, +} + +impl ChunkApplyStatsV0 { + pub fn new(height: BlockHeight, shard_id: ShardId) -> ChunkApplyStatsV0 { + ChunkApplyStatsV0 { + height: height, + shard_id: shard_id, + is_new_chunk: false, + transactions_num: 0, + incoming_receipts_num: 0, + bandwidth_scheduler: Default::default(), + balance: Default::default(), + receipt_sink: Default::default(), + } + } + + pub fn set_new_bandwidth_requests( + &mut self, + requests: &BandwidthRequests, + params: &BandwidthSchedulerParams, + ) { + self.bandwidth_scheduler.set_new_bandwidth_requests(self.shard_id, requests, params); + } + + /// Dummy data for tests. + pub fn dummy() -> ChunkApplyStatsV0 { + ChunkApplyStatsV0 { + height: 0, + shard_id: ShardId::new(0), + is_new_chunk: false, + transactions_num: 0, + incoming_receipts_num: 0, + bandwidth_scheduler: Default::default(), + balance: Default::default(), + receipt_sink: Default::default(), + } + } +} + +#[derive(Debug, Clone, Default, BorshSerialize, BorshDeserialize)] +pub struct BandwidthSchedulerStats { + /// Scheduler params, should always be Some but there is no Default impl. + pub params: Option, + /// Bandwidth requests generated by previous chunks, used as input to bandwidth scheduler. + pub prev_bandwidth_requests: BTreeMap<(ShardId, ShardId), Vec>, + /// Number of previous bandwidth requests (prev_bandwidth_requests.len()). + pub prev_bandwidth_requests_num: u64, + /// How long it took to run the bandwidth scheduler (in milliseconds). + pub time_to_run_ms: u128, + /// Bandwidth granted by the scheduler. + pub granted_bandwidth: BTreeMap<(ShardId, ShardId), Bandwidth>, + /// Bandwidth requests generated at the end of chunk application. + pub new_bandwidth_requests: BTreeMap<(ShardId, ShardId), Vec>, +} + +impl BandwidthSchedulerStats { + /// Set `prev_bandwidth_requests` and `prev_bandwidth_requests_num`. Automatically converts to + /// the target representation. + pub fn set_prev_bandwidth_requests( + &mut self, + requests: &BlockBandwidthRequests, + params: &BandwidthSchedulerParams, + ) { + for (from_shard, shard_requests) in &requests.shards_bandwidth_requests { + Self::add_requests_to_map( + shard_requests, + &mut self.prev_bandwidth_requests, + *from_shard, + params, + ); + } + self.prev_bandwidth_requests_num = self.prev_bandwidth_requests.len().try_into().unwrap(); + } + + /// Set `new_bandwidth_requests`. Automatically converts to the target representation. + pub fn set_new_bandwidth_requests( + &mut self, + from_shard: ShardId, + requests: &BandwidthRequests, + params: &BandwidthSchedulerParams, + ) { + Self::add_requests_to_map(requests, &mut self.new_bandwidth_requests, from_shard, params); + } + + /// Convert bandwidth requests to the target representation and add to the given map. + fn add_requests_to_map( + requests: &BandwidthRequests, + map: &mut BTreeMap<(ShardId, ShardId), Vec>, + from_shard: ShardId, + params: &BandwidthSchedulerParams, + ) { + match requests { + BandwidthRequests::V1(requests_v1) => { + for request in &requests_v1.requests { + map.insert( + (from_shard, request.to_shard.into()), + get_requested_values(request, params), + ); + } + } + } + } +} + +#[derive(Debug, Clone, Default, BorshSerialize, BorshDeserialize)] +pub struct ReceiptSinkStats { + /// Outgoing size and gas limits to every shard. + pub outgoing_limits: BTreeMap, + /// New outgoing receipts generated during this chunk application. + pub forwarded_receipts: BTreeMap, + /// New buffered receipts that couldn't be forwarded because of the outgoing limits. + pub buffered_receipts: BTreeMap, + /// Final state of the outgoing buffers after all new receipts have been forwarded or buffered. + /// Used to generate new bandwidth requests. + pub final_outgoing_buffers: BTreeMap, + /// Whether the `ReceiptGroupsQueue` is fully initialized. Can only be false during the protocol + /// upgrade that enables bandwidth scheduler. + pub is_outgoing_metadata_ready: BTreeMap, + /// Whether `is_outgoing_metadata_ready` is true for all shards. This must be true before + /// resharding can start. + pub all_outgoing_metadatas_ready: bool, +} + +impl ReceiptSinkStats { + pub fn set_outgoing_limits(&mut self, limits: impl Iterator) { + for (shard_id, (size, gas)) in limits { + self.outgoing_limits.insert(shard_id, OutgoingLimitStats { size, gas }); + } + } +} + +#[derive(Debug, Clone, Default, BorshSerialize, BorshDeserialize)] +pub struct OutgoingLimitStats { + pub size: u64, + pub gas: Gas, +} + +/// Stats about a set of receipts +#[derive(Debug, Clone, Default, BorshSerialize, BorshDeserialize)] +pub struct ReceiptsStats { + /// Number of receipts + pub num: u64, + /// Total size of receipts, as calculated by `congestion_control::compute_receipt_size`. + pub total_size: u64, + /// Total gas of receipts, as calculated by `compute_receipt_congestion_gas`. + pub total_gas: u128, +} + +impl ReceiptsStats { + pub fn add_receipt(&mut self, size: u64, gas: Gas) { + self.num += 1; + self.total_size += size; + let gas_u128: u128 = gas.into(); + self.total_gas += gas_u128; + } +} + +/// Stats about token balance, used in balance checker. +#[derive(Debug, Clone, Default, BorshSerialize, BorshDeserialize)] +pub struct BalanceStats { + pub tx_burnt_amount: Balance, + pub slashed_burnt_amount: Balance, + pub other_burnt_amount: Balance, + /// This is a negative amount. This amount was not charged from the account that issued + /// the transaction. It's likely due to the delayed queue of the receipts. + pub gas_deficit_amount: Balance, +} + +/// Convert a bandwidth request from the bitmap representation to a list of requested values. +fn get_requested_values( + bandwidth_request: &BandwidthRequest, + params: &BandwidthSchedulerParams, +) -> Vec { + let values = BandwidthRequestValues::new(params); + let mut res = Vec::new(); + for i in 0..bandwidth_request.requested_values_bitmap.len() { + if bandwidth_request.requested_values_bitmap.get_bit(i) { + res.push(values.values[i]); + } + } + res +} diff --git a/core/primitives/src/lib.rs b/core/primitives/src/lib.rs index 2109759a947..32221064a5d 100644 --- a/core/primitives/src/lib.rs +++ b/core/primitives/src/lib.rs @@ -14,6 +14,7 @@ pub mod block; pub mod block_body; pub mod block_header; pub mod challenge; +pub mod chunk_apply_stats; pub mod congestion_info; pub mod epoch_block_info; pub mod epoch_info; diff --git a/core/store/src/adapter/chain_store.rs b/core/store/src/adapter/chain_store.rs index a5c25f40bc4..3e935721f7d 100644 --- a/core/store/src/adapter/chain_store.rs +++ b/core/store/src/adapter/chain_store.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use near_chain_primitives::Error; use near_primitives::block::{Block, BlockHeader, Tip}; +use near_primitives::chunk_apply_stats::ChunkApplyStats; use near_primitives::hash::CryptoHash; use near_primitives::merkle::PartialMerkleTree; use near_primitives::receipt::Receipt; @@ -252,6 +253,16 @@ impl ChainStoreAdapter { ) } + pub fn get_chunk_apply_stats( + &self, + block_hash: &CryptoHash, + shard_id: &ShardId, + ) -> Result, Error> { + self.store + .get_ser(DBCol::ChunkApplyStats, &get_block_shard_id(block_hash, *shard_id)) + .map_err(|e| e.into()) + } + pub fn get_outgoing_receipts( &self, prev_block_hash: &CryptoHash, diff --git a/core/store/src/columns.rs b/core/store/src/columns.rs index bf0d7036514..b05c781582c 100644 --- a/core/store/src/columns.rs +++ b/core/store/src/columns.rs @@ -317,6 +317,11 @@ pub enum DBCol { /// - *Rows*: `CryptoHash` /// - *Column type*: `Vec` StateSyncNewChunks, + /// Stores chunk application stats for every applied chunk. + /// The stats can be read to analyze what happened during chunk application. + /// - *Rows*: BlockShardId (BlockHash || ShardId) - 40 bytes + /// - *Column type*: `ChunkApplyStats` + ChunkApplyStats, } /// Defines different logical parts of a db key. @@ -462,7 +467,8 @@ impl DBCol { | DBCol::StateHeaders | DBCol::TransactionResultForBlock | DBCol::Transactions - | DBCol::StateShardUIdMapping => true, + | DBCol::StateShardUIdMapping + | DBCol::ChunkApplyStats => true, // TODO DBCol::ChallengedBlocks => false, @@ -598,6 +604,7 @@ impl DBCol { DBCol::StateShardUIdMapping => &[DBKeyType::ShardUId], DBCol::StateSyncHashes => &[DBKeyType::EpochId], DBCol::StateSyncNewChunks => &[DBKeyType::BlockHash], + DBCol::ChunkApplyStats => &[DBKeyType::BlockHash, DBKeyType::ShardId], } } } diff --git a/core/store/src/metadata.rs b/core/store/src/metadata.rs index 9cf60ce64f4..a71825310a5 100644 --- a/core/store/src/metadata.rs +++ b/core/store/src/metadata.rs @@ -2,7 +2,7 @@ pub type DbVersion = u32; /// Current version of the database. -pub const DB_VERSION: DbVersion = 43; +pub const DB_VERSION: DbVersion = 44; /// Database version at which point DbKind was introduced. const DB_VERSION_WITH_KIND: DbVersion = 34; diff --git a/cspell.json b/cspell.json index e525b3de9a2..6c12d579a4f 100644 --- a/cspell.json +++ b/cspell.json @@ -321,7 +321,14 @@ "yocto", "yoctonear", "zstd", - "Zulip" + "Zulip", + "deser", + "deprioritized", + "deprioritize", + "nocapture", + "defence", + "syscalls", + "contractregistry", ], "ignoreWords": [], "import": [] diff --git a/nearcore/src/migrations.rs b/nearcore/src/migrations.rs index 677397809e0..797c553f346 100644 --- a/nearcore/src/migrations.rs +++ b/nearcore/src/migrations.rs @@ -93,6 +93,7 @@ impl<'a> near_store::StoreMigrator for Migrator<'a> { 40 => near_store::migrations::migrate_40_to_41(store), 41 => near_store::migrations::migrate_41_to_42(store), 42 => near_store::migrations::migrate_42_to_43(store), + 43 => Ok(()), // DBCol::ChunkApplyStats column added, no need to perform a migration DB_VERSION.. => unreachable!(), } } diff --git a/runtime/runtime-params-estimator/src/estimator_context.rs b/runtime/runtime-params-estimator/src/estimator_context.rs index ec774f85a52..8ede652bf2d 100644 --- a/runtime/runtime-params-estimator/src/estimator_context.rs +++ b/runtime/runtime-params-estimator/src/estimator_context.rs @@ -8,6 +8,7 @@ use near_parameters::config::CongestionControlConfig; use near_parameters::{ExtCosts, RuntimeConfigStore}; use near_primitives::apply::ApplyChunkReason; use near_primitives::bandwidth_scheduler::BlockBandwidthRequests; +use near_primitives::chunk_apply_stats::ChunkApplyStatsV0; use near_primitives::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; use near_primitives::hash::CryptoHash; use near_primitives::receipt::Receipt; @@ -480,7 +481,8 @@ impl Testbed<'_> { let mut state_update = TrieUpdate::new(self.trie()); let mut outgoing_receipts = vec![]; let mut validator_proposals = vec![]; - let mut stats = node_runtime::ApplyStats::default(); + let mut stats = + ChunkApplyStatsV0::new(self.apply_state.block_height, self.apply_state.shard_id); // TODO: mock is not accurate, potential DB requests are skipped in the mock! let epoch_info_provider = MockEpochInfoProvider::default(); let clock = GasCost::measure(metric); diff --git a/runtime/runtime/src/balance_checker.rs b/runtime/runtime/src/balance_checker.rs index aeec8117a1a..a82c3981528 100644 --- a/runtime/runtime/src/balance_checker.rs +++ b/runtime/runtime/src/balance_checker.rs @@ -3,8 +3,9 @@ use crate::config::{ total_prepaid_gas, total_prepaid_send_fees, }; use crate::{safe_add_balance_apply, SignedValidPeriodTransactions}; -use crate::{ApplyStats, DelayedReceiptIndices, ValidatorAccountsUpdate}; +use crate::{DelayedReceiptIndices, ValidatorAccountsUpdate}; use near_parameters::{ActionCosts, RuntimeConfig}; +use near_primitives::chunk_apply_stats::BalanceStats; use near_primitives::errors::{ BalanceMismatchError, IntegerOverflowError, RuntimeError, StorageError, }; @@ -278,7 +279,7 @@ pub(crate) fn check_balance( yield_timeout_receipts: &[Receipt], transactions: SignedValidPeriodTransactions<'_>, outgoing_receipts: &[Receipt], - stats: &ApplyStats, + stats: &BalanceStats, ) -> Result<(), RuntimeError> { let initial_state = final_state.trie(); @@ -387,7 +388,6 @@ pub(crate) fn check_balance( #[cfg(test)] mod tests { use super::*; - use crate::ApplyStats; use near_crypto::InMemorySigner; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::receipt::{ @@ -423,7 +423,7 @@ mod tests { &[], SignedValidPeriodTransactions::empty(), &[], - &ApplyStats::default(), + &BalanceStats::default(), ) .unwrap(); } @@ -442,7 +442,7 @@ mod tests { &[], SignedValidPeriodTransactions::empty(), &[], - &ApplyStats::default(), + &BalanceStats::default(), ) .unwrap_err(); assert_matches!(err, RuntimeError::BalanceMismatchError(_)); @@ -507,7 +507,7 @@ mod tests { &[], SignedValidPeriodTransactions::empty(), &[], - &ApplyStats::default(), + &BalanceStats::default(), ) .unwrap(); } @@ -556,7 +556,7 @@ mod tests { &[], SignedValidPeriodTransactions::new(&[tx], &[true]), &[receipt], - &ApplyStats { + &BalanceStats { tx_burnt_amount: total_validator_reward, gas_deficit_amount: 0, other_burnt_amount: 0, @@ -629,7 +629,7 @@ mod tests { &[], SignedValidPeriodTransactions::new(&[tx], &[true]), &[], - &ApplyStats::default(), + &BalanceStats::default(), ), Err(RuntimeError::UnexpectedIntegerOverflow(_)) ); @@ -672,7 +672,7 @@ mod tests { &[], SignedValidPeriodTransactions::new(&[tx], &[true]), &[], - &ApplyStats::default(), + &BalanceStats::default(), ), Err(RuntimeError::BalanceMismatchError { .. }) ); @@ -752,7 +752,7 @@ mod tests { &[], SignedValidPeriodTransactions::new(&[tx], &[true]), &[], - &ApplyStats { + &BalanceStats { // send gas was burnt on this shard, exec gas is part of the receipt value tx_burnt_amount: send_gas as Balance * gas_price, gas_deficit_amount: 0, @@ -823,7 +823,7 @@ mod tests { &[], SignedValidPeriodTransactions::empty(), &outgoing_receipts, - &ApplyStats::default(), + &BalanceStats::default(), ) .unwrap(); } @@ -887,7 +887,7 @@ mod tests { &[], SignedValidPeriodTransactions::empty(), &outgoing_receipts, - &ApplyStats::default(), + &BalanceStats::default(), ); assert_matches!(result, Err(RuntimeError::BalanceMismatchError { .. })); } diff --git a/runtime/runtime/src/bandwidth_scheduler/mod.rs b/runtime/runtime/src/bandwidth_scheduler/mod.rs index f4d3c90725e..eb957fe9b10 100644 --- a/runtime/runtime/src/bandwidth_scheduler/mod.rs +++ b/runtime/runtime/src/bandwidth_scheduler/mod.rs @@ -4,6 +4,7 @@ use std::num::NonZeroU64; use near_primitives::bandwidth_scheduler::{ BandwidthSchedulerParams, BandwidthSchedulerState, BandwidthSchedulerStateV1, }; +use near_primitives::chunk_apply_stats::BandwidthSchedulerStats; use near_primitives::congestion_info::CongestionControl; use near_primitives::errors::RuntimeError; use near_primitives::hash::{hash, CryptoHash}; @@ -35,11 +36,13 @@ pub fn run_bandwidth_scheduler( apply_state: &ApplyState, state_update: &mut TrieUpdate, epoch_info_provider: &dyn EpochInfoProvider, + stats: &mut BandwidthSchedulerStats, ) -> Result, RuntimeError> { if !ProtocolFeature::BandwidthScheduler.enabled(apply_state.current_protocol_version) { return Ok(None); } + let start_time = std::time::Instant::now(); let _span = tracing::debug_span!( target: "runtime", "run_bandwidth_scheduler", @@ -93,6 +96,10 @@ pub fn run_bandwidth_scheduler( &apply_state.config, ); + // Record stats + stats.params = Some(params); + stats.set_prev_bandwidth_requests(&apply_state.bandwidth_requests, ¶ms); + // Run the bandwidth scheduler algorithm. let granted_bandwidth = BandwidthScheduler::run( shard_layout, @@ -103,6 +110,8 @@ pub fn run_bandwidth_scheduler( apply_state.prev_block_hash.0, ); + stats.granted_bandwidth = granted_bandwidth.granted.clone(); + // Hash (some of) the inputs to the scheduler algorithm and save the checksum in the state. // This is a sanity check to make sure that all shards run the scheduler with the same inputs. // It would be a bit nicer to hash all inputs, but that could be slow and the serialization @@ -121,5 +130,7 @@ pub fn run_bandwidth_scheduler( state_update.commit(StateChangeCause::BandwidthSchedulerStateUpdate); let scheduler_state_hash: CryptoHash = hash(&borsh::to_vec(&scheduler_state).unwrap()); + + stats.time_to_run_ms = start_time.elapsed().as_millis(); Ok(Some(BandwidthSchedulerOutput { granted_bandwidth, params, scheduler_state_hash })) } diff --git a/runtime/runtime/src/bandwidth_scheduler/scheduler.rs b/runtime/runtime/src/bandwidth_scheduler/scheduler.rs index 2b2669bf92b..e07ae4b624b 100644 --- a/runtime/runtime/src/bandwidth_scheduler/scheduler.rs +++ b/runtime/runtime/src/bandwidth_scheduler/scheduler.rs @@ -156,7 +156,7 @@ use rand_chacha::ChaCha20Rng; /// How many bytes of outgoing receipts can be sent from one shard to another at the current height. /// Produced by the bandwidth scheduler. pub struct GrantedBandwidth { - granted: BTreeMap<(ShardId, ShardId), Bandwidth>, + pub granted: BTreeMap<(ShardId, ShardId), Bandwidth>, } impl GrantedBandwidth { diff --git a/runtime/runtime/src/congestion_control.rs b/runtime/runtime/src/congestion_control.rs index fa15c92513f..bad59e4288d 100644 --- a/runtime/runtime/src/congestion_control.rs +++ b/runtime/runtime/src/congestion_control.rs @@ -9,6 +9,7 @@ use near_parameters::{ActionCosts, RuntimeConfig}; use near_primitives::bandwidth_scheduler::{ BandwidthRequest, BandwidthRequests, BandwidthRequestsV1, BandwidthSchedulerParams, }; +use near_primitives::chunk_apply_stats::{ChunkApplyStatsV0, ReceiptSinkStats, ReceiptsStats}; use near_primitives::congestion_info::{CongestionControl, CongestionInfo, CongestionInfoV1}; use near_primitives::errors::{EpochError, IntegerOverflowError, RuntimeError}; use near_primitives::receipt::{ @@ -59,6 +60,7 @@ pub(crate) struct ReceiptSinkV2 { pub(crate) outgoing_metadatas: OutgoingMetadatas, pub(crate) bandwidth_scheduler_output: Option, pub(crate) protocol_version: ProtocolVersion, + pub(crate) stats: ReceiptSinkStats, } /// Limits for outgoing receipts to a shard. @@ -126,6 +128,11 @@ impl ReceiptSink { apply_state.current_protocol_version, )?; + let mut stats = ReceiptSinkStats::default(); + stats.set_outgoing_limits( + outgoing_limit.iter().map(|(shard_id, limit)| (*shard_id, (limit.size, limit.gas))), + ); + Ok(ReceiptSink::V2(ReceiptSinkV2 { own_congestion_info, outgoing_receipts: Vec::new(), @@ -134,6 +141,7 @@ impl ReceiptSink { outgoing_metadatas, bandwidth_scheduler_output, protocol_version, + stats, })) } else { debug_assert!(!ProtocolFeature::CongestionControl.enabled(protocol_version)); @@ -188,10 +196,19 @@ impl ReceiptSink { } } - pub(crate) fn into_outgoing_receipts(self) -> Vec { + /// Consumes receipt sink, finalizes ReceiptSinkStats and returns the outgoing receipts. + /// Called at the end of chunk application. + pub(crate) fn finalize_stats_get_outgoing_receipts( + self, + stats: &mut ReceiptSinkStats, + ) -> Vec { match self { ReceiptSink::V1(inner) => inner.outgoing_receipts, - ReceiptSink::V2(inner) => inner.outgoing_receipts, + ReceiptSink::V2(mut inner) => { + inner.record_outgoing_buffer_stats(); + *stats = inner.stats; + inner.outgoing_receipts + } } } @@ -215,11 +232,12 @@ impl ReceiptSink { trie: &dyn TrieAccess, shard_layout: &ShardLayout, side_effects: bool, + stats: &mut ChunkApplyStatsV0, ) -> Result, StorageError> { match self { ReceiptSink::V1(_) => Ok(None), ReceiptSink::V2(inner) => { - inner.generate_bandwidth_requests(trie, shard_layout, side_effects) + inner.generate_bandwidth_requests(trie, shard_layout, side_effects, stats) } } } @@ -323,6 +341,7 @@ impl ReceiptSinkV2 { &mut self.outgoing_limit, &mut self.outgoing_receipts, apply_state, + &mut self.stats, )? { ReceiptForwarding::Forwarded => { self.own_congestion_info.remove_receipt_bytes(size)?; @@ -379,6 +398,7 @@ impl ReceiptSinkV2 { &mut self.outgoing_limit, &mut self.outgoing_receipts, apply_state, + &mut self.stats, )? { ReceiptForwarding::Forwarded => (), ReceiptForwarding::NotForwarded(receipt) => { @@ -409,6 +429,7 @@ impl ReceiptSinkV2 { outgoing_limit: &mut HashMap, outgoing_receipts: &mut Vec, apply_state: &ApplyState, + stats: &mut ReceiptSinkStats, ) -> Result { // There is a bug which allows to create receipts that are above the size limit. Receipts // above the size limit might not fit under the maximum outgoing size limit. Let's pretend @@ -459,6 +480,7 @@ impl ReceiptSinkV2 { // underflow impossible: checked forward_limit > gas/size_to_forward above forward_limit.gas -= gas; forward_limit.size -= size; + stats.forwarded_receipts.entry(shard).or_default().add_receipt(size, gas); Ok(ReceiptForwarding::Forwarded) } else { @@ -502,6 +524,7 @@ impl ReceiptSinkV2 { } self.outgoing_buffers.to_shard(shard).push_back(state_update, &receipt)?; + self.stats.buffered_receipts.entry(shard).or_default().add_receipt(size, gas); Ok(()) } @@ -510,6 +533,7 @@ impl ReceiptSinkV2 { trie: &dyn TrieAccess, shard_layout: &ShardLayout, side_effects: bool, + stats: &mut ChunkApplyStatsV0, ) -> Result, StorageError> { if !ProtocolFeature::BandwidthScheduler.enabled(self.protocol_version) { return Ok(None); @@ -534,7 +558,9 @@ impl ReceiptSinkV2 { } } - Ok(Some(BandwidthRequests::V1(BandwidthRequestsV1 { requests }))) + let bandwidth_requests = BandwidthRequests::V1(BandwidthRequestsV1 { requests }); + stats.set_new_bandwidth_requests(&bandwidth_requests, ¶ms); + Ok(Some(bandwidth_requests)) } fn generate_bandwidth_request( @@ -619,6 +645,50 @@ impl ReceiptSinkV2 { } } } + + /// Record information about the outgoing buffer in ReceiptSinkStats. + fn record_outgoing_buffer_stats(&mut self) { + for shard in self.outgoing_buffers.shards() { + let buffer = self.outgoing_buffers.to_shard(shard); + + if buffer.len() == 0 { + self.stats + .final_outgoing_buffers + .insert(shard, ReceiptsStats { num: 0, total_size: 0, total_gas: 0 }); + self.stats.is_outgoing_metadata_ready.insert(shard, true); + continue; + } + + // If the outgoing buffer metadata is fully initialized, record the total size and gas + // of the receipts in the buffer. Otherwise, record the number of receipts in the buffer + // and set the total size and gas to 0. See + // `get_receipt_group_sizes_for_buffer_to_shard` for more information about the metadata + // initialization. + match self.outgoing_metadatas.get_metadata_for_shard(&shard) { + Some(metadata) if metadata.total_receipts_num() == buffer.len() => { + self.stats.final_outgoing_buffers.insert( + shard, + ReceiptsStats { + num: buffer.len(), + total_size: metadata.total_size(), + total_gas: metadata.total_gas(), + }, + ); + self.stats.is_outgoing_metadata_ready.insert(shard, true); + } + _ => { + self.stats.final_outgoing_buffers.insert( + shard, + ReceiptsStats { num: buffer.len(), total_size: 0, total_gas: 0 }, + ); + self.stats.is_outgoing_metadata_ready.insert(shard, false); + } + } + } + + self.stats.all_outgoing_metadatas_ready = + self.stats.is_outgoing_metadata_ready.values().all(|&ready| ready); + } } /// Get the receipt gas from the receipt that was retrieved from the state. diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index debb8bc8e29..9d33f39f774 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -22,6 +22,7 @@ pub use near_primitives; use near_primitives::account::Account; use near_primitives::bandwidth_scheduler::{BandwidthRequests, BlockBandwidthRequests}; use near_primitives::checked_feature; +use near_primitives::chunk_apply_stats::{BalanceStats, ChunkApplyStatsV0}; use near_primitives::congestion_info::{BlockCongestionInfo, CongestionInfo}; use near_primitives::errors::{ ActionError, ActionErrorKind, EpochError, IntegerOverflowError, InvalidTxError, RuntimeError, @@ -174,16 +175,6 @@ pub struct VerificationResult { pub burnt_amount: Balance, } -#[derive(Debug, Default)] -pub struct ApplyStats { - pub tx_burnt_amount: Balance, - pub slashed_burnt_amount: Balance, - pub other_burnt_amount: Balance, - /// This is a negative amount. This amount was not charged from the account that issued - /// the transaction. It's likely due to the delayed queue of the receipts. - pub gas_deficit_amount: Balance, -} - #[derive(Debug)] pub struct ApplyResult { pub state_root: StateRoot, @@ -192,7 +183,7 @@ pub struct ApplyResult { pub outgoing_receipts: Vec, pub outcomes: Vec, pub state_changes: Vec, - pub stats: ApplyStats, + pub stats: ChunkApplyStatsV0, pub processed_delayed_receipts: Vec, pub processed_yield_timeouts: Vec, pub proof: Option, @@ -336,7 +327,7 @@ impl Runtime { apply_state: &ApplyState, signed_transaction: &SignedTransaction, transaction_cost: &TransactionCost, - stats: &mut ApplyStats, + stats: &mut ChunkApplyStatsV0, ) -> Result<(Receipt, ExecutionOutcomeWithId), InvalidTxError> { let span = tracing::Span::current(); metrics::TRANSACTION_PROCESSED_TOTAL.inc(); @@ -375,9 +366,11 @@ impl Runtime { actions: transaction.actions().to_vec(), }), }); - stats.tx_burnt_amount = - safe_add_balance(stats.tx_burnt_amount, verification_result.burnt_amount) - .map_err(|_| InvalidTxError::CostOverflow)?; + stats.balance.tx_burnt_amount = safe_add_balance( + stats.balance.tx_burnt_amount, + verification_result.burnt_amount, + ) + .map_err(|_| InvalidTxError::CostOverflow)?; let gas_burnt = verification_result.gas_burnt; let compute_usage = verification_result.gas_burnt; let outcome = ExecutionOutcomeWithId { @@ -616,7 +609,7 @@ impl Runtime { receipt: &Receipt, receipt_sink: &mut ReceiptSink, validator_proposals: &mut Vec, - stats: &mut ApplyStats, + stats: &mut ChunkApplyStatsV0, epoch_info_provider: &dyn EpochInfoProvider, ) -> Result { let _span = tracing::debug_span!( @@ -771,8 +764,8 @@ impl Runtime { // If the refund fails tokens are burned. if result.result.is_err() { - stats.other_burnt_amount = safe_add_balance( - stats.other_burnt_amount, + stats.balance.other_burnt_amount = safe_add_balance( + stats.balance.other_burnt_amount, total_deposit(&action_receipt.actions)?, )? } @@ -787,7 +780,8 @@ impl Runtime { &apply_state.config, )? }; - stats.gas_deficit_amount = safe_add_balance(stats.gas_deficit_amount, gas_deficit_amount)?; + stats.balance.gas_deficit_amount = + safe_add_balance(stats.balance.gas_deficit_amount, gas_deficit_amount)?; // Moving validator proposals validator_proposals.append(&mut result.validator_proposals); @@ -843,7 +837,8 @@ impl Runtime { } } - stats.tx_burnt_amount = safe_add_balance(stats.tx_burnt_amount, tx_burnt_amount)?; + stats.balance.tx_burnt_amount = + safe_add_balance(stats.balance.tx_burnt_amount, tx_burnt_amount)?; // Generating outgoing data // A { @@ -1246,7 +1241,7 @@ impl Runtime { &self, state_update: &mut TrieUpdate, validator_accounts_update: &ValidatorAccountsUpdate, - stats: &mut ApplyStats, + stats: &mut BalanceStats, ) -> Result<(), RuntimeError> { for (account_id, max_of_stakes) in &validator_accounts_update.stake_info { if let Some(mut account) = get_account(state_update, account_id)? { @@ -1470,6 +1465,10 @@ impl Runtime { let mut processing_state = ApplyProcessingState::new(&apply_state, trie, epoch_info_provider, transactions); + processing_state.stats.transactions_num = + transactions.transactions.len().try_into().unwrap(); + processing_state.stats.incoming_receipts_num = incoming_receipts.len().try_into().unwrap(); + processing_state.stats.is_new_chunk = !apply_state.is_new_chunk; if let Some(prefetcher) = &mut processing_state.prefetcher { // Prefetcher is allowed to fail @@ -1481,7 +1480,7 @@ impl Runtime { self.update_validator_accounts( &mut processing_state.state_update, validator_accounts_update, - &mut processing_state.stats, + &mut processing_state.stats.balance, )?; } @@ -1508,6 +1507,7 @@ impl Runtime { apply_state, &mut processing_state.state_update, epoch_info_provider, + &mut processing_state.stats.bandwidth_scheduler, )?; // If the chunk is missing, exit early and don't process any receipts. @@ -2114,6 +2114,7 @@ impl Runtime { let _span = tracing::debug_span!(target: "runtime", "apply_commit").entered(); let apply_state = processing_state.apply_state; let epoch_info_provider = processing_state.epoch_info_provider; + let mut stats = processing_state.stats; let mut state_update = processing_state.state_update; let pending_delayed_receipts = processing_state.delayed_receipts; let processed_delayed_receipts = process_receipts_result.processed_delayed_receipts; @@ -2160,8 +2161,12 @@ impl Runtime { ); } - let bandwidth_requests = - receipt_sink.generate_bandwidth_requests(&state_update, &shard_layout, true)?; + let bandwidth_requests = receipt_sink.generate_bandwidth_requests( + &state_update, + &shard_layout, + true, + &mut stats, + )?; if cfg!(debug_assertions) { if let Err(err) = check_balance( @@ -2173,7 +2178,7 @@ impl Runtime { &promise_yield_result.timeout_receipts, processing_state.transactions, &receipt_sink.outgoing_receipts(), - &processing_state.stats, + &stats.balance, ) { panic!( "The runtime's balance_checker failed for shard {} at height {} with block hash {} and protocol version {}: {}", @@ -2239,14 +2244,16 @@ impl Runtime { .bandwidth_scheduler_output() .map(|o| o.scheduler_state_hash) .unwrap_or_default(); + let outgoing_receipts = + receipt_sink.finalize_stats_get_outgoing_receipts(&mut stats.receipt_sink); Ok(ApplyResult { state_root, trie_changes, validator_proposals: unique_proposals, - outgoing_receipts: receipt_sink.into_outgoing_receipts(), + outgoing_receipts, outcomes: processing_state.outcomes, state_changes, - stats: processing_state.stats, + stats, processed_delayed_receipts, processed_yield_timeouts, proof, @@ -2529,7 +2536,7 @@ struct ApplyProcessingState<'a> { epoch_info_provider: &'a dyn EpochInfoProvider, transactions: SignedValidPeriodTransactions<'a>, total: TotalResourceGuard, - stats: ApplyStats, + stats: ChunkApplyStatsV0, } impl<'a> ApplyProcessingState<'a> { @@ -2550,7 +2557,7 @@ impl<'a> ApplyProcessingState<'a> { gas: 0, compute: 0, }; - let stats = ApplyStats::default(); + let stats = ChunkApplyStatsV0::new(apply_state.block_height, apply_state.shard_id); Self { protocol_version, apply_state, @@ -2640,7 +2647,7 @@ struct ApplyProcessingReceiptState<'a> { epoch_info_provider: &'a dyn EpochInfoProvider, transactions: SignedValidPeriodTransactions<'a>, total: TotalResourceGuard, - stats: ApplyStats, + stats: ChunkApplyStatsV0, outcomes: Vec, metrics: ApplyMetrics, local_receipts: VecDeque, @@ -2767,7 +2774,8 @@ pub mod estimator { use super::{ReceiptSink, Runtime}; use crate::congestion_control::ReceiptSinkV2; use crate::pipelining::ReceiptPreparationPipeline; - use crate::{ApplyState, ApplyStats}; + use crate::ApplyState; + use near_primitives::chunk_apply_stats::{ChunkApplyStatsV0, ReceiptSinkStats}; use near_primitives::congestion_info::CongestionInfo; use near_primitives::errors::RuntimeError; use near_primitives::receipt::Receipt; @@ -2785,12 +2793,13 @@ pub mod estimator { receipt: &Receipt, outgoing_receipts: &mut Vec, validator_proposals: &mut Vec, - stats: &mut ApplyStats, + stats: &mut ChunkApplyStatsV0, epoch_info_provider: &dyn EpochInfoProvider, ) -> Result { // TODO(congestion_control - edit runtime config parameters for limitless estimator runs let congestion_info = CongestionInfo::default(); // no limits set for any shards => limitless + // TODO(bandwidth_scheduler) - now empty map means all limits are zero, fix. let outgoing_limit = HashMap::new(); // ShardId used in EstimatorContext::testbed @@ -2810,6 +2819,7 @@ pub mod estimator { outgoing_metadatas, bandwidth_scheduler_output: None, protocol_version: apply_state.current_protocol_version, + stats: ReceiptSinkStats::default(), }); let empty_pipeline = ReceiptPreparationPipeline::new( std::sync::Arc::clone(&apply_state.config), @@ -2827,7 +2837,9 @@ pub mod estimator { stats, epoch_info_provider, ); - outgoing_receipts.extend(receipt_sink.into_outgoing_receipts().into_iter()); + let new_outgoing_receipts = + receipt_sink.finalize_stats_get_outgoing_receipts(&mut stats.receipt_sink); + outgoing_receipts.extend(new_outgoing_receipts.into_iter()); apply_result } } diff --git a/runtime/runtime/src/tests/apply.rs b/runtime/runtime/src/tests/apply.rs index a4f2bb2200a..cbb301b8f0e 100644 --- a/runtime/runtime/src/tests/apply.rs +++ b/runtime/runtime/src/tests/apply.rs @@ -737,7 +737,7 @@ fn test_apply_deficit_gas_for_transfer() { Default::default(), ) .unwrap(); - assert_eq!(result.stats.gas_deficit_amount, result.stats.tx_burnt_amount * 9) + assert_eq!(result.stats.balance.gas_deficit_amount, result.stats.balance.tx_burnt_amount * 9) } #[test] @@ -795,7 +795,7 @@ fn test_apply_deficit_gas_for_function_call_covered() { ) .unwrap(); // We used part of the prepaid gas to paying extra fees. - assert_eq!(result.stats.gas_deficit_amount, 0); + assert_eq!(result.stats.balance.gas_deficit_amount, 0); // The refund is less than the received amount. match result.outgoing_receipts[0].receipt() { ReceiptEnum::Action(ActionReceipt { actions, .. }) => { @@ -862,9 +862,9 @@ fn test_apply_deficit_gas_for_function_call_partial() { ) .unwrap(); // Used full prepaid gas, but it still not enough to cover deficit. - assert_eq!(result.stats.gas_deficit_amount, expected_deficit); + assert_eq!(result.stats.balance.gas_deficit_amount, expected_deficit); // Burnt all the fees + all prepaid gas. - assert_eq!(result.stats.tx_burnt_amount, total_receipt_cost); + assert_eq!(result.stats.balance.tx_burnt_amount, total_receipt_cost); } #[test] diff --git a/tools/state-viewer/src/cli.rs b/tools/state-viewer/src/cli.rs index d1086cef35b..0716fdb0e2c 100644 --- a/tools/state-viewer/src/cli.rs +++ b/tools/state-viewer/src/cli.rs @@ -52,6 +52,8 @@ pub enum StateViewerSubCommand { CheckBlock, /// Looks up a certain chunk. Chunks(ChunksCmd), + /// View chunk application stats for a chunk. + ChunkApplyStats(ChunkApplyStatsCmd), /// Clear recoverable data in CachedContractCode column. #[clap(alias = "clear_cache")] ClearCache, @@ -168,6 +170,7 @@ impl StateViewerSubCommand { StateViewerSubCommand::Chain(cmd) => cmd.run(home_dir, near_config, store), StateViewerSubCommand::CheckBlock => check_block_chunk_existence(near_config, store), StateViewerSubCommand::Chunks(cmd) => cmd.run(near_config, store), + StateViewerSubCommand::ChunkApplyStats(cmd) => cmd.run(near_config, store), StateViewerSubCommand::ClearCache => clear_cache(store), StateViewerSubCommand::ContractAccounts(cmd) => cmd.run(home_dir, near_config, store), StateViewerSubCommand::DebugUI(cmd) => { @@ -418,6 +421,20 @@ impl ChunksCmd { } } +#[derive(clap::Parser)] +pub struct ChunkApplyStatsCmd { + #[clap(long)] + block_hash: CryptoHash, + #[clap(long)] + shard_id: u64, +} + +impl ChunkApplyStatsCmd { + pub fn run(self, near_config: NearConfig, store: Store) { + print_chunk_apply_stats(&self.block_hash, self.shard_id, near_config, store); + } +} + #[derive(clap::Parser)] pub struct ContractAccountsCmd { #[clap(flatten)] diff --git a/tools/state-viewer/src/commands.rs b/tools/state-viewer/src/commands.rs index d5d169c870a..99310ce8622 100644 --- a/tools/state-viewer/src/commands.rs +++ b/tools/state-viewer/src/commands.rs @@ -553,6 +553,27 @@ pub(crate) fn get_chunk(chunk_hash: ChunkHash, near_config: NearConfig, store: S println!("Chunk: {:#?}", chunk); } +pub(crate) fn print_chunk_apply_stats( + block_hash: &CryptoHash, + shard_id: u64, + near_config: NearConfig, + store: Store, +) { + let chain_store = ChainStore::new( + store, + near_config.genesis.config.genesis_height, + near_config.client_config.save_trie_changes, + near_config.genesis.config.transaction_validity_period, + ); + match chain_store.get_chunk_apply_stats(block_hash, &ShardId::new(shard_id)) { + Ok(Some(stats)) => println!("{:#?}", stats), + Ok(None) => { + println!("\nNo stats found for block hash {} and shard {}\n", block_hash, shard_id) + } + Err(e) => eprintln!("Error: {:#?}", e), + } +} + pub(crate) fn get_partial_chunk( partial_chunk_hash: ChunkHash, near_config: NearConfig,