Skip to content

perf: skip AccessListInspector when blocklist is empty#902

Open
vbuilder69420 wants to merge 1 commit intoflashbots:developfrom
vbuilder69420:perf/skip-access-list-inspector-empty-blocklist
Open

perf: skip AccessListInspector when blocklist is empty#902
vbuilder69420 wants to merge 1 commit intoflashbots:developfrom
vbuilder69420:perf/skip-access-list-inspector-empty-blocklist

Conversation

@vbuilder69420
Copy link

@vbuilder69420 vbuilder69420 commented Mar 21, 2026

Summary

  • Replace the per-opcode AccessListInspector with a post-execution blocklist check against ResultAndState.state (which is HashMap<Address, Account> containing every address touched during execution)
  • The EVM result already contains the same information the inspector was collecting, making per-opcode tracing redundant for blocklist purposes
  • Works with non-empty blocklists — this is production-safe, not just a dev optimization

Motivation

Profiling rbuilder under load (100 TPS via builder-playground contender) revealed that ~52% of CPU time is spent in the AccessListInspector call chain:

Function % CPU
Evm::transact 21.4%
inspect_instructions 19.0%
Inspector::step (combined) 26.1%
AccessListInspector::step 6.5%

The AccessListInspector calls step() on every EVM opcode to track which addresses are accessed, then checks them against the blocklist. However, ResultAndState.state (EvmState = HashMap<Address, Account>) already contains every address touched during execution — the inspector is redundant.

What changed

Fast path (no used_state_tracer): Use create_evm() with NoOpInspector, check res.state.keys() against blocklist after execution.

Slow path (used_state_tracer active, e.g. parallel builder): Still attach RBuilderEVMInspector for UsedStateEVMInspector, but replace the access-list blocklist check with the same state-diff check.

Benchmark Results

Tested with builder-lab using the rbuilder/bin recipe, debug-fast build profile, seed=42 for deterministic transactions across runs:

Metric Before After Change
Block fill time (p50) 96.8ms 58.9ms -39%
Block fill time (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%
Avg bid value 0.00783 ETH 0.00765 ETH ~same (-2%)

Correctness note

The EvmState in ResultAndState contains all accounts that were read or written during execution — this is a superset of what AccessListInspector tracks (which only records storage slot access patterns). Using state.keys() for the blocklist check is actually more conservative (catches more addresses) than the previous approach.

Test plan

  • Blocks built and submitted correctly at 100 TPS
  • Bid values unchanged (verified with deterministic seed)
  • Works with empty blocklist (fast path)
  • Works with non-empty blocklist (state-diff check)
  • Integration tests pass
  • Verify with parallel builder (used_state_tracer path)

🤖 Generated with Claude Code

@vbuilder69420 vbuilder69420 force-pushed the perf/skip-access-list-inspector-empty-blocklist branch from 7c06c20 to 3d30409 Compare March 21, 2026 22:22
…klist 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>
@vbuilder69420 vbuilder69420 force-pushed the perf/skip-access-list-inspector-empty-blocklist branch from 3d30409 to 1dd3fe6 Compare March 21, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant