From 50741dc320e7cb83f3c285bfca014e37d53ea666 Mon Sep 17 00:00:00 2001 From: "liquan.eth" Date: Thu, 3 Sep 2026 19:21:19 +0800 Subject: [PATCH] perf(core): drop the per-state-read heap allocations in the witness path `WitnessDatabase` reads go through salt's `find()` with in-place decoding and stack-allocated key encodings; `LightWitness::metadata` stops cloning the `SaltValue` it only reads. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY --- crates/stateless-core/src/data_types.rs | 45 +++++++++++++++++++--- crates/stateless-core/src/evm_database.rs | 28 ++++++++------ crates/stateless-core/src/light_witness.rs | 2 +- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/crates/stateless-core/src/data_types.rs b/crates/stateless-core/src/data_types.rs index 47905aa3..4a859424 100644 --- a/crates/stateless-core/src/data_types.rs +++ b/crates/stateless-core/src/data_types.rs @@ -26,7 +26,7 @@ use std::{collections::BTreeMap, vec::Vec}; pub use alloy_primitives::Bytes; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256, FixedBytes, U256}; use revm::primitives::KECCAK_EMPTY; use salt::{SaltKey, SaltValue}; @@ -67,14 +67,33 @@ impl PlainKey { /// - Unknown: preserved raw bytes from decode pub fn encode(&self) -> Vec { match self { - PlainKey::Account(addr) => addr.as_slice().to_vec(), - PlainKey::Storage(addr, slot) => { - addr.concat_const::(*slot).as_slice().to_vec() - } + PlainKey::Account(addr) => Self::account_key_bytes(addr).to_vec(), + PlainKey::Storage(addr, slot) => Self::storage_key_bytes(*addr, *slot).to_vec(), PlainKey::Unknown(data) => data.clone(), } } + /// Encoding of an account key — the raw address bytes. + /// + /// Same bytes as `PlainKey::Account(address).encode()` without the heap allocation, + /// for per-state-read hot paths. + #[inline] + pub(crate) fn account_key_bytes(address: &Address) -> &[u8] { + address.as_slice() + } + + /// Stack-allocated encoding of a storage-slot key — address (20) ++ slot (32). + /// + /// Same bytes as `PlainKey::Storage(address, slot).encode()` without the heap + /// allocation, for per-state-read hot paths. + #[inline] + pub(crate) fn storage_key_bytes( + address: Address, + slot: B256, + ) -> FixedBytes { + address.concat_const::(slot) + } + /// Decodes a byte slice into a PlainKey. /// /// Returns `PlainKey::Unknown` if the buffer length is neither 20 (account) @@ -239,6 +258,22 @@ mod tests { entries.into_iter().enumerate().map(|(i, v)| (SaltKey::from((0u32, i as u64)), v)).collect() } + /// The allocation-free encoders the witness read path uses must produce exactly what + /// `encode()` produces — `encode()` delegates to them today, and this pins that contract + /// against a future edit that stops delegating and lets the two drift into looking up + /// different keys. + #[test] + fn stack_key_encodings_match_encode() { + let addr = Address::from([0x11; 20]); + assert_eq!(PlainKey::account_key_bytes(&addr), PlainKey::Account(addr).encode().as_slice()); + for slot in [B256::ZERO, B256::from([0xAB; 32]), B256::from(U256::from(7))] { + assert_eq!( + PlainKey::storage_key_bytes(addr, slot).as_slice(), + PlainKey::Storage(addr, slot).encode().as_slice(), + ); + } + } + #[test] fn test_plain_key_round_trip() { let addr = Address::from([0xAB; 20]); diff --git a/crates/stateless-core/src/evm_database.rs b/crates/stateless-core/src/evm_database.rs index 5927c3d7..1618a1ec 100644 --- a/crates/stateless-core/src/evm_database.rs +++ b/crates/stateless-core/src/evm_database.rs @@ -8,7 +8,6 @@ use std::{ collections::BTreeMap, format, string::{String, ToString}, - vec::Vec, }; use alloy_consensus::Header; @@ -79,10 +78,16 @@ where W: StateReader, W::Error: core::fmt::Display, { - /// Get value from witness for the given plain key - fn plain_value(&self, plain_key: &[u8]) -> Result>, WitnessDatabaseError> { + /// Get the witness entry for the given plain key. + /// + /// Returns the whole `SaltValue` (an inline array) instead of going through salt's + /// `plain_value()`, which heap-allocates a copy of the value bytes on every read — + /// this lookup runs once per unique account/slot touched during block replay. + /// Callers decode from `.value()` in place. + fn find(&self, plain_key: &[u8]) -> Result, WitnessDatabaseError> { EphemeralSaltState::new(self.witness) - .plain_value(plain_key) + .find(plain_key) + .map(|found| found.map(|(_, salt_value)| salt_value)) .map_err(|e| WitnessDatabaseError(e.to_string())) } } @@ -98,9 +103,9 @@ where fn basic_ref(&self, address: Address) -> Result, Self::Error> { trace!(?address, "basic_ref"); - let raw_value = self.plain_value(&PlainKey::Account(address).encode())?; + let salt_value = self.find(PlainKey::account_key_bytes(&address))?; - match raw_value.and_then(|v| match PlainValue::decode(&v) { + match salt_value.and_then(|v| match PlainValue::decode(v.value()) { PlainValue::Account(acc) => Some(acc), _ => None, }) { @@ -134,10 +139,11 @@ where fn storage_ref(&self, address: Address, index: U256) -> Result { trace!(?address, index = %format_args!("{:#x}", index), "storage_ref"); - let raw_value = self.plain_value(&PlainKey::Storage(address, index.into()).encode())?; + let salt_value = + self.find(PlainKey::storage_key_bytes(address, index.into()).as_slice())?; - Ok(raw_value - .and_then(|v| match PlainValue::decode(&v) { + Ok(salt_value + .and_then(|v| match PlainValue::decode(v.value()) { PlainValue::Storage(value) => Some(value), _ => None, }) @@ -298,11 +304,11 @@ impl SaltEnv for WitnessExternalEnv { } fn bucket_id_for_account(account: Address) -> BucketId { - hasher::bucket_id(&PlainKey::Account(account).encode()) + hasher::bucket_id(PlainKey::account_key_bytes(&account)) } fn bucket_id_for_slot(address: Address, key: U256) -> BucketId { - hasher::bucket_id(&PlainKey::Storage(address, key.into()).encode()) + hasher::bucket_id(PlainKey::storage_key_bytes(address, key.into()).as_slice()) } } diff --git a/crates/stateless-core/src/light_witness.rs b/crates/stateless-core/src/light_witness.rs index 973a03e6..244b450e 100644 --- a/crates/stateless-core/src/light_witness.rs +++ b/crates/stateless-core/src/light_witness.rs @@ -180,7 +180,7 @@ impl StateReader for LightWitness { fn metadata(&self, bucket_id: BucketId) -> Result { let metadata_key = bucket_metadata_key(bucket_id); match self.kvs.get(&metadata_key) { - Some(Some(salt_value)) => BucketMeta::try_from(salt_value.clone()) + Some(Some(salt_value)) => BucketMeta::try_from(salt_value) .map_err(|_| LightWitnessError { message: "Failed to decode metadata" }), // A well-formed witness never maps a metadata key to a deletion, // but witness bytes are network input (and the light decode