From 3d0432cc7f9c8a095477ddf0e48b9fc7693d586e Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Tue, 2 Jun 2026 14:34:11 +0200 Subject: [PATCH 1/4] perf(l1): short-circuit KECCAK256 on empty input Return the precomputed keccak256("") constant for zero-length input instead of running the permutation. Benchmarkoor shows ethrex at ~19x reth's gap on empty-input KECCAK256 while at parity for non-empty hashing, isolating the cost to per-op hashing of empty data. --- crates/vm/levm/src/opcode_handlers/keccak.rs | 36 +++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index 770a857ce2d..2499543b2bf 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -11,8 +11,18 @@ use crate::{ utils::size_offset_to_usize, vm::VM, }; +use ethrex_common::U256; use ethrex_common::utils::u256_from_big_endian; +/// `keccak256("")` as a `U256`. Returned directly for zero-length input so the +/// hot path skips the permutation entirely (matches what other clients do). +const EMPTY_KECCAK_U256: U256 = U256([ + 0x7bfad8045d85a470, + 0xe500b653ca82273b, + 0x927e7db2dcc703c0, + 0xc5d2460186f7233c, +]); + pub struct OpKeccak256Handler; impl OpcodeHandler for OpKeccak256Handler { #[inline(always)] @@ -27,12 +37,28 @@ impl OpcodeHandler for OpKeccak256Handler { len, )?)?; - vm.current_call_frame - .stack - .push(u256_from_big_endian(&vm.crypto.keccak256( - &vm.current_call_frame.memory.load_range(offset, len)?, - )))?; + let hash = if len == 0 { + EMPTY_KECCAK_U256 + } else { + u256_from_big_endian( + &vm.crypto + .keccak256(&vm.current_call_frame.memory.load_range(offset, len)?), + ) + }; + vm.current_call_frame.stack.push(hash)?; Ok(OpcodeResult::Continue) } } + +#[cfg(test)] +mod tests { + use super::*; + use ethrex_crypto::{Crypto, NativeCrypto}; + + #[test] + fn empty_keccak_const_matches_hash() { + let expected = u256_from_big_endian(&NativeCrypto.keccak256(&[])); + assert_eq!(EMPTY_KECCAK_U256, expected); + } +} From 8ef02c21a1d44b975b5cbbfcbd9e31c095a32244 Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Wed, 3 Jun 2026 10:58:47 +0200 Subject: [PATCH 2/4] refactor(l1): address review on KECCAK256 empty-input fast path - merge ethrex_common imports - reword misleading hot-path comment - add drift guard test against EMPTY_KECCACK_HASH --- crates/vm/levm/src/opcode_handlers/keccak.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index 2499543b2bf..739f790e8a6 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -11,11 +11,11 @@ use crate::{ utils::size_offset_to_usize, vm::VM, }; -use ethrex_common::U256; -use ethrex_common::utils::u256_from_big_endian; +use ethrex_common::{U256, utils::u256_from_big_endian}; -/// `keccak256("")` as a `U256`. Returned directly for zero-length input so the -/// hot path skips the permutation entirely (matches what other clients do). +/// `keccak256("")` as a `U256`. Returned directly for zero-length input so we +/// skip the permutation entirely; the result is a well-known constant +/// (matches what other clients do). const EMPTY_KECCAK_U256: U256 = U256([ 0x7bfad8045d85a470, 0xe500b653ca82273b, @@ -54,6 +54,7 @@ impl OpcodeHandler for OpKeccak256Handler { #[cfg(test)] mod tests { use super::*; + use ethrex_common::constants::EMPTY_KECCACK_HASH; use ethrex_crypto::{Crypto, NativeCrypto}; #[test] @@ -61,4 +62,14 @@ mod tests { let expected = u256_from_big_endian(&NativeCrypto.keccak256(&[])); assert_eq!(EMPTY_KECCAK_U256, expected); } + + #[test] + fn empty_keccak_const_matches_common_constant() { + // Guards against drift between this const and `EMPTY_KECCACK_HASH` + // in `ethrex_common::constants` (note: the latter has a typo'd name). + assert_eq!( + EMPTY_KECCAK_U256, + u256_from_big_endian(EMPTY_KECCACK_HASH.as_bytes()) + ); + } } From 4c999522754b77d5d2b85c6cf60ab21bd932124f Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Wed, 3 Jun 2026 11:00:10 +0200 Subject: [PATCH 3/4] refactor(l1,l2): fix EMPTY_KECCACK_HASH typo -> EMPTY_KECCAK_HASH --- crates/blockchain/vm.rs | 8 +++---- crates/common/constants.rs | 2 +- crates/common/types/account.rs | 14 +++++------ crates/common/types/block.rs | 6 ++--- .../common/types/block_execution_witness.rs | 6 ++--- crates/networking/p2p/sync/healing/state.rs | 4 ++-- crates/networking/p2p/sync/snap_sync.rs | 8 +++---- crates/networking/rpc/types/block.rs | 4 ++-- crates/storage/store.rs | 4 ++-- crates/vm/backends/levm/db.rs | 4 ++-- crates/vm/backends/levm/mod.rs | 24 +++++++++---------- crates/vm/backends/levm/tracing.rs | 8 +++---- crates/vm/levm/src/account.rs | 4 ++-- crates/vm/levm/src/db/gen_db.rs | 12 +++++----- crates/vm/levm/src/opcode_handlers/keccak.rs | 8 +++---- test/tests/storage/store_tests.rs | 8 +++---- tooling/archive_sync/src/main.rs | 4 ++-- tooling/ef_tests/blockchain/test_runner.rs | 4 ++-- 18 files changed, 66 insertions(+), 66 deletions(-) diff --git a/crates/blockchain/vm.rs b/crates/blockchain/vm.rs index ce3c13fbc86..c720eb58958 100644 --- a/crates/blockchain/vm.rs +++ b/crates/blockchain/vm.rs @@ -1,6 +1,6 @@ use ethrex_common::{ Address, H256, U256, - constants::EMPTY_KECCACK_HASH, + constants::EMPTY_KECCAK_HASH, types::{AccountState, BlockHash, BlockHeader, BlockNumber, ChainConfig, Code, CodeMetadata}, }; use ethrex_crypto::keccak::keccak_hash; @@ -210,7 +210,7 @@ impl VmDatabase for StoreVmDatabase { fields(namespace = "block_execution") )] fn get_account_code(&self, code_hash: H256) -> Result { - if code_hash == *EMPTY_KECCACK_HASH { + if code_hash == *EMPTY_KECCAK_HASH { return Ok(Code::default()); } match self.store.get_account_code(code_hash) { @@ -229,9 +229,9 @@ impl VmDatabase for StoreVmDatabase { fields(namespace = "block_execution") )] fn get_code_metadata(&self, code_hash: H256) -> Result { - use ethrex_common::constants::EMPTY_KECCACK_HASH; + use ethrex_common::constants::EMPTY_KECCAK_HASH; - if code_hash == *EMPTY_KECCACK_HASH { + if code_hash == *EMPTY_KECCAK_HASH { return Ok(CodeMetadata { length: 0 }); } match self.store.get_code_metadata(code_hash) { diff --git a/crates/common/constants.rs b/crates/common/constants.rs index 1da2596507c..d8d45c6dc54 100644 --- a/crates/common/constants.rs +++ b/crates/common/constants.rs @@ -35,7 +35,7 @@ pub static EMPTY_WITHDRAWALS_HASH: LazyLock = LazyLock::new(|| { }); // Keccak256(""), represents the code hash for an account without code -pub static EMPTY_KECCACK_HASH: LazyLock = LazyLock::new(|| { +pub static EMPTY_KECCAK_HASH: LazyLock = LazyLock::new(|| { H256::from_slice( &hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") .expect("Failed to decode hex from string"), diff --git a/crates/common/types/account.rs b/crates/common/types/account.rs index 589ac4f41cf..8f544ebc138 100644 --- a/crates/common/types/account.rs +++ b/crates/common/types/account.rs @@ -15,7 +15,7 @@ use ethrex_rlp::{ }; use super::GenesisAccount; -use crate::constants::{EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH}; +use crate::constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct Code { @@ -140,7 +140,7 @@ pub struct AccountStateSlimCodec(pub AccountState); impl Default for AccountInfo { fn default() -> Self { Self { - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, balance: Default::default(), nonce: Default::default(), } @@ -153,7 +153,7 @@ impl Default for AccountState { nonce: Default::default(), balance: Default::default(), storage_root: *EMPTY_TRIE_HASH, - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, } } } @@ -162,7 +162,7 @@ impl Default for Code { fn default() -> Self { Self { bytecode: Bytes::new(), - hash: *EMPTY_KECCACK_HASH, + hash: *EMPTY_KECCAK_HASH, jump_targets: Vec::new(), } } @@ -262,7 +262,7 @@ impl RLPEncode for AccountStateSlimCodec { struct CodeHashCodec<'a>(&'a H256); impl RLPEncode for CodeHashCodec<'_> { fn encode(&self, buf: &mut dyn BufMut) { - let data = if *self.0 != *EMPTY_KECCACK_HASH { + let data = if *self.0 != *EMPTY_KECCAK_HASH { self.0.as_bytes() } else { &[] @@ -306,7 +306,7 @@ impl RLPDecode for AccountStateSlimCodec { impl RLPDecode for CodeHashCodec { fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { let value = match rlp.split_off_first() { - Some(0x80) => *EMPTY_KECCACK_HASH, + Some(0x80) => *EMPTY_KECCAK_HASH, Some(0xA0) => { let data; (data, rlp) = rlp @@ -376,7 +376,7 @@ impl Account { impl AccountInfo { pub fn is_empty(&self) -> bool { - self.balance.is_zero() && self.nonce == 0 && self.code_hash == *EMPTY_KECCACK_HASH + self.balance.is_zero() && self.nonce == 0 && self.code_hash == *EMPTY_KECCAK_HASH } } diff --git a/crates/common/types/block.rs b/crates/common/types/block.rs index 7f339fd1ab1..6960a6e3718 100644 --- a/crates/common/types/block.rs +++ b/crates/common/types/block.rs @@ -877,7 +877,7 @@ pub fn calc_excess_blob_gas(parent: &BlockHeader, schedule: ForkBlobSchedule, fo #[cfg(test)] mod test { use super::*; - use crate::constants::EMPTY_KECCACK_HASH; + use crate::constants::EMPTY_KECCAK_HASH; use crate::types::{BLOB_BASE_FEE_UPDATE_FRACTION, ELASTICITY_MULTIPLIER}; use ethereum_types::H160; use hex_literal::hex; @@ -951,7 +951,7 @@ mod test { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*EMPTY_KECCACK_HASH), + requests_hash: Some(*EMPTY_KECCAK_HASH), ..Default::default() }; let block = BlockHeader { @@ -995,7 +995,7 @@ mod test { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*EMPTY_KECCACK_HASH), + requests_hash: Some(*EMPTY_KECCAK_HASH), ..Default::default() }; assert!(validate_block_header(&block, &parent_block, ELASTICITY_MULTIPLIER).is_ok()); diff --git a/crates/common/types/block_execution_witness.rs b/crates/common/types/block_execution_witness.rs index 35ecfada8da..d6d00379d65 100644 --- a/crates/common/types/block_execution_witness.rs +++ b/crates/common/types/block_execution_witness.rs @@ -6,7 +6,7 @@ use crate::rkyv_utils::H256Wrapper; use crate::serde_utils; use crate::types::{Block, Code, CodeMetadata}; use crate::{ - constants::EMPTY_KECCACK_HASH, + constants::EMPTY_KECCAK_HASH, types::{AccountState, AccountUpdate, BlockHeader, ChainConfig}, utils::keccak, }; @@ -631,7 +631,7 @@ impl GuestProgramState { /// include every byte of code the VM reads, and EIP-8025 stateless /// validation requires the same. pub fn get_account_code(&self, code_hash: H256) -> Result { - if code_hash == *EMPTY_KECCACK_HASH { + if code_hash == *EMPTY_KECCAK_HASH { return Ok(Code::default()); } self.codes_hashed @@ -646,7 +646,7 @@ impl GuestProgramState { &self, code_hash: H256, ) -> Result { - if code_hash == *EMPTY_KECCACK_HASH { + if code_hash == *EMPTY_KECCAK_HASH { return Ok(CodeMetadata { length: 0 }); } self.codes_hashed diff --git a/crates/networking/p2p/sync/healing/state.rs b/crates/networking/p2p/sync/healing/state.rs index 4bbc705f416..fab26e93d63 100644 --- a/crates/networking/p2p/sync/healing/state.rs +++ b/crates/networking/p2p/sync/healing/state.rs @@ -15,7 +15,7 @@ use std::{ time::{Duration, Instant}, }; -use ethrex_common::{H256, constants::EMPTY_KECCACK_HASH, types::AccountState}; +use ethrex_common::{H256, constants::EMPTY_KECCAK_HASH, types::AccountState}; use ethrex_crypto::NativeCrypto; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; use ethrex_storage::Store; @@ -211,7 +211,7 @@ async fn heal_state_trie( H256::from_slice(&meta.path.concat(&node.partial).to_bytes()); // // Collect valid code hash - if account.code_hash != *EMPTY_KECCACK_HASH { + if account.code_hash != *EMPTY_KECCAK_HASH { code_hash_collector.add(account.code_hash); code_hash_collector.flush_if_needed().await?; } diff --git a/crates/networking/p2p/sync/snap_sync.rs b/crates/networking/p2p/sync/snap_sync.rs index 626ad4b15c3..4353f52a616 100644 --- a/crates/networking/p2p/sync/snap_sync.rs +++ b/crates/networking/p2p/sync/snap_sync.rs @@ -15,7 +15,7 @@ use ethrex_blockchain::Blockchain; use ethrex_common::types::{AccountState, BlockHeader, Code}; use ethrex_common::{ H256, - constants::{EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH}, + constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH}, }; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::Store; @@ -928,7 +928,7 @@ pub fn validate_bytecodes(store: Store, state_root: H256) -> bool { .iter_accounts(state_root) .expect("we couldn't iterate over accounts") { - if account_state.code_hash != *EMPTY_KECCACK_HASH + if account_state.code_hash != *EMPTY_KECCAK_HASH && !store .get_account_code(account_state.code_hash) .is_ok_and(|code| code.is_some()) @@ -1012,7 +1012,7 @@ async fn insert_accounts( let code_hashes_from_snapshot: Vec = account_states_snapshot .iter() .filter_map(|(_, state)| { - (state.code_hash != *EMPTY_KECCACK_HASH).then_some(state.code_hash) + (state.code_hash != *EMPTY_KECCAK_HASH).then_some(state.code_hash) }) .collect(); @@ -1132,7 +1132,7 @@ async fn insert_accounts( for account in iter { let account = account.map_err(|err| SyncError::RocksDBError(err.into_string()))?; let account_state = AccountState::decode(&account.1).map_err(SyncError::Rlp)?; - if account_state.code_hash != *EMPTY_KECCACK_HASH { + if account_state.code_hash != *EMPTY_KECCAK_HASH { code_hash_collector.add(account_state.code_hash); code_hash_collector.flush_if_needed().await?; } diff --git a/crates/networking/rpc/types/block.rs b/crates/networking/rpc/types/block.rs index 008046a906f..61346b8d776 100644 --- a/crates/networking/rpc/types/block.rs +++ b/crates/networking/rpc/types/block.rs @@ -120,7 +120,7 @@ mod test { use bytes::Bytes; use ethrex_common::{ Address, Bloom, H256, U256, - constants::EMPTY_KECCACK_HASH, + constants::EMPTY_KECCAK_HASH, types::{EIP1559Transaction, Transaction, TxKind}, }; use std::str::FromStr; @@ -170,7 +170,7 @@ mod test { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*EMPTY_KECCACK_HASH), + requests_hash: Some(*EMPTY_KECCAK_HASH), ..Default::default() }; diff --git a/crates/storage/store.rs b/crates/storage/store.rs index 4888470880c..2550e2db924 100644 --- a/crates/storage/store.rs +++ b/crates/storage/store.rs @@ -778,10 +778,10 @@ impl Store { /// Checks cache first, falls back to database. If metadata is missing, /// falls back to loading full code and extracts length (auto-migration). pub fn get_code_metadata(&self, code_hash: H256) -> Result, StoreError> { - use ethrex_common::constants::EMPTY_KECCACK_HASH; + use ethrex_common::constants::EMPTY_KECCAK_HASH; // Empty code special case - if code_hash == *EMPTY_KECCACK_HASH { + if code_hash == *EMPTY_KECCAK_HASH { return Ok(Some(CodeMetadata { length: 0 })); } diff --git a/crates/vm/backends/levm/db.rs b/crates/vm/backends/levm/db.rs index 560ac831b0c..59e847b670d 100644 --- a/crates/vm/backends/levm/db.rs +++ b/crates/vm/backends/levm/db.rs @@ -1,5 +1,5 @@ use ethrex_common::U256 as CoreU256; -use ethrex_common::constants::EMPTY_KECCACK_HASH; +use ethrex_common::constants::EMPTY_KECCAK_HASH; use ethrex_common::types::{AccountState, Code, CodeMetadata}; use ethrex_common::{Address as CoreAddress, H256 as CoreH256}; use ethrex_levm::db::Database as LevmDatabase; @@ -69,7 +69,7 @@ impl LevmDatabase for DatabaseLogger { } fn get_account_code(&self, code_hash: CoreH256) -> Result { - if code_hash != *EMPTY_KECCACK_HASH { + if code_hash != *EMPTY_KECCAK_HASH { let mut code_accessed = self .code_accessed .lock() diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 6fc2a7079a4..746357bd180 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -11,7 +11,7 @@ use bytes::Bytes; #[cfg(all(feature = "rayon", not(feature = "eip-8025")))] use ethrex_common::H256; #[cfg(all(feature = "rayon", not(feature = "eip-8025")))] -use ethrex_common::constants::EMPTY_KECCACK_HASH; +use ethrex_common::constants::EMPTY_KECCAK_HASH; #[cfg(all(feature = "rayon", not(feature = "eip-8025")))] use ethrex_common::types::Code; #[cfg(all(feature = "rayon", not(feature = "eip-8025")))] @@ -843,10 +843,10 @@ impl LEVM { } // Detect account removal (EIP-161): post-state empty but pre-state existed - let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCACK_HASH; + let post_empty = balance.is_zero() && nonce == 0 && code_hash == *EMPTY_KECCAK_HASH; let pre_empty = prestate.balance.is_zero() && prestate.nonce == 0 - && prestate.code_hash == *EMPTY_KECCACK_HASH; + && prestate.code_hash == *EMPTY_KECCAK_HASH; let removed = post_empty && !pre_empty; let balance_changed = acct_changes @@ -1654,7 +1654,7 @@ impl LEVM { let seeded_hash = if seeded_pos > 0 { let seeded_code = &acct.code_changes[seeded_pos - 1].new_code; if seeded_code.is_empty() { - *EMPTY_KECCACK_HASH + *EMPTY_KECCAK_HASH } else { ethrex_common::utils::keccak(seeded_code) } @@ -1667,7 +1667,7 @@ impl LEVM { store .get_account_state(*addr) .map(|a| a.code_hash) - .unwrap_or(*EMPTY_KECCACK_HASH) + .unwrap_or(*EMPTY_KECCAK_HASH) }) }; if account.info.code_hash != seeded_hash { @@ -1782,7 +1782,7 @@ impl LEVM { if let Some(expected_code) = find_exact_change_code(&acct.code_changes, withdrawal_idx) { let code_hash = if expected_code.is_empty() { - *EMPTY_KECCACK_HASH + *EMPTY_KECCAK_HASH } else { ethrex_common::utils::keccak(expected_code) }; @@ -1932,7 +1932,7 @@ impl LEVM { // Code if !has_exact_change_code(&acct.code_changes, withdrawal_idx) { let seeded_hash = match acct.code_changes.last() { - Some(c) if c.new_code.is_empty() => *EMPTY_KECCACK_HASH, + Some(c) if c.new_code.is_empty() => *EMPTY_KECCAK_HASH, Some(c) => ethrex_common::utils::keccak(&c.new_code), None => { db.store @@ -2146,7 +2146,7 @@ impl LEVM { store .get_account_state(ac.address) .ok() - .filter(|s| s.code_hash != *EMPTY_KECCACK_HASH) + .filter(|s| s.code_hash != *EMPTY_KECCAK_HASH) .map(|s| s.code_hash) }) .collect(); @@ -2940,7 +2940,7 @@ mod bal_tests { AccountState { balance: U256::from(100), nonce: 5, - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, storage_root: H256::zero(), }, ); @@ -2967,7 +2967,7 @@ mod bal_tests { // Last balance entry wins assert_eq!(info.balance, U256::from(80)); assert_eq!(info.nonce, 6); - assert_eq!(info.code_hash, *EMPTY_KECCACK_HASH); + assert_eq!(info.code_hash, *EMPTY_KECCAK_HASH); // Storage let key = ethrex_common::utils::u256_to_h256(U256::from(42)); assert_eq!(*u.added_storage.get(&key).unwrap(), U256::from(999)); @@ -2982,7 +2982,7 @@ mod bal_tests { AccountState { balance: U256::from(1000), nonce: 0, - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, storage_root: H256::zero(), }, ); @@ -3023,7 +3023,7 @@ mod bal_tests { AccountState { balance: U256::from(50), nonce: 1, - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, storage_root: H256::zero(), }, ); diff --git a/crates/vm/backends/levm/tracing.rs b/crates/vm/backends/levm/tracing.rs index a5e316e5abc..bb14a9fdc8b 100644 --- a/crates/vm/backends/levm/tracing.rs +++ b/crates/vm/backends/levm/tracing.rs @@ -1,4 +1,4 @@ -use ethrex_common::constants::EMPTY_KECCACK_HASH; +use ethrex_common::constants::EMPTY_KECCAK_HASH; use ethrex_common::tracing::{PrePostState, PrestateAccountState, PrestateResult, PrestateTrace}; use ethrex_common::types::{Block, Transaction}; use ethrex_common::{ @@ -194,7 +194,7 @@ fn build_account_output( account: &LevmAccount, db: &GeneralizedDatabase, ) -> Result { - let has_code = account.info.code_hash != *EMPTY_KECCACK_HASH; + let has_code = account.info.code_hash != *EMPTY_KECCAK_HASH; let code = if has_code { get_preloaded_code(db, &account.info.code_hash)? } else { @@ -251,7 +251,7 @@ fn build_post_output( modified = true; } if pre_account.info.code_hash != post_account.info.code_hash { - if post_account.info.code_hash != *EMPTY_KECCACK_HASH { + if post_account.info.code_hash != *EMPTY_KECCAK_HASH { state.code_hash = post_account.info.code_hash; state.code = get_preloaded_code(db, &post_account.info.code_hash)?; } @@ -358,7 +358,7 @@ fn preload_touched_codes( .unwrap_or_default(); [post.info.code_hash, pre_hash] }) - .filter(|h| *h != *EMPTY_KECCACK_HASH) + .filter(|h| *h != *EMPTY_KECCAK_HASH) .collect(); for hash in hashes { diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index cd652edb104..e5517495a95 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -2,7 +2,7 @@ use ethrex_common::H256; use ethrex_common::constants::EMPTY_TRIE_HASH; use ethrex_common::types::{AccountState, GenesisAccount}; use ethrex_common::utils::keccak; -use ethrex_common::{U256, constants::EMPTY_KECCACK_HASH, types::AccountInfo}; +use ethrex_common::{U256, constants::EMPTY_KECCAK_HASH, types::AccountInfo}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -107,7 +107,7 @@ impl LevmAccount { } pub fn has_code(&self) -> bool { - self.info.code_hash != *EMPTY_KECCACK_HASH + self.info.code_hash != *EMPTY_KECCAK_HASH } pub fn create_would_collide(&self) -> bool { diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index 1cf69a8e20f..f59d946b7ff 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -94,7 +94,7 @@ pub fn seed_one_address_info_from_bal( // path — iterate-all-keys / bulk-read patterns will see an empty map. let has_all_info = balance_pos > 0 && nonce_pos > 0 && code_pos > 0; if has_all_info { - use ethrex_common::constants::EMPTY_KECCACK_HASH; + use ethrex_common::constants::EMPTY_KECCAK_HASH; let balance = acct_changes .balance_changes .get(balance_pos.saturating_sub(1)) @@ -108,7 +108,7 @@ pub fn seed_one_address_info_from_bal( let code_hash = code_update .as_ref() .map(|(h, _)| *h) - .unwrap_or(*EMPTY_KECCACK_HASH); + .unwrap_or(*EMPTY_KECCAK_HASH); let acc = db .current_accounts_state .entry(addr) @@ -192,9 +192,9 @@ pub fn seed_one_storage_slot_from_bal( /// Compute code hash and optional `Code` object from raw bytecode in a BAL entry. #[cfg(all(feature = "rayon", not(feature = "eip-8025")))] pub fn code_from_bal(new_code: &bytes::Bytes) -> (H256, Option) { - use ethrex_common::constants::EMPTY_KECCACK_HASH; + use ethrex_common::constants::EMPTY_KECCAK_HASH; if new_code.is_empty() { - (*EMPTY_KECCACK_HASH, None) + (*EMPTY_KECCAK_HASH, None) } else { let code_obj = Code::from_bytecode(new_code.clone(), ðrex_crypto::NativeCrypto); let hash = code_obj.hash; @@ -503,10 +503,10 @@ impl GeneralizedDatabase { /// Convenience method to get code length by address (optimized for EXTCODESIZE). pub fn get_code_length(&mut self, address: Address) -> Result { - use ethrex_common::constants::EMPTY_KECCACK_HASH; + use ethrex_common::constants::EMPTY_KECCAK_HASH; let code_hash = self.get_account(address)?.info.code_hash; - if code_hash == *EMPTY_KECCACK_HASH { + if code_hash == *EMPTY_KECCAK_HASH { return Ok(0); } let metadata = self.get_code_metadata(code_hash)?; diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index 739f790e8a6..1aad9c58691 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -54,7 +54,7 @@ impl OpcodeHandler for OpKeccak256Handler { #[cfg(test)] mod tests { use super::*; - use ethrex_common::constants::EMPTY_KECCACK_HASH; + use ethrex_common::constants::EMPTY_KECCAK_HASH; use ethrex_crypto::{Crypto, NativeCrypto}; #[test] @@ -65,11 +65,11 @@ mod tests { #[test] fn empty_keccak_const_matches_common_constant() { - // Guards against drift between this const and `EMPTY_KECCACK_HASH` - // in `ethrex_common::constants` (note: the latter has a typo'd name). + // Guards against drift between this const and `EMPTY_KECCAK_HASH` + // in `ethrex_common::constants`. assert_eq!( EMPTY_KECCAK_U256, - u256_from_big_endian(EMPTY_KECCACK_HASH.as_bytes()) + u256_from_big_endian(EMPTY_KECCAK_HASH.as_bytes()) ); } } diff --git a/test/tests/storage/store_tests.rs b/test/tests/storage/store_tests.rs index 5a9ac5d25f5..42363df8181 100644 --- a/test/tests/storage/store_tests.rs +++ b/test/tests/storage/store_tests.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use ethereum_types::{H256, U256}; use ethrex_common::{ Address, Bloom, H160, - constants::{EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH}, + constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH}, types::{ AccountState, BlockBody, BlockHeader, ChainConfig, Code, Genesis, Receipt, Transaction, TxType, @@ -66,7 +66,7 @@ async fn test_iter_accounts(store: Store) { AccountState { nonce: 2 * i, balance: U256::from(3 * i), - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, storage_root: *EMPTY_TRIE_HASH, }, ) @@ -107,7 +107,7 @@ async fn test_iter_storage(store: Store) { nonce: 1, balance: U256::zero(), storage_root, - code_hash: *EMPTY_KECCACK_HASH, + code_hash: *EMPTY_KECCAK_HASH, } .encode_to_vec(), ) @@ -219,7 +219,7 @@ fn create_block_for_testing() -> (BlockHeader, BlockBody) { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*EMPTY_KECCACK_HASH), + requests_hash: Some(*EMPTY_KECCAK_HASH), ..Default::default() }; let block_body = BlockBody { diff --git a/tooling/archive_sync/src/main.rs b/tooling/archive_sync/src/main.rs index 39cfde5a904..38033f55766 100644 --- a/tooling/archive_sync/src/main.rs +++ b/tooling/archive_sync/src/main.rs @@ -10,7 +10,7 @@ use ethrex_common::utils::keccak; use ethrex_common::{Address, serde_utils}; use ethrex_common::{BigEndianHash, Bytes, H256, U256, types::BlockNumber}; use ethrex_common::{ - constants::{EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH}, + constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH}, types::{AccountState, Block}, }; use ethrex_rlp::decode::RLPDecode; @@ -145,7 +145,7 @@ async fn process_dump(dump: Dump, store: Store, current_root: H256) -> eyre::Res dump_account.get_account_state().encode_to_vec(), )?; // Add code to DB if it is not empty - if dump_account.code_hash != *EMPTY_KECCACK_HASH { + if dump_account.code_hash != *EMPTY_KECCAK_HASH { store .add_account_code(Code::from_bytecode( dump_account.code.clone(), diff --git a/tooling/ef_tests/blockchain/test_runner.rs b/tooling/ef_tests/blockchain/test_runner.rs index 749004f8e0b..a5449abc739 100644 --- a/tooling/ef_tests/blockchain/test_runner.rs +++ b/tooling/ef_tests/blockchain/test_runner.rs @@ -14,7 +14,7 @@ use ethrex_common::types::block_access_list::BlockAccessList; #[cfg(feature = "stateless")] use ethrex_common::types::block_execution_witness::RpcExecutionWitness; use ethrex_common::{ - constants::EMPTY_KECCACK_HASH, + constants::EMPTY_KECCAK_HASH, types::{ Account as CoreAccount, Block as CoreBlock, BlockHeader as CoreBlockHeader, InvalidBlockHeaderError, @@ -461,7 +461,7 @@ async fn check_poststate_against_db(test_key: &str, test: &TestUnit, db: &Store) ); // Check code let code_hash = expected_account.info.code_hash; - if code_hash != *EMPTY_KECCACK_HASH { + if code_hash != *EMPTY_KECCAK_HASH { // We don't want to get account code if there's no code. let db_account_code = db .get_account_code(code_hash) From f43eb322ec3bc94411f55db9bd8800c589da169c Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Wed, 3 Jun 2026 11:13:43 +0200 Subject: [PATCH 4/4] docs: changelog entry for KECCAK256 empty-input fast path --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe09d1b0ab7..41e246e8b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ ## Perf +### 2026-06-03 + +- Short-circuit the `KECCAK256` opcode on zero-length input by returning the precomputed `keccak256("")` constant, skipping the permutation [#6775](https://github.com/lambdaclass/ethrex/pull/6775) + ### 2026-05-27 - Prefetch all BAL storage synchronously before execution [#6732](https://github.com/lambdaclass/ethrex/pull/6732)