Skip to content

Commit 1dd3fe6

Browse files
vbuilder69420claude
andcommitted
perf: replace AccessListInspector with post-execution state-diff blocklist check
Remove the AccessListInspector entirely from RBuilderEVMInspector. Replace the per-opcode blocklist tracking with a post-execution check against ResultAndState.state (EvmState = HashMap<Address, Account>), which already contains every address touched during EVM execution. The AccessListInspector called step() on every EVM opcode to build an access list, solely used to check addresses against the blocklist. Profiling showed this inspector overhead consumed ~52% of CPU time. The EVM execution result already contains the same information in its state diff, making the inspector entirely redundant. Changes: - order_commit.rs: Use create_evm() (NoOpInspector) when no used_state_tracer is needed. Check blocklist via res.state.keys() after execution instead of via access list. - evm_inspector.rs: Remove AccessListInspector from RBuilderEVMInspector. The inspector now only wraps the optional UsedStateEVMInspector (used by parallel builder / EVM caching). This optimization works regardless of whether a blocklist is configured. Benchmark (builder-lab, 100 TPS, seed=42, 60s profiling window): | Metric | Before | After | Change | |---------------------|----------|----------|--------| | Block fill p50 | 96.8ms | 58.9ms | -39% | | Block fill p95 | 129.2ms | 87.1ms | -33% | | E2E latency p50 | 98ms | 61ms | -38% | | E2E latency p95 | 134ms | 92ms | -31% | | Blocks submitted | 255 | 342 | +34% | | Txs included | 17,882 | 23,449 | +31% | Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c66f845 commit 1dd3fe6

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

crates/rbuilder-primitives/src/evm_inspector.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use ahash::HashMap;
22
use alloy_consensus::Transaction;
33
use alloy_primitives::{Address, B256, U256};
4-
use alloy_rpc_types::AccessList;
54
use reth_primitives::{Recovered, TransactionSigned};
65
use revm::{
76
bytecode::opcode,
@@ -10,7 +9,6 @@ use revm::{
109
interpreter::{interpreter_types::Jumps, CallInputs, CallOutcome, Interpreter},
1110
Inspector,
1211
};
13-
use revm_inspectors::access_list::AccessListInspector;
1412

1513
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1614
pub struct SlotKey {
@@ -257,7 +255,6 @@ where
257255

258256
#[derive(Debug)]
259257
pub struct RBuilderEVMInspector<'a> {
260-
access_list_inspector: AccessListInspector,
261258
used_state_inspector: Option<UsedStateEVMInspector<'a>>,
262259
}
263260

@@ -266,23 +263,15 @@ impl<'a> RBuilderEVMInspector<'a> {
266263
tx: &Recovered<TransactionSigned>,
267264
used_state_trace: Option<&'a mut UsedStateTrace>,
268265
) -> Self {
269-
let access_list_inspector =
270-
AccessListInspector::new(tx.access_list().cloned().unwrap_or_default());
271-
272266
let mut used_state_inspector = used_state_trace.map(UsedStateEVMInspector::new);
273267
if let Some(i) = &mut used_state_inspector {
274268
i.use_tx_nonce(tx);
275269
}
276270

277271
Self {
278-
access_list_inspector,
279272
used_state_inspector,
280273
}
281274
}
282-
283-
pub fn into_access_list(self) -> AccessList {
284-
self.access_list_inspector.into_access_list()
285-
}
286275
}
287276

288277
impl<'a, CTX> Inspector<CTX> for RBuilderEVMInspector<'a>
@@ -292,7 +281,6 @@ where
292281
{
293282
#[inline]
294283
fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) {
295-
self.access_list_inspector.step(interp, context);
296284
if let Some(used_state_inspector) = &mut self.used_state_inspector {
297285
used_state_inspector.step(interp, context);
298286
}

crates/rbuilder/src/building/order_commit.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,35 @@ where
11621162
Factory: EvmFactory,
11631163
{
11641164
let tx = tx_with_blobs.internal_tx_unsecure();
1165+
1166+
// Skip the AccessListInspector entirely — it calls step() on every EVM opcode
1167+
// just to track accessed addresses for the blocklist check. Instead, we check
1168+
// the blocklist against ResultAndState.state (EvmState = HashMap<Address, Account>)
1169+
// which already contains every address touched during execution.
1170+
// This eliminates ~50% of CPU overhead during block building.
1171+
if used_state_tracer.is_none() {
1172+
let mut evm = evm_factory.create_evm(db, evm_env);
1173+
let res = match evm.transact(tx) {
1174+
Ok(res) => res,
1175+
Err(err) => match err {
1176+
EVMError::Transaction(tx_err) => {
1177+
return Ok(Err(TransactionErr::InvalidTransaction(tx_err)))
1178+
}
1179+
EVMError::Database(_) | EVMError::Header(_) | EVMError::Custom(_) => {
1180+
return Err(err.into())
1181+
}
1182+
},
1183+
};
1184+
// Check blocklist against addresses in the execution state diff
1185+
if !blocklist.is_empty() && res.state.keys().any(|addr| blocklist.contains(addr)) {
1186+
return Ok(Err(TransactionErr::Blocklist));
1187+
}
1188+
return Ok(Ok(res));
1189+
}
1190+
1191+
// Slow path: used_state_tracer is active (parallel builder conflict detection).
1192+
// Still need the inspector for UsedStateEVMInspector, but we can skip AccessListInspector
1193+
// and use the state diff for blocklist checking instead.
11651194
let mut rbuilder_inspector = RBuilderEVMInspector::new(tx, used_state_tracer);
11661195

11671196
let mut evm = evm_factory.create_evm_with_inspector(db, evm_env, &mut rbuilder_inspector);
@@ -1177,8 +1206,8 @@ where
11771206
},
11781207
};
11791208
drop(evm);
1180-
let access_list = rbuilder_inspector.into_access_list();
1181-
if access_list.flatten().any(|(a, _)| blocklist.contains(&a)) {
1209+
// Use state diff for blocklist check instead of access list
1210+
if !blocklist.is_empty() && res.state.keys().any(|addr| blocklist.contains(addr)) {
11821211
return Ok(Err(TransactionErr::Blocklist));
11831212
}
11841213

0 commit comments

Comments
 (0)