Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: time and gas consumption metrics for each contract function #840

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -63,6 +64,7 @@ fn populate_signature_maps(
seen: &mut HashSet<Vec<u8>>,
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) {
Expand All @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions src/eth/evm/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
//! 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;

use crate::eth::primitives::bytes::extract_function_signature;
use crate::eth::primitives::Address;
use crate::eth::primitives::BlockNumber;
use crate::eth::primitives::Bytes;
Expand All @@ -21,10 +24,12 @@ use crate::eth::primitives::ExternalReceipt;
use crate::eth::primitives::ExternalTransaction;
use crate::eth::primitives::Gas;
use crate::eth::primitives::Nonce;
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;

Expand Down Expand Up @@ -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<SoliditySignature> {
if self.is_contract_deployment() {
return Some(Cow::from("contract_deployment"));
}
let id: [u8; 4] = self.data.get(..4)?.try_into().ok()?;

Some(extract_function_signature(id))
}

/// Creates from a transaction that was sent directly to Stratus with `eth_sendRawTransaction`.
pub fn from_eth_transaction(input: TransactionInput) -> Self {
Self {
Expand Down
32 changes: 27 additions & 5 deletions src/eth/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")]
carneiro-cw marked this conversation as resolved.
Show resolved Hide resolved
let gas_used = evm_result.execution.gas;

#[cfg(feature = "metrics")]
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)),
Expand All @@ -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))
}
Expand All @@ -235,6 +246,8 @@ impl Executor {
pub async fn transact(&self, tx_input: TransactionInput) -> anyhow::Result<TransactionExecution> {
#[cfg(feature = "metrics")]
let start = metrics::now();
#[cfg(feature = "metrics")]
let function = tx_input.extract_function();

tracing::info!(
hash = %tx_input.hash,
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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)
}
Expand All @@ -303,6 +318,8 @@ impl Executor {
pub async fn call(&self, input: CallInput, point_in_time: StoragePointInTime) -> anyhow::Result<EvmExecution> {
#[cfg(feature = "metrics")]
let start = metrics::now();
#[cfg(feature = "metrics")]
let function = input.extract_function();

tracing::info!(
from = ?input.from,
Expand All @@ -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)
}

// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/eth/primitives/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ pub fn test_accounts() -> Vec<Account> {
hex!("15d34aaf54267db7d7c367839aaf71a00a2c6a65"),
hex!("9965507d1a55bcc2695c58ba16fb37d819b0a4dc"),
hex!("976ea74026e726554db657fa54763abd0c3a0aa9"),
hex!("e45b176cad7090a5cf70b69a73b6def9296ba6a2"),
]
.into_iter()
.map(|address| Account {
Expand Down
4 changes: 3 additions & 1 deletion src/eth/primitives/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
//! maintenance by providing clear, descriptive types in Ethereum-related
//! operations.

use std::borrow::Cow;

/// 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;
pub type SoliditySignature = Cow<'static, str>;
11 changes: 11 additions & 0 deletions src/eth/primitives/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//! various byte formats and Ethereum-specific types, playing a key role in
//! data serialization and processing.

use std::borrow::Cow;
use std::fmt::Debug;
use std::fmt::Display;
use std::ops::Deref;
Expand All @@ -17,6 +18,9 @@ use revm::primitives::Bytecode as RevmBytecode;
use revm::primitives::Bytes as RevmBytes;
use revm::primitives::Output as RevmOutput;

use crate::eth::codegen;
use crate::eth::primitives::Signature4Bytes;
use crate::eth::primitives::SoliditySignature;
use crate::gen_newtype_from;

#[derive(Clone, Default, Eq, PartialEq, fake::Dummy, sqlx::Type)]
Expand Down Expand Up @@ -139,3 +143,10 @@ impl From<Bytes> for RevmBytecode {
to_analysed(RevmBytecode::new_raw(value.0.into()))
}
}

pub fn extract_function_signature(id: Signature4Bytes) -> SoliditySignature {
carneiro-cw marked this conversation as resolved.
Show resolved Hide resolved
match codegen::SIGNATURES_4_BYTES.get(&id) {
Some(signature) => Cow::from(*signature),
None => Cow::from(const_hex::encode_prefixed(id)),
}
}
9 changes: 9 additions & 0 deletions src/eth/primitives/call_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
//! the transaction data payload (`data`). It is crucial in constructing and
//! interpreting transaction calls, especially for smart contract interactions.

use crate::eth::primitives::bytes::extract_function_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)]
Expand All @@ -21,3 +23,10 @@ pub struct CallInput {
#[serde(default)]
pub data: Bytes,
}

impl CallInput {
pub fn extract_function(&self) -> Option<SoliditySignature> {
let id: [u8; 4] = self.data.get(..4)?.try_into().ok()?;
Some(extract_function_signature(id))
}
}
2 changes: 1 addition & 1 deletion src/eth/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ 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;
Expand Down
13 changes: 13 additions & 0 deletions src/eth/primitives/transaction_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,13 +18,15 @@ use fake::Fake;
use fake::Faker;
use rlp::Decodable;

use crate::eth::primitives::bytes::extract_function_signature;
use crate::eth::primitives::Address;
use crate::eth::primitives::Bytes;
use crate::eth::primitives::ChainId;
use crate::eth::primitives::ExternalTransaction;
use crate::eth::primitives::Gas;
use crate::eth::primitives::Hash;
use crate::eth::primitives::Nonce;
use crate::eth::primitives::SoliditySignature;
use crate::eth::primitives::Wei;
use crate::ext::not;
use crate::ext::OptionExt;
Expand Down Expand Up @@ -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<SoliditySignature> {
if self.is_contract_deployment() {
return Some(Cow::from("contract_deployment"));
}
let id: [u8; 4] = self.input.get(..4)?.try_into().ok()?;

Some(extract_function_signature(id))
}
}

impl Dummy<Faker> for TransactionInput {
Expand Down
25 changes: 6 additions & 19 deletions src/eth/rpc/rpc_middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand All @@ -71,24 +69,13 @@ where

fn extract_function_from_call(params: Params) -> Option<SoliditySignature> {
let (_, call) = next_rpc_param::<CallInput>(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<SoliditySignature> {
let (_, data) = next_rpc_param::<Bytes>(params.sequence()).ok()?;
let transaction = parse_rpc_rlp::<TransactionInput>(&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<SoliditySignature> {
match codegen::SIGNATURES_4_BYTES.get(&id) {
Some(signature) => Some(signature),
None => Some("unknown"),
}
transaction.extract_function()
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -123,7 +110,7 @@ impl<F: Future<Output = MethodResponse>> Future for RpcResponse<F> {
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(),
Expand All @@ -132,7 +119,7 @@ impl<F: Future<Output = MethodResponse>> Future for RpcResponse<F> {

// 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
Expand Down
Loading
Loading