Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions crates/blockifier/src/blockifier/transaction_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::bouncer::{Bouncer, BouncerWeights, CasmHashComputationData};
use crate::concurrency::worker_logic::WorkerExecutor;
use crate::concurrency::worker_pool::WorkerPool;
use crate::context::BlockContext;
use crate::metrics::{record_transaction_executor_metrics, TransactionExecutorMetrics};
use crate::state::cached_state::{CachedState, CommitmentStateDiff, StateMaps, TransactionalState};
use crate::state::compiled_class_hash_migration::CompiledClassHashMigrationUpdater;
use crate::state::errors::StateError;
Expand Down Expand Up @@ -187,13 +188,15 @@ impl<S: StateReader> TransactionExecutor<S> {
execution_deadline: Option<Instant>,
) -> Vec<TransactionExecutorResult<TransactionExecutionOutput>> {
let mut results = Vec::new();
let mut execution_attempts = 0;
for tx in txs {
if let Some(deadline) = execution_deadline {
if Instant::now() > deadline {
log::debug!("Execution timed out.");
break;
}
}
execution_attempts += 1;
match self.execute(tx) {
Ok((tx_execution_info, state_diff)) => {
results.push(Ok((tx_execution_info, state_diff)))
Expand All @@ -202,6 +205,13 @@ impl<S: StateReader> TransactionExecutor<S> {
Err(error) => results.push(Err(error)),
}
}
record_transaction_executor_metrics(TransactionExecutorMetrics {
transactions: u64::try_from(txs.len()).expect("transaction count should fit in u64"),
committed_transactions: u64::try_from(results.len())
.expect("committed transaction count should fit in u64"),
execution_attempts,
..Default::default()
});
results
}

Expand Down
50 changes: 32 additions & 18 deletions crates/blockifier/src/concurrency/worker_logic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
Expand All @@ -23,7 +23,12 @@ use crate::concurrency::versioned_state::{
};
use crate::concurrency::TxIndex;
use crate::context::BlockContext;
use crate::metrics::{CALLS_RUNNING_NATIVE, TOTAL_CALLS};
use crate::metrics::{
record_transaction_executor_metrics,
TransactionExecutorMetrics,
CALLS_RUNNING_NATIVE,
TOTAL_CALLS,
};
use crate::state::cached_state::{ContractClassMapping, StateMaps, TransactionalState};
use crate::state::state_api::{StateReader, UpdatableState};
use crate::transaction::objects::{TransactionExecutionInfo, TransactionExecutionResult};
Expand All @@ -47,10 +52,10 @@ pub struct ExecutionTaskOutput {

#[derive(Default)]
pub struct ConcurrencyMetrics {
abort_counter: AtomicUsize,
abort_in_commit_counter: AtomicUsize,
execute_counter: AtomicUsize,
validate_counter: AtomicUsize,
abort_counter: AtomicU64,
abort_in_commit_counter: AtomicU64,
execute_counter: AtomicU64,
validate_counter: AtomicU64,
}

impl ConcurrencyMetrics {
Expand All @@ -66,13 +71,14 @@ impl ConcurrencyMetrics {
pub fn count_validate(&self) {
self.validate_counter.fetch_add(1, Ordering::Relaxed);
}
pub fn get_metrics(&self) -> (usize, usize, usize, usize) {
(
self.abort_counter.load(Ordering::Relaxed),
self.abort_in_commit_counter.load(Ordering::Relaxed),
self.execute_counter.load(Ordering::Relaxed),
self.validate_counter.load(Ordering::Relaxed),
)
pub fn snapshot(&self) -> TransactionExecutorMetrics {
TransactionExecutorMetrics {
execution_attempts: self.execute_counter.load(Ordering::Relaxed),
validation_attempts: self.validate_counter.load(Ordering::Relaxed),
aborts: self.abort_counter.load(Ordering::Relaxed),
commit_phase_aborts: self.abort_in_commit_counter.load(Ordering::Relaxed),
..Default::default()
}
}
}

Expand Down Expand Up @@ -419,14 +425,22 @@ impl<S: StateReader> WorkerExecutor<S> {

impl<U: UpdatableState> WorkerExecutor<U> {
pub fn commit_chunk_and_recover_block_state(&self, n_committed_txs: usize) -> U {
let (abort_counter, abort_in_commit_counter, execute_counter, validate_counter) =
self.metrics.get_metrics();
let n_txs = self.get_n_txs();
let metrics = TransactionExecutorMetrics {
transactions: u64::try_from(n_txs).expect("transaction count should fit in u64"),
committed_transactions: u64::try_from(n_committed_txs)
.expect("committed transaction count should fit in u64"),
..self.metrics.snapshot()
};
record_transaction_executor_metrics(metrics);
log::debug!(
"Concurrent execution done. Number of transactions: {n_txs}; Committed chunk size: \
{n_committed_txs}; Execute counter: {execute_counter}; Validate counter: \
{validate_counter}; Abort counter: {abort_counter}; Abort in commit counter: \
{abort_in_commit_counter}"
{n_committed_txs}; Execute counter: {}; Validate counter: {}; Abort counter: {}; Abort \
in commit counter: {}",
metrics.execution_attempts,
metrics.validation_attempts,
metrics.aborts,
metrics.commit_phase_aborts,
);

self.state.into_inner_state().commit_chunk_and_recover_block_state(n_committed_txs)
Expand Down
18 changes: 17 additions & 1 deletion crates/blockifier/src/concurrency/worker_logic_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use starknet_api::transaction::TransactionVersion;
use starknet_api::{contract_address, declare_tx_args, felt, invoke_tx_args, nonce, storage_key};
use starknet_types_core::felt::Felt;

use super::WorkerExecutor;
use super::{ConcurrencyMetrics, WorkerExecutor};
use crate::bouncer::Bouncer;
use crate::concurrency::fee_utils::STORAGE_READ_SEQUENCER_BALANCE_INDICES;
use crate::concurrency::scheduler::{Task, TransactionStatus};
Expand Down Expand Up @@ -881,3 +881,19 @@ fn test_worker_commit_phase_with_halt() {
assert!(!result.unwrap().is_reverted());
}
}

#[test]
fn concurrency_metrics_snapshot_has_named_counters() {
let metrics = ConcurrencyMetrics::default();
metrics.count_execute();
metrics.count_execute();
metrics.count_validate();
metrics.count_abort();
metrics.count_abort_in_commit();

let snapshot = metrics.snapshot();
assert_eq!(snapshot.execution_attempts, 2);
assert_eq!(snapshot.validation_attempts, 1);
assert_eq!(snapshot.aborts, 1);
assert_eq!(snapshot.commit_phase_aborts, 1);
}
81 changes: 81 additions & 0 deletions crates/blockifier/src/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use apollo_metrics::define_metrics;
use apollo_metrics::metrics::{MetricCounter, MetricDetails, MetricScope};
use std::sync::atomic::{AtomicU64, Ordering};

define_metrics!(
Blockifier => {
Expand Down Expand Up @@ -29,6 +30,49 @@ define_metrics!(

pub const BLOCKIFIER_METRIC_RATE_DURATION: &str = "5m";

/// Process-lifetime transaction execution counters.
///
/// These mirror the per-chunk counters emitted by Blockifier's concurrent
/// executor while also covering sequential `execute_txs` calls. They are kept
/// as relaxed atomics so downstream nodes can export them without adding a
/// metrics dependency to the execution hot path.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TransactionExecutorMetrics {
pub transactions: u64,
pub committed_transactions: u64,
pub execution_attempts: u64,
pub validation_attempts: u64,
pub aborts: u64,
pub commit_phase_aborts: u64,
}

static TRANSACTIONS: AtomicU64 = AtomicU64::new(0);
static COMMITTED_TRANSACTIONS: AtomicU64 = AtomicU64::new(0);
static EXECUTION_ATTEMPTS: AtomicU64 = AtomicU64::new(0);
static VALIDATION_ATTEMPTS: AtomicU64 = AtomicU64::new(0);
static ABORTS: AtomicU64 = AtomicU64::new(0);
static COMMIT_PHASE_ABORTS: AtomicU64 = AtomicU64::new(0);

pub fn transaction_executor_metrics() -> TransactionExecutorMetrics {
TransactionExecutorMetrics {
transactions: TRANSACTIONS.load(Ordering::Relaxed),
committed_transactions: COMMITTED_TRANSACTIONS.load(Ordering::Relaxed),
execution_attempts: EXECUTION_ATTEMPTS.load(Ordering::Relaxed),
validation_attempts: VALIDATION_ATTEMPTS.load(Ordering::Relaxed),
aborts: ABORTS.load(Ordering::Relaxed),
commit_phase_aborts: COMMIT_PHASE_ABORTS.load(Ordering::Relaxed),
}
}

pub(crate) fn record_transaction_executor_metrics(metrics: TransactionExecutorMetrics) {
TRANSACTIONS.fetch_add(metrics.transactions, Ordering::Relaxed);
COMMITTED_TRANSACTIONS.fetch_add(metrics.committed_transactions, Ordering::Relaxed);
EXECUTION_ATTEMPTS.fetch_add(metrics.execution_attempts, Ordering::Relaxed);
VALIDATION_ATTEMPTS.fetch_add(metrics.validation_attempts, Ordering::Relaxed);
ABORTS.fetch_add(metrics.aborts, Ordering::Relaxed);
COMMIT_PHASE_ABORTS.fetch_add(metrics.commit_phase_aborts, Ordering::Relaxed);
}

pub struct CacheMetrics {
misses: MetricCounter,
hits: MetricCounter,
Expand All @@ -48,6 +92,43 @@ impl CacheMetrics {
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn transaction_executor_metrics_accumulate_monotonically() {
let before = transaction_executor_metrics();
let increment = TransactionExecutorMetrics {
transactions: 50,
committed_transactions: 48,
execution_attempts: 73,
validation_attempts: 81,
aborts: 23,
commit_phase_aborts: 4,
};

record_transaction_executor_metrics(increment);
let after = transaction_executor_metrics();

assert!(after.transactions >= before.transactions + increment.transactions);
assert!(
after.committed_transactions
>= before.committed_transactions + increment.committed_transactions
);
assert!(
after.execution_attempts >= before.execution_attempts + increment.execution_attempts
);
assert!(
after.validation_attempts >= before.validation_attempts + increment.validation_attempts
);
assert!(after.aborts >= before.aborts + increment.aborts);
assert!(
after.commit_phase_aborts >= before.commit_phase_aborts + increment.commit_phase_aborts
);
}
}

impl CacheMetrics {
pub fn register(&self) {
self.misses.register();
Expand Down
1 change: 1 addition & 0 deletions crates/starknet_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ cached.workspace = true
cairo-lang-runner.workspace = true
cairo-lang-starknet-classes.workspace = true
cairo-lang-utils.workspace = true
dashmap.workspace = true
derive_more = { workspace = true, features = [
"add",
"add_assign",
Expand Down
19 changes: 15 additions & 4 deletions crates/starknet_api/src/abi/abi_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use starknet_types_core::felt::{Felt, NonZeroFelt};
use starknet_types_core::hash::{Pedersen, StarkHash};

use crate::abi::constants;
use crate::core::{ContractAddress, EntryPointSelector, PatriciaKey, L2_ADDRESS_UPPER_BOUND};
use crate::core::{ContractAddress, EntryPointSelector, L2_ADDRESS_UPPER_BOUND, PatriciaKey};
use crate::hash_cache;
use crate::state::StorageKey;

#[cfg(test)]
Expand All @@ -12,13 +13,18 @@ mod test;

/// A variant of eth-keccak that computes a value that fits in a Starknet field element.
pub fn starknet_keccak(data: &[u8]) -> Felt {
if let Some(cached) = hash_cache::sn_keccak_get(data) {
return cached;
}
let mut hasher = Keccak256::new();
hasher.update(data);
let mut result: [u8; 32] = hasher.finalize().into();

// Truncate result to 250 bits.
*result.first_mut().unwrap() &= 3;
Felt::from_bytes_be(&result)
let result = Felt::from_bytes_be(&result);
hash_cache::sn_keccak_insert(data, result);
result
}

/// Returns an entry point selector, given its name.
Expand All @@ -39,8 +45,13 @@ pub fn selector_from_name(entry_point_name: &str) -> EntryPointSelector {
pub fn get_storage_var_address(storage_var_name: &str, args: &[Felt]) -> StorageKey {
let storage_var_name_hash = starknet_keccak(storage_var_name.as_bytes());

let storage_key_hash =
args.iter().fold(storage_var_name_hash, |res, arg| Pedersen::hash(&res, arg));
let storage_key_hash = args.iter().fold(storage_var_name_hash, |res, arg| {
hash_cache::pedersen_pair_get(res, *arg).unwrap_or_else(|| {
let result = Pedersen::hash(&res, arg);
hash_cache::pedersen_pair_insert(res, *arg, result);
result
})
});

let storage_key = storage_key_hash
.mod_floor(&NonZeroFelt::from_raw(Felt::from(*L2_ADDRESS_UPPER_BOUND).to_raw()));
Expand Down
19 changes: 15 additions & 4 deletions crates/starknet_api/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ use starknet_types_core::hash::{Pedersen, StarkHash as CoreStarkHash};

use crate::crypto::utils::PublicKey;
use crate::hash::{HashOutput, PoseidonHash, StarkHash};
use crate::hash_cache;
use crate::serde_utils::{BytesAsHex, PrefixedBytesAsHex};
use crate::transaction::fields::{Calldata, ContractAddressSalt};
use crate::{impl_from_through_intermediate, StarknetApiError, StarknetApiResult};
use crate::{StarknetApiError, StarknetApiResult, impl_from_through_intermediate};

/// Felt.
pub fn ascii_as_felt(ascii_str: &str) -> Result<Felt, StarknetApiError> {
Expand Down Expand Up @@ -269,17 +270,27 @@ pub fn calculate_contract_address(
constructor_calldata: &Calldata,
deployer_address: ContractAddress,
) -> Result<ContractAddress, StarknetApiError> {
let constructor_calldata_hash = Pedersen::hash_array(&constructor_calldata.0);
let constructor_calldata_hash = hash_cache::pedersen_array_get(&constructor_calldata.0)
.unwrap_or_else(|| {
let result = Pedersen::hash_array(&constructor_calldata.0);
hash_cache::pedersen_array_insert(&constructor_calldata.0, result);
result
});
let contract_address_prefix = format!("0x{}", hex::encode(CONTRACT_ADDRESS_PREFIX));
let address = Pedersen::hash_array(&[
let values = [
Felt::from_hex(contract_address_prefix.as_str()).map_err(|_| {
StarknetApiError::OutOfRange { string: contract_address_prefix.clone() }
})?,
*deployer_address.0.key(),
salt.0,
class_hash.0,
constructor_calldata_hash,
]);
];
let address = hash_cache::pedersen_array_get(&values).unwrap_or_else(|| {
let result = Pedersen::hash_array(&values);
hash_cache::pedersen_array_insert(&values, result);
result
});
let (_, address) = address.div_rem(&L2_ADDRESS_UPPER_BOUND);

ContractAddress::try_from(address)
Expand Down
13 changes: 11 additions & 2 deletions crates/starknet_api/src/crypto/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use starknet_types_core::hash::{Pedersen, Poseidon, StarkHash as CoreStarkHash};
use thiserror::Error;

use crate::hash::StarkHash;
use crate::hash_cache;

/// An error that can occur during cryptographic operations.

Expand Down Expand Up @@ -113,12 +114,20 @@ impl HashChain {

// Returns the pedersen hash of the chained felts, hashed with the length of the chain.
pub fn get_pedersen_hash(&self) -> StarkHash {
Pedersen::hash_array(self.elements.as_slice())
hash_cache::pedersen_array_get(self.elements.as_slice()).unwrap_or_else(|| {
let result = Pedersen::hash_array(self.elements.as_slice());
hash_cache::pedersen_array_insert(self.elements.as_slice(), result);
result
})
}

// Returns the poseidon hash of the chained felts.
pub fn get_poseidon_hash(&self) -> StarkHash {
Poseidon::hash_array(self.elements.as_slice())
hash_cache::poseidon_array_get(self.elements.as_slice()).unwrap_or_else(|| {
let result = Poseidon::hash_array(self.elements.as_slice());
hash_cache::poseidon_array_insert(self.elements.as_slice(), result);
result
})
}
}

Expand Down
Loading
Loading