diff --git a/Cargo.toml b/Cargo.toml index 12408986a..07396f5ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ path = "src/bin/run_with_importer.rs" # ------------------------------------------------------------------------------ [features] -default = ["metrics", "rocks"] +default = ["metrics"] # Application is running in develoment mode. dev = [] diff --git a/build.rs b/build.rs index 1395e4559..cd4d27917 100644 --- a/build.rs +++ b/build.rs @@ -38,8 +38,9 @@ fn generate_signature_maps() { let mut signatures_4_bytes = phf_codegen::Map::<[u8; 4]>::new(); let mut signatures_32_bytes = phf_codegen::Map::<[u8; 32]>::new(); for signature_file in signature_files { - let signatures_content = fs::read_to_string(signature_file).expect("Reading signature file shoult not fail"); - populate_signature_maps(&signatures_content, &mut seen, &mut signatures_4_bytes, &mut signatures_32_bytes); + let prefix = signature_file.file_name().unwrap().to_str().unwrap().split('.').next().unwrap(); + let signatures_content = fs::read_to_string(&signature_file).expect("Reading signature file shoult not fail"); + populate_signature_maps(&signatures_content, &mut seen, &mut signatures_4_bytes, &mut signatures_32_bytes, prefix); } // write signatures.rs file @@ -63,6 +64,7 @@ fn populate_signature_maps( seen: &mut HashSet>, signatures_4_bytes: &mut phf_codegen::Map<[u8; 4]>, signatures_32_bytes: &mut phf_codegen::Map<[u8; 32]>, + prefix: &str, ) { for line in input.lines() { if let Ok((_, (id, signature))) = parse_signature(line) { @@ -71,7 +73,7 @@ fn populate_signature_maps( } seen.insert(id.clone()); - let signature = format!("\"{}\"", signature); + let signature = format!("\"{}::{}\"", prefix, signature); match id.len() { 4 => { signatures_4_bytes.entry(id.try_into().unwrap(), &signature); diff --git a/src/eth/evm/evm.rs b/src/eth/evm/evm.rs index 0b8f2c274..708a92fdc 100644 --- a/src/eth/evm/evm.rs +++ b/src/eth/evm/evm.rs @@ -6,6 +6,8 @@ //! facilitates flexible EVM integrations, enabling the project to adapt to different blockchain environments //! or requirements while maintaining a consistent execution interface. +use std::borrow::Cow; + use display_json::DebugAsJson; use itertools::Itertools; @@ -21,10 +23,13 @@ use crate::eth::primitives::ExternalReceipt; use crate::eth::primitives::ExternalTransaction; use crate::eth::primitives::Gas; use crate::eth::primitives::Nonce; +use crate::eth::primitives::Signature; +use crate::eth::primitives::SoliditySignature; use crate::eth::primitives::StoragePointInTime; use crate::eth::primitives::TransactionInput; use crate::eth::primitives::UnixTime; use crate::eth::primitives::Wei; +use crate::ext::not; use crate::ext::OptionExt; use crate::if_else; @@ -126,6 +131,19 @@ pub struct EvmInput { } impl EvmInput { + fn is_contract_deployment(&self) -> bool { + self.to.is_none() && not(self.data.is_empty()) + } + + pub fn extract_function(&self) -> Option { + if self.is_contract_deployment() { + return Some(Cow::from("contract_deployment")); + } + let sig = Signature::Function(self.data.get(..4)?.try_into().ok()?); + + Some(sig.extract()) + } + /// Creates from a transaction that was sent directly to Stratus with `eth_sendRawTransaction`. pub fn from_eth_transaction(input: TransactionInput) -> Self { Self { diff --git a/src/eth/executor.rs b/src/eth/executor.rs index e81188c64..eaccf5555 100644 --- a/src/eth/executor.rs +++ b/src/eth/executor.rs @@ -188,6 +188,10 @@ impl Executor { Ok(evm_input) => evm_input, Err(e) => return Err((tx, receipt, e)), }; + + #[cfg(feature = "metrics")] + let function = evm_input.extract_function(); + let evm_result = self.execute_in_evm(evm_input).await; // handle reexecution result @@ -197,6 +201,13 @@ impl Executor { if let Err(e) = evm_result.execution.apply_execution_costs(receipt) { return Err((tx, receipt, e)); }; + + #[cfg(feature = "metrics")] + { + let gas_used = evm_result.execution.gas; + metrics::inc_executor_external_transaction_gas(gas_used.as_u64() as usize, function.clone()); + } + evm_result.execution.gas = match receipt.gas_used.unwrap_or_default().try_into() { Ok(gas) => gas, Err(e) => return Err((tx, receipt, e)), @@ -213,7 +224,7 @@ impl Executor { // track metrics #[cfg(feature = "metrics")] - metrics::inc_executor_external_transaction(start.elapsed()); + metrics::inc_executor_external_transaction(start.elapsed(), function); Ok(ExternalTransactionExecution::new(tx.clone(), receipt.clone(), evm_result)) } @@ -235,6 +246,8 @@ impl Executor { pub async fn transact(&self, tx_input: TransactionInput) -> anyhow::Result { #[cfg(feature = "metrics")] let start = metrics::now(); + #[cfg(feature = "metrics")] + let function = tx_input.extract_function(); tracing::info!( hash = %tx_input.hash, @@ -271,7 +284,7 @@ impl Executor { continue; } else { #[cfg(feature = "metrics")] - metrics::inc_executor_transact(start.elapsed(), false); + metrics::inc_executor_transact(start.elapsed(), false, function); return Err(e); } } @@ -293,7 +306,9 @@ impl Executor { }; #[cfg(feature = "metrics")] - metrics::inc_executor_transact(start.elapsed(), true); + metrics::inc_executor_transact(start.elapsed(), true, function.clone()); + #[cfg(feature = "metrics")] + metrics::inc_executor_transact_gas(tx_execution.execution().gas.as_u64() as usize, true, function); Ok(tx_execution) } @@ -303,6 +318,8 @@ impl Executor { pub async fn call(&self, input: CallInput, point_in_time: StoragePointInTime) -> anyhow::Result { #[cfg(feature = "metrics")] let start = metrics::now(); + #[cfg(feature = "metrics")] + let function = input.extract_function(); tracing::info!( from = ?input.from, @@ -317,9 +334,14 @@ impl Executor { let evm_result = self.execute_in_evm(evm_input).await; #[cfg(feature = "metrics")] - metrics::inc_executor_call(start.elapsed(), evm_result.is_ok()); + metrics::inc_executor_call(start.elapsed(), evm_result.is_ok(), function.clone()); + + let execution = evm_result?.execution; + + #[cfg(feature = "metrics")] + metrics::inc_executor_call_gas(execution.gas.as_u64() as usize, function.clone()); - evm_result.map(|x| x.execution) + Ok(execution) } // ------------------------------------------------------------------------- diff --git a/src/eth/primitives/account.rs b/src/eth/primitives/account.rs index b27a4ce6e..b40c58a03 100644 --- a/src/eth/primitives/account.rs +++ b/src/eth/primitives/account.rs @@ -167,6 +167,7 @@ pub fn test_accounts() -> Vec { hex!("15d34aaf54267db7d7c367839aaf71a00a2c6a65"), hex!("9965507d1a55bcc2695c58ba16fb37d819b0a4dc"), hex!("976ea74026e726554db657fa54763abd0c3a0aa9"), + hex!("e45b176cad7090a5cf70b69a73b6def9296ba6a2"), ] .into_iter() .map(|address| Account { diff --git a/src/eth/primitives/alias.rs b/src/eth/primitives/alias.rs deleted file mode 100644 index 814a72c71..000000000 --- a/src/eth/primitives/alias.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Alias Module -//! -//! This module defines convenient aliases for various Ethereum-specific -//! signatures and identifiers. It includes types like `Signature4Bytes` for -//! identifying function and error signatures, `Signature32Bytes` for event -//! signatures, and `SoliditySignature` for general Solidity function, error, -//! or event signatures. These aliases simplify code readability and -//! maintenance by providing clear, descriptive types in Ethereum-related -//! operations. - -/// Alias for 4 byte signature used to identify functions and errors. -pub type Signature4Bytes = [u8; 4]; - -/// Alias for 32 byte signature used to identify events. -pub type Signature32Bytes = [u8; 32]; - -/// Alias for a Solidity function, error or event signature. -pub type SoliditySignature = &'static str; diff --git a/src/eth/primitives/call_input.rs b/src/eth/primitives/call_input.rs index f8d5840d5..d11c0decb 100644 --- a/src/eth/primitives/call_input.rs +++ b/src/eth/primitives/call_input.rs @@ -6,8 +6,10 @@ //! the transaction data payload (`data`). It is crucial in constructing and //! interpreting transaction calls, especially for smart contract interactions. +use super::Signature; use crate::eth::primitives::Address; use crate::eth::primitives::Bytes; +use crate::eth::primitives::SoliditySignature; use crate::eth::primitives::Wei; #[derive(Debug, Clone, serde::Deserialize)] @@ -21,3 +23,10 @@ pub struct CallInput { #[serde(default)] pub data: Bytes, } + +impl CallInput { + pub fn extract_function(&self) -> Option { + let sig = Signature::Function(self.data.get(..4)?.try_into().ok()?); + Some(sig.extract()) + } +} diff --git a/src/eth/primitives/mod.rs b/src/eth/primitives/mod.rs index 2e709b167..20c95ae91 100644 --- a/src/eth/primitives/mod.rs +++ b/src/eth/primitives/mod.rs @@ -76,13 +76,12 @@ mod account; mod address; -mod alias; mod block; mod block_header; mod block_number; mod block_selection; mod bytecode; -mod bytes; +pub mod bytes; mod call_input; mod chain_id; mod code_hash; @@ -111,6 +110,7 @@ pub mod logs_bloom; mod miner_nonce; mod nonce; mod pending_block; +mod signature; mod size; mod slot; mod slot_access; @@ -128,9 +128,6 @@ mod wei; pub use account::test_accounts; pub use account::Account; pub use address::Address; -pub use alias::Signature32Bytes; -pub use alias::Signature4Bytes; -pub use alias::SoliditySignature; pub use block::Block; pub use block_header::BlockHeader; pub use block_number::BlockNumber; @@ -168,6 +165,8 @@ pub use log_topic::LogTopic; pub use miner_nonce::MinerNonce; pub use nonce::Nonce; pub use pending_block::PendingBlock; +pub use signature::Signature; +pub use signature::SoliditySignature; pub use size::Size; pub use slot::Slot; pub use slot_access::SlotAccess; diff --git a/src/eth/primitives/signature.rs b/src/eth/primitives/signature.rs new file mode 100644 index 000000000..6a4a13172 --- /dev/null +++ b/src/eth/primitives/signature.rs @@ -0,0 +1,38 @@ +use std::borrow::Cow; + +use crate::eth::codegen; + +/// Alias for 4 byte signature used to identify functions and errors. +pub type Signature4Bytes = [u8; 4]; + +/// Alias for 32 byte signature used to identify events. +pub type Signature32Bytes = [u8; 32]; + +/// Alias for a Solidity function, error or event signature. +pub type SoliditySignature = Cow<'static, str>; + +pub enum Signature { + Function(Signature4Bytes), + Event(Signature32Bytes), +} + +impl Signature { + pub fn encoded(self) -> String { + match self { + Signature::Function(id) => const_hex::encode_prefixed(id), + Signature::Event(id) => const_hex::encode_prefixed(id), + } + } + + pub fn extract(self) -> SoliditySignature { + let sig = match self { + Signature::Function(id) => codegen::SIGNATURES_4_BYTES.get(&id), + Signature::Event(id) => codegen::SIGNATURES_32_BYTES.get(&id), + }; + + match sig { + Some(signature) => Cow::from(*signature), + None => Cow::from(self.encoded()), + } + } +} diff --git a/src/eth/primitives/transaction_input.rs b/src/eth/primitives/transaction_input.rs index 629c90735..8388bbf66 100644 --- a/src/eth/primitives/transaction_input.rs +++ b/src/eth/primitives/transaction_input.rs @@ -5,6 +5,8 @@ //! payload. It is essential for creating and interpreting Ethereum //! transactions, providing a comprehensive interface for transaction data. +use std::borrow::Cow; + use anyhow::anyhow; use display_json::DebugAsJson; use ethereum_types::U256; @@ -16,6 +18,8 @@ use fake::Fake; use fake::Faker; use rlp::Decodable; +use super::signature::SoliditySignature; +use super::Signature; use crate::eth::primitives::Address; use crate::eth::primitives::Bytes; use crate::eth::primitives::ChainId; @@ -54,6 +58,15 @@ impl TransactionInput { pub fn is_contract_deployment(&self) -> bool { self.to.is_none() && not(self.input.is_empty()) } + + pub fn extract_function(&self) -> Option { + if self.is_contract_deployment() { + return Some(Cow::from("contract_deployment")); + } + let sig = Signature::Function(self.input.get(..4)?.try_into().ok()?); + + Some(sig.extract()) + } } impl Dummy for TransactionInput { diff --git a/src/eth/rpc/rpc_middleware.rs b/src/eth/rpc/rpc_middleware.rs index 8c4e6df2e..c5f4d42a5 100644 --- a/src/eth/rpc/rpc_middleware.rs +++ b/src/eth/rpc/rpc_middleware.rs @@ -11,10 +11,8 @@ use jsonrpsee::types::Params; use jsonrpsee::MethodResponse; use pin_project::pin_project; -use crate::eth::codegen::{self}; use crate::eth::primitives::Bytes; use crate::eth::primitives::CallInput; -use crate::eth::primitives::Signature4Bytes; use crate::eth::primitives::SoliditySignature; use crate::eth::primitives::TransactionInput; use crate::eth::rpc::next_rpc_param; @@ -50,14 +48,14 @@ where tracing::info!( id = %request.id, %method, - function = %function.unwrap_or_default(), + function = %function.clone().unwrap_or_default(), params = ?request.params(), "rpc request" ); // metrify request #[cfg(feature = "metrics")] - metrics::inc_rpc_requests_started(method, function); + metrics::inc_rpc_requests_started(method, function.clone()); RpcResponse { id: request.id.to_string(), @@ -71,24 +69,13 @@ where fn extract_function_from_call(params: Params) -> Option { let (_, call) = next_rpc_param::(params.sequence()).ok()?; - let data = call.data; - extract_function_signature(data.get(..4)?.try_into().ok()?) + call.extract_function() } fn extract_function_from_transaction(params: Params) -> Option { let (_, data) = next_rpc_param::(params.sequence()).ok()?; let transaction = parse_rpc_rlp::(&data).ok()?; - if transaction.is_contract_deployment() { - return Some("contract_deployment"); - } - extract_function_signature(transaction.input.get(..4)?.try_into().ok()?) -} - -fn extract_function_signature(id: Signature4Bytes) -> Option { - match codegen::SIGNATURES_4_BYTES.get(&id) { - Some(signature) => Some(signature), - None => Some("unknown"), - } + transaction.extract_function() } // ----------------------------------------------------------------------------- @@ -123,7 +110,7 @@ impl> Future for RpcResponse { tracing::info!( id = %proj.id, method = %proj.method, - function = %proj.function.unwrap_or_default(), + function = %proj.function.clone().unwrap_or_default(), duration_ms = %elapsed.as_millis(), success = %response.is_success(), result = %response.as_result(), @@ -132,7 +119,7 @@ impl> Future for RpcResponse { // metrify response #[cfg(feature = "metrics")] - metrics::inc_rpc_requests_finished(elapsed, proj.method.clone(), *proj.function, response.is_success()); + metrics::inc_rpc_requests_finished(elapsed, proj.method.clone(), proj.function.clone(), response.is_success()); } response diff --git a/src/eth/storage/stratus_storage.rs b/src/eth/storage/stratus_storage.rs index 4d3b55e7a..67b9476ed 100644 --- a/src/eth/storage/stratus_storage.rs +++ b/src/eth/storage/stratus_storage.rs @@ -362,6 +362,7 @@ impl StratusStorage { let start = metrics::now(); let result_result = self.temp.reset().await; + #[cfg(feature = "metrics")] let result_set = self.set_active_block_number_as_next().await; #[cfg(feature = "metrics")] diff --git a/src/infra/metrics.rs b/src/infra/metrics.rs index 2d5977e0a..bc3829d1d 100644 --- a/src/infra/metrics.rs +++ b/src/infra/metrics.rs @@ -1,6 +1,7 @@ //! Metrics services. #![cfg(feature = "metrics")] +use std::borrow::Cow; use std::stringify; use std::time::Instant; @@ -174,7 +175,10 @@ metrics! { histogram_duration executor_external_block{} [], "Time to execute and persist temporary changes of a single transaction inside import_offline operation." - histogram_duration executor_external_transaction{} [], + histogram_duration executor_external_transaction{function} [], + + "Gas spent to execute a single transaction inside import_offline operation." + histogram_counter executor_external_transaction_gas{function} [], "Number of account reads when importing an external block." histogram_counter executor_external_block_account_reads{} [0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 150., 200.], @@ -186,10 +190,16 @@ metrics! { histogram_counter executor_external_block_slot_reads_cached{} [0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 200., 300., 400., 500., 600., 700., 800., 900., 1000., 2000., 3000., 4000., 5000., 6000., 7000., 8000., 9000., 10000.], "Time to execute a transaction received with eth_sendRawTransaction." - histogram_duration executor_transact{success} [], + histogram_duration executor_transact{success, function} [], + + "Gas spent execute a transaction received with eth_sendRawTransaction." + histogram_counter executor_transact_gas{success, function} [], "Time to execute a transaction received with eth_call or eth_estimateGas." - histogram_duration executor_call{success} [] + histogram_duration executor_call{success, function} [], + + "Gas spent to execute a transaction received with eth_call or eth_estimateGas." + histogram_counter executor_call_gas{function} [] } metrics! { @@ -255,6 +265,15 @@ pub enum LabelValue { None, } +impl From>> for LabelValue { + fn from(value: Option>) -> Self { + match value { + Some(str) => Self::Some(str.into_owned()), + None => Self::None, + } + } +} + impl From<&str> for LabelValue { fn from(value: &str) -> Self { Self::Some(value.to_owned()) diff --git a/tests/test_import_external_snapshot_common.rs b/tests/test_import_external_snapshot_common.rs index 79f4943b7..4ae11fc7f 100644 --- a/tests/test_import_external_snapshot_common.rs +++ b/tests/test_import_external_snapshot_common.rs @@ -4,6 +4,7 @@ use std::fs; use std::sync::Arc; use std::time::Duration; +#[cfg(feature = "dev")] use fancy_duration::AsFancyDuration; use itertools::Itertools; use stratus::config::IntegrationTestConfig; @@ -188,7 +189,10 @@ pub async fn execute_test( println!("{:<70} = {}", query, value); } else { let secs = Duration::from_secs_f64(value); + #[cfg(feature = "dev")] println!("{:<70} = {}", query, secs.fancy_duration().truncate(2)); + #[cfg(not(feature = "dev"))] + println!("{:<70} = {}", query, secs.as_millis()); } } }