diff --git a/bin/debug-trace-server/src/block_data_cache.rs b/bin/debug-trace-server/src/block_data_cache.rs index 0a55dada..e33d6f0a 100644 --- a/bin/debug-trace-server/src/block_data_cache.rs +++ b/bin/debug-trace-server/src/block_data_cache.rs @@ -81,7 +81,8 @@ const AUTHORIZATION_BYTES: u64 = 192; /// which quick_cache re-invokes on admission, promotion, and eviction, under a shard /// lock — never re-walks the payload. pub fn block_data_weight(data: &BlockData) -> u64 { - let witness = light_witness_memory_bytes(&data.witness) as u64; + let witness = (light_witness_memory_bytes(data.witness.light_witness()) + + data.witness.lookup_table_memory_bytes()) as u64; let contracts: u64 = data.contracts.values().map(bytecode_weight).sum(); let block = BLOCK_FIXED_OVERHEAD + match &data.block.transactions { @@ -246,7 +247,8 @@ mod tests { #[test] fn weigher_charges_witness_contracts_and_transactions() { let data = fixture_block_data(); - let witness_bytes = light_witness_memory_bytes(&data.witness) as u64; + let witness_bytes = (light_witness_memory_bytes(data.witness.light_witness()) + + data.witness.lookup_table_memory_bytes()) as u64; let contract_bytes: u64 = data.contracts.values().map(bytecode_weight).sum(); let weight = block_data_weight(&data); diff --git a/bin/debug-trace-server/src/data_provider.rs b/bin/debug-trace-server/src/data_provider.rs index 563da94c..cc9389b3 100644 --- a/bin/debug-trace-server/src/data_provider.rs +++ b/bin/debug-trace-server/src/data_provider.rs @@ -46,7 +46,8 @@ use quick_cache::sync::Cache; use revm::state::Bytecode; use stateless_common::{CodeFetchError, RpcClient, RpcDeadlineExceeded, WitnessSizeBreakdown}; use stateless_core::{ - ContractStore, LightWitness, StoreResult, db::StoreError, withdrawals::MptWitness, + ContractStore, LightWitness, LightWitnessExecutor, StoreResult, db::StoreError, + withdrawals::MptWitness, }; use stateless_db::ContractCache; use tracing::{debug, instrument, trace, warn}; @@ -110,8 +111,11 @@ impl WitnessFetchConfig { pub struct BlockData { /// The block with full transaction data. pub block: Block, - /// Light witness without expensive EC point validation. - pub witness: LightWitness, + /// The witness wrapped in its execution form. Built once per block here, so every + /// trace request served from this `BlockData` shares one witness and one + /// direct-lookup table instead of deep-cloning the maps and rebuilding the table + /// per request. + pub witness: LightWitnessExecutor, /// Contract bytecodes keyed by code hash, required for EVM execution. /// `Bytecode` is internally reference-counted, so values share their underlying allocation /// with the `ContractCache` (and across `BlockData` clones) via cheap refcount-bump clones. @@ -835,7 +839,7 @@ impl DataProvider { ); } - Ok(Some(BlockData { block, witness, contracts })) + Ok(Some(BlockData { block, witness: LightWitnessExecutor::from(witness), contracts })) } /// Single-flight fetch via [`futures::future::Shared`]: concurrent callers for the same @@ -1045,7 +1049,7 @@ async fn do_fetch_block_data( ); } - Ok(BlockData { block, witness, contracts }) + Ok(BlockData { block, witness: LightWitnessExecutor::from(witness), contracts }) } /// Reads the local DB's canonical tip height, `None` when no DB is configured or the tip is @@ -1316,7 +1320,7 @@ pub(crate) mod test_support { let witness = LightWitness::from(&fixtures.salt_witnesses[&hash]); let contracts: HashMap = fixtures.contracts.iter().map(|(h, code)| (*h, code.clone())).collect(); - BlockData { block, witness, contracts } + BlockData { block, witness: LightWitnessExecutor::from(witness), contracts } } /// Minimal self-consistent RPC `Header` for `number`: `hash` is the real `hash_slow()` @@ -1463,6 +1467,7 @@ mod tests { /// code hash the witness references, so DB-served fetches never fall through to RPC. fn fixture_block_and_cache() -> (Block, LightWitness, Arc) { let BlockData { block, witness, contracts } = test_support::fixture_block_data(); + let witness = witness.light_witness().clone(); let contract_cache = test_support::noop_contract_cache(); let codes: Vec<(B256, Bytecode)> = crate::tracing_executor::extract_code_hashes(&witness) .into_iter() @@ -1555,7 +1560,10 @@ mod tests { let (block, witness, contract_cache) = fixture_block_and_cache(); let hash = block.header.hash; let cache = Arc::new(BlockDataCache::new(1024 * 1024 * 1024)); - cache.insert(hash, Arc::new(BlockData { block, witness, contracts: HashMap::default() })); + cache.insert( + hash, + Arc::new(BlockData { block, witness: witness.into(), contracts: HashMap::default() }), + ); let provider = provider_with_tiers(&test_support::hanging_url(), None, Some(cache), contract_cache); diff --git a/bin/debug-trace-server/src/rpc_service.rs b/bin/debug-trace-server/src/rpc_service.rs index be9d3711..5160d067 100644 --- a/bin/debug-trace-server/src/rpc_service.rs +++ b/bin/debug-trace-server/src/rpc_service.rs @@ -299,7 +299,7 @@ impl RpcContext { crate::tracing_executor::trace_block( &self.chain_spec, &data.block, - data.witness.clone(), + &data.witness, &data.contracts, opts, ) @@ -633,7 +633,7 @@ impl DebugTraceRpcServer for RpcContext { &self.chain_spec, &data.block, tx_index, - data.witness.clone(), + &data.witness, &data.contracts, opts, ) @@ -715,7 +715,7 @@ impl TraceRpcServer for RpcContext { crate::tracing_executor::parity_trace_block( &self.chain_spec, &data.block, - data.witness.clone(), + &data.witness, &data.contracts, ) }) @@ -762,7 +762,7 @@ impl TraceRpcServer for RpcContext { &self.chain_spec, &data.block, tx_index, - data.witness.clone(), + &data.witness, &data.contracts, ) .map_err(|e| { @@ -1055,7 +1055,7 @@ mod tests { // Data-attributable: the same block cached with a witness that cannot replay it. let mut data = fixture_block_data(); - data.witness = empty_light_witness(); + data.witness = empty_light_witness().into(); let left = entries_left_after_failed_trace(chain_spec, data, None).await; assert_eq!(left, 0, "a data error must evict the poisoned entry"); } diff --git a/bin/debug-trace-server/src/tracing_executor.rs b/bin/debug-trace-server/src/tracing_executor.rs index e5f96f6a..1b3b91ad 100644 --- a/bin/debug-trace-server/src/tracing_executor.rs +++ b/bin/debug-trace-server/src/tracing_executor.rs @@ -108,23 +108,23 @@ struct TracingEnv<'a> { >, block_ctx: MegaBlockExecutionCtx, evm_env: alloy_evm::EvmEnv, - light_witness_executor: LightWitnessExecutor, + light_witness_executor: &'a LightWitnessExecutor, } impl<'a> TracingEnv<'a> { fn new( chain_spec: &ChainSpec, block: &'a Block, - light_witness: LightWitness, + witness: &'a LightWitnessExecutor, ) -> Result { let BlockTransactions::Full(transactions) = &block.transactions else { return Err(ValidationError::BlockIncomplete); }; - let ext_env = WitnessExternalEnv::from_light_witness(&light_witness, block.header.number) - .map_err(ValidationError::EnvOracleConstructionFailed)?; + let ext_env = + WitnessExternalEnv::from_light_witness(witness.light_witness(), block.header.number) + .map_err(ValidationError::EnvOracleConstructionFailed)?; - let light_witness_executor = LightWitnessExecutor::from(light_witness); let evm_env = create_evm_env(&block.header.inner, chain_spec); let evm_factory = MegaEvmFactory::new().with_external_env_factory(ext_env); @@ -155,7 +155,7 @@ impl<'a> TracingEnv<'a> { executor_factory, block_ctx, evm_env, - light_witness_executor, + light_witness_executor: witness, }) } @@ -163,7 +163,7 @@ impl<'a> TracingEnv<'a> { &'b self, contracts: &'b HashMap, ) -> WitnessDatabase<'b, LightWitnessExecutor> { - WitnessDatabase { header: self.header, witness: &self.light_witness_executor, contracts } + WitnessDatabase { header: self.header, witness: self.light_witness_executor, contracts } } } @@ -490,7 +490,7 @@ fn trace_tx_with_tracing_inspector( pub fn trace_block( chain_spec: &ChainSpec, block: &Block, - witness: LightWitness, + witness: &LightWitnessExecutor, contracts: &HashMap, opts: GethDebugTracingOptions, ) -> Result, TraceError> { @@ -703,7 +703,7 @@ pub fn trace_transaction( chain_spec: &ChainSpec, block: &Block, tx_index: usize, - light_witness: LightWitness, + light_witness: &LightWitnessExecutor, contracts: &HashMap, opts: GethDebugTracingOptions, ) -> Result { @@ -826,7 +826,7 @@ pub fn trace_transaction( pub fn parity_trace_block( chain_spec: &ChainSpec, block: &Block, - light_witness: LightWitness, + light_witness: &LightWitnessExecutor, contracts: &HashMap, ) -> Result, TraceError> { let env = TracingEnv::new(chain_spec, block, light_witness)?; @@ -877,7 +877,7 @@ pub fn parity_trace_transaction( chain_spec: &ChainSpec, block: &Block, tx_index: usize, - light_witness: LightWitness, + light_witness: &LightWitnessExecutor, contracts: &HashMap, ) -> Result, TraceError> { let env = TracingEnv::new(chain_spec, block, light_witness)?; @@ -1058,7 +1058,7 @@ mod tests { let err = trace_block( &chain_spec, &block, - witness.clone(), + &witness, &HashMap::default(), GethDebugTracingOptions::default(), ) @@ -1070,7 +1070,7 @@ mod tests { tracer_config: GethDebugTracerConfig(serde_json::json!({"bogusTracer": {}})), ..Default::default() }; - let err = trace_block(&chain_spec, &block, witness, &contracts, opts) + let err = trace_block(&chain_spec, &block, &witness, &contracts, opts) .expect_err("an unparsable mux config must be rejected"); assert!(matches!(err, TraceError::Request(_)), "got: {err:?}"); } @@ -1107,14 +1107,14 @@ mod tests { ..Default::default() }; - let err = trace_block(&chain_spec, &block, witness.clone(), &contracts, opts.clone()) + let err = trace_block(&chain_spec, &block, &witness, &contracts, opts.clone()) .expect_err("malformed config must fail the block trace"); assert!( matches!(&err, TraceError::Request(msg) if msg.contains("tracerConfig")), "got: {err:?}" ); - let err = trace_transaction(&chain_spec, &block, 0, witness.clone(), &contracts, opts) + let err = trace_transaction(&chain_spec, &block, 0, &witness, &contracts, opts) .expect_err("malformed config must fail the tx trace"); assert!( matches!(&err, TraceError::Request(msg) if msg.contains("tracerConfig")), @@ -1130,7 +1130,7 @@ mod tests { tracer_config: GethDebugTracerConfig(serde_json::json!({"onlyTopCall": true})), ..Default::default() }; - trace_block(&chain_spec, &block, witness, &contracts, opts) + trace_block(&chain_spec, &block, &witness, &contracts, opts) .expect("a well-formed config must trace"); } } diff --git a/crates/stateless-core/src/light_witness.rs b/crates/stateless-core/src/light_witness.rs index dd9c0dda..b52de9fe 100644 --- a/crates/stateless-core/src/light_witness.rs +++ b/crates/stateless-core/src/light_witness.rs @@ -299,6 +299,19 @@ impl LightWitnessExecutor { pub fn kvs(&self) -> &BTreeMap> { &self.light_witness.kvs } + + /// The wrapped witness. + pub fn light_witness(&self) -> &LightWitness { + &self.light_witness + } + + /// Estimated heap bytes of the direct-lookup table, in the same + /// per-entry-term style as `light_witness_memory_bytes`: the plain-key + /// bytes plus the map entry (key header, `SaltKey`). + pub fn lookup_table_memory_bytes(&self) -> usize { + let entry = size_of::>() + size_of::(); + self.direct_lookup_tbl.keys().map(|k| k.len() + entry).sum() + } } #[cfg(test)]