Skip to content
Merged
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
7 changes: 2 additions & 5 deletions bin/debug-trace-server/src/chain_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,10 @@ mod tests {
// Minimal mock for test compilation
struct MockBlockStore;
impl stateless_core::ContractStore for MockBlockStore {
fn get_contracts(
&self,
_: &[B256],
) -> StoreResult<(HashMap<B256, Arc<Bytecode>>, Vec<B256>)> {
fn get_contracts(&self, _: &[B256]) -> StoreResult<(HashMap<B256, Bytecode>, Vec<B256>)> {
Ok((Default::default(), vec![]))
}
fn add_contracts(&self, _: &[(B256, Arc<Bytecode>)]) -> StoreResult<()> {
fn add_contracts(&self, _: &[(B256, Bytecode)]) -> StoreResult<()> {
Ok(())
}
}
Expand Down
18 changes: 8 additions & 10 deletions bin/debug-trace-server/src/data_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ pub struct BlockData {
/// Light witness without expensive EC point validation.
pub witness: LightWitness,
/// Contract bytecodes keyed by code hash, required for EVM execution.
/// Values share allocations with the `ContractCache`.
pub contracts: HashMap<B256, Arc<Bytecode>>,
/// `Bytecode` is internally reference-counted, so values share their underlying allocation
/// with the `ContractCache` (and across `BlockData` clones) via cheap refcount-bump clones.
pub contracts: HashMap<B256, Bytecode>,
}

/// Default timeout for a user-facing witness fetch in seconds (8 seconds).
Expand Down Expand Up @@ -554,7 +555,7 @@ impl DataProvider {
&self,
code_hashes: &[B256],
deadline: Instant,
) -> DataProviderResult<HashMap<B256, Arc<Bytecode>>> {
) -> DataProviderResult<HashMap<B256, Bytecode>> {
resolve_contracts_inner(&self.rpc_client, &self.contract_cache, code_hashes, deadline).await
}
}
Expand Down Expand Up @@ -753,7 +754,7 @@ async fn resolve_contracts_inner(
contract_cache: &ContractCache,
code_hashes: &[B256],
deadline: Instant,
) -> DataProviderResult<HashMap<B256, Arc<Bytecode>>> {
) -> DataProviderResult<HashMap<B256, Bytecode>> {
let (mut contracts, missing) = contract_cache.get(code_hashes)?;

if missing.is_empty() {
Expand All @@ -771,7 +772,7 @@ async fn resolve_contracts_inner(
// `TraceRpcMetrics` adapter inside `round_robin_with_backoff`.
let fetched = rpc_client.get_codes_with_deadline(&missing, true, Some(deadline)).await?;

let new_contracts: Vec<(B256, Arc<Bytecode>)> = fetched.into_iter().collect();
let new_contracts: Vec<(B256, Bytecode)> = fetched.into_iter().collect();

// Write-through: memory always, disk in local-cache mode. We don't fail the trace on
// cache-insert errors; the request has already been served.
Expand All @@ -791,14 +792,11 @@ async fn resolve_contracts_inner(
pub(crate) struct NoopContractStore;

impl ContractStore for NoopContractStore {
fn get_contracts(
&self,
hashes: &[B256],
) -> StoreResult<(HashMap<B256, Arc<Bytecode>>, Vec<B256>)> {
fn get_contracts(&self, hashes: &[B256]) -> StoreResult<(HashMap<B256, Bytecode>, Vec<B256>)> {
Ok((HashMap::default(), hashes.to_vec()))
}

fn add_contracts(&self, _codes: &[(B256, Arc<Bytecode>)]) -> StoreResult<()> {
fn add_contracts(&self, _codes: &[(B256, Bytecode)]) -> StoreResult<()> {
Ok(())
}
}
Expand Down
12 changes: 4 additions & 8 deletions bin/debug-trace-server/src/server_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Provides persistent storage of block data, witnesses, and canonical chain state
//! for serving `debug_*` and `trace_*` RPC methods.

use std::{path::Path, sync::Arc};
use std::path::Path;

use alloy_primitives::{B256, BlockHash, BlockNumber, map::HashMap};
use alloy_rpc_types_eth::Block;
Expand Down Expand Up @@ -218,14 +218,11 @@ impl ServerDB {
}

impl ContractStore for ServerDB {
fn get_contracts(
&self,
hashes: &[B256],
) -> StoreResult<(HashMap<B256, Arc<Bytecode>>, Vec<B256>)> {
fn get_contracts(&self, hashes: &[B256]) -> StoreResult<(HashMap<B256, Bytecode>, Vec<B256>)> {
read_contracts(&self.database, hashes)
}

fn add_contracts(&self, codes: &[(B256, Arc<Bytecode>)]) -> StoreResult<()> {
fn add_contracts(&self, codes: &[(B256, Bytecode)]) -> StoreResult<()> {
write_add_contracts(&self.database, codes)
}
}
Expand Down Expand Up @@ -352,8 +349,7 @@ mod tests {

let hash1 = B256::from([1u8; 32]);
let hash2 = B256::from([2u8; 32]);
let bytecode =
Arc::new(Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00])));
let bytecode = Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00]));

ContractStore::add_contracts(&db, &[(hash1, bytecode.clone())]).unwrap();

Expand Down
12 changes: 5 additions & 7 deletions bin/debug-trace-server/src/tracing_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
//! ## Parity-style (trace_* methods)
//! - `LocalizedTransactionTrace` - Flat call traces with block/tx context

use std::sync::Arc;

use alloy_consensus::Transaction;
use alloy_evm::{Evm as EvmTrait, block::BlockExecutor};
use alloy_op_evm::block::OpAlloyReceiptBuilder;
Expand Down Expand Up @@ -167,7 +165,7 @@ impl<'a> TracingEnv<'a> {

fn create_witness_db<'b>(
&'b self,
contracts: &'b HashMap<B256, Arc<Bytecode>>,
contracts: &'b HashMap<B256, Bytecode>,
) -> WitnessDatabase<'b, LightWitnessExecutor> {
WitnessDatabase { header: self.header, witness: &self.light_witness_executor, contracts }
}
Expand Down Expand Up @@ -444,7 +442,7 @@ pub fn trace_block(
chain_spec: &ChainSpec,
block: &Block<OpTransaction>,
witness: LightWitness,
contracts: &HashMap<B256, Arc<Bytecode>>,
contracts: &HashMap<B256, Bytecode>,
opts: GethDebugTracingOptions,
) -> Result<Vec<TraceResult>, ValidationError> {
let env = TracingEnv::new(chain_spec, block, witness)?;
Expand Down Expand Up @@ -710,7 +708,7 @@ pub fn trace_transaction(
block: &Block<OpTransaction>,
tx_index: usize,
light_witness: LightWitness,
contracts: &HashMap<B256, Arc<Bytecode>>,
contracts: &HashMap<B256, Bytecode>,
opts: GethDebugTracingOptions,
) -> Result<GethTrace, ValidationError> {
let env = TracingEnv::new(chain_spec, block, light_witness)?;
Expand Down Expand Up @@ -877,7 +875,7 @@ pub fn parity_trace_block(
chain_spec: &ChainSpec,
block: &Block<OpTransaction>,
light_witness: LightWitness,
contracts: &HashMap<B256, Arc<Bytecode>>,
contracts: &HashMap<B256, Bytecode>,
) -> Result<Vec<LocalizedTransactionTrace>, ValidationError> {
let env = TracingEnv::new(chain_spec, block, light_witness)?;

Expand Down Expand Up @@ -927,7 +925,7 @@ pub fn parity_trace_transaction(
block: &Block<OpTransaction>,
tx_index: usize,
light_witness: LightWitness,
contracts: &HashMap<B256, Arc<Bytecode>>,
contracts: &HashMap<B256, Bytecode>,
) -> Result<Vec<LocalizedTransactionTrace>, ValidationError> {
let env = TracingEnv::new(chain_spec, block, light_witness)?;

Expand Down
2 changes: 1 addition & 1 deletion bin/stateless-validator/src/chain_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl BlockProcessor for ValidatorProcessor {
}
})?;

let new_bytecodes: Vec<(B256, Arc<Bytecode>)> = fetched.into_iter().collect();
let new_bytecodes: Vec<(B256, Bytecode)> = fetched.into_iter().collect();

self.contract_cache
.insert(&new_bytecodes)
Expand Down
21 changes: 7 additions & 14 deletions bin/stateless-validator/src/validator_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! CANONICAL_CHAIN is bounded to `max_chain_length` entries; older entries are
//! pruned inline during [`ChainStore::advance_chain`].

use std::{path::Path, sync::Arc};
use std::path::Path;

use alloy_genesis::Genesis;
use alloy_primitives::{B256, BlockHash, BlockNumber, map::HashMap};
Expand Down Expand Up @@ -73,14 +73,11 @@ impl ValidatorDB {
}

impl ContractStore for ValidatorDB {
fn get_contracts(
&self,
hashes: &[B256],
) -> StoreResult<(HashMap<B256, Arc<Bytecode>>, Vec<B256>)> {
fn get_contracts(&self, hashes: &[B256]) -> StoreResult<(HashMap<B256, Bytecode>, Vec<B256>)> {
read_contracts(&self.database, hashes)
}

fn add_contracts(&self, codes: &[(B256, Arc<Bytecode>)]) -> StoreResult<()> {
fn add_contracts(&self, codes: &[(B256, Bytecode)]) -> StoreResult<()> {
write_add_contracts(&self.database, codes)
}
}
Expand Down Expand Up @@ -227,10 +224,8 @@ mod tests {
let hash2 = B256::from([2u8; 32]);
let hash3 = B256::from([3u8; 32]);

let bytecode1 =
Arc::new(Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00])));
let bytecode2 =
Arc::new(Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x01])));
let bytecode1 = Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00]));
let bytecode2 = Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x01]));

store.add_contracts(&[(hash1, bytecode1.clone()), (hash2, bytecode2.clone())]).unwrap();

Expand Down Expand Up @@ -282,8 +277,7 @@ mod tests {
let cache = ContractCache::new(Arc::new(store));

let hash = B256::from([1u8; 32]);
let bytecode =
Arc::new(Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00])));
let bytecode = Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00]));

cache.insert(&[(hash, bytecode.clone())]).unwrap();

Expand All @@ -299,8 +293,7 @@ mod tests {
let db_path = dir.path().join("test.redb");

let hash = B256::from([1u8; 32]);
let bytecode =
Arc::new(Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00])));
let bytecode = Bytecode::new_raw(alloy_primitives::Bytes::from_static(&[0x60, 0x00]));

{
let store = ValidatorDB::new(&db_path).unwrap();
Expand Down
28 changes: 14 additions & 14 deletions crates/stateless-common/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,15 +654,15 @@ impl RpcClient {
///
/// # Return type
///
/// Returns `Arc<Bytecode>` so the value shares one allocation with the
/// [`crate::ContractCache`] end-to-end. Callers that feed into the verified
/// `ContractCache` tiers should pass `verify = true`; the cache itself trusts memory/disk
/// hits and does not re-verify.
/// Returns plain `Bytecode`: it is already internally reference-counted, so it shares one
/// underlying allocation with the [`crate::ContractCache`] end-to-end without an outer `Arc`.
/// Callers that feed into the verified `ContractCache` tiers should pass `verify = true`; the
/// cache itself trusts memory/disk hits and does not re-verify.
pub async fn get_codes(
&self,
hashes: &[B256],
verify: bool,
) -> std::result::Result<HashMap<B256, Arc<Bytecode>>, CodeFetchError> {
) -> std::result::Result<HashMap<B256, Bytecode>, CodeFetchError> {
self.get_codes_with_deadline(hashes, verify, None).await
}

Expand All @@ -677,7 +677,7 @@ impl RpcClient {
hashes: &[B256],
verify: bool,
deadline: Option<Instant>,
) -> std::result::Result<HashMap<B256, Arc<Bytecode>>, CodeFetchError> {
) -> std::result::Result<HashMap<B256, Bytecode>, CodeFetchError> {
// `try_join_all` cancels the remaining per-hash futures on the first error — a
// `VerificationFailure` or `Deadline` on one hash stops the rest of the batch
// immediately and releases their concurrency permits, so a slow straggler can't
Expand All @@ -691,7 +691,7 @@ impl RpcClient {
return Err(CodeFetchError::VerificationFailure { requested: hash, actual });
}
}
Ok::<_, CodeFetchError>((hash, Arc::new(code)))
Ok::<_, CodeFetchError>((hash, code))
}))
.await
.map(|pairs| pairs.into_iter().collect())
Expand Down Expand Up @@ -1655,10 +1655,10 @@ mod tests {
handle.stop().unwrap();
}

/// All-good batch: verified entries are returned wrapped in `Arc<Bytecode>` (the type
/// change that lets `ContractCache::insert` avoid a second allocation at each call site).
/// All-good batch: verified entries are returned as plain `Bytecode` (already internally
/// reference-counted, so `ContractCache::insert` shares the allocation without a second wrap).
#[tokio::test]
async fn test_get_codes_verify_ok_returns_arc_bytecode() {
async fn test_get_codes_verify_ok_returns_bytecode() {
let good_code = Bytes::from_static(&[0x60, 0x00, 0x60, 0x01]);
let good_hash = Bytecode::new_raw(good_code.clone()).hash_slow();

Expand All @@ -1667,10 +1667,10 @@ mod tests {

let ok = client.get_codes(&[good_hash], true).await.unwrap();
assert_eq!(ok.len(), 1);
let arc = ok.get(&good_hash).expect("good hash present");
assert_eq!(arc.bytes_slice(), Bytecode::new_raw(good_code).bytes_slice());
// Compile-time check that the return type is `Arc<Bytecode>` (refcount share with cache).
let _arc_clone: Arc<Bytecode> = Arc::clone(arc);
let code = ok.get(&good_hash).expect("good hash present");
assert_eq!(code.bytes_slice(), Bytecode::new_raw(good_code).bytes_slice());
// Compile-time check that the return type is plain `Bytecode` (a cheap refcount clone).
let _code_clone: Bytecode = code.clone();

handle.stop().unwrap();
}
Expand Down
13 changes: 7 additions & 6 deletions crates/stateless-core/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! Concrete implementations live in their respective binaries;
//! shared redb helpers live in the `stateless-db` crate.

use std::{boxed::Box, fmt, string::String, sync::Arc, vec::Vec};
use std::{boxed::Box, fmt, string::String, vec::Vec};

use alloy_primitives::{B256, BlockHash, BlockNumber, map::HashMap};
use revm::state::Bytecode;
Expand Down Expand Up @@ -86,16 +86,17 @@ impl fmt::Display for MissingDataKind {
}

/// Result of a contract bytecode lookup: `(found, missing)`.
pub type ContractLookup = (HashMap<B256, Arc<Bytecode>>, Vec<B256>);
pub type ContractLookup = (HashMap<B256, Bytecode>, Vec<B256>);

/// Contract bytecode persistence.
///
/// Values are exchanged as `Arc<Bytecode>` so the in-memory `ContractCache` and
/// downstream consumers (e.g. revm execution via `WitnessDatabase`) can share a
/// single allocation instead of deep-cloning bytecode on every cache hit.
/// Values are exchanged as plain `Bytecode`, not `Arc<Bytecode>`: `Bytecode` is already
/// internally reference-counted (its `Bytes` buffer and `JumpTable` are both `Arc`-backed),
/// so cloning is an O(1) refcount bump that shares the same allocation. An outer `Arc` would
/// only add a redundant layer of indirection and a second heap allocation per contract.
pub trait ContractStore: Send + Sync {
fn get_contracts(&self, hashes: &[B256]) -> StoreResult<ContractLookup>;
fn add_contracts(&self, codes: &[(B256, Arc<Bytecode>)]) -> StoreResult<()>;
fn add_contracts(&self, codes: &[(B256, Bytecode)]) -> StoreResult<()>;
}

/// Chain-cursor management — the storage surface the pipeline drives on **every** scenario.
Expand Down
Loading
Loading