Skip to content

perf(builder): reuse unchanged withdrawals roots - #5041

Open
BrianBland wants to merge 1 commit into
mainfrom
toshi/cache-withdrawal-root-computation
Open

BrianBland wants to merge 1 commit into
mainfrom
toshi/cache-withdrawal-root-computation

Conversation

@BrianBland

Copy link
Copy Markdown
Contributor

Summary

Avoid recomputing the L2ToL1MessagePasser storage root when building a post-Isthmus payload whose MessagePasser storage is unchanged relative to its parent. Both the standard assembler and flashblocks builder now use a shared helper that reuses the parent's withdrawals root.

Reuse requires a post-Isthmus parent with a withdrawals root, no changed storage slots, and no storage destruction. Otherwise, use the existing calculation. Flashblocks retain their current published-root behavior and inspect cumulative changes relative to the parent, not just the latest flashblock. No global or flashblock-specific cache is introduced; the fast path also benefits Denim's 200ms blocks.

Includes regression tests for unchanged storage, net-zero writes, cumulative flashblocks, destruction, activation boundaries, different parents, and provider errors. Also imports alloc::Box explicitly to fix an existing no_std compilation error exposed during validation.

Validation

Rebased onto base/base/main at 380b55db3; all checks passed after rebasing:

  • cargo test -p base-execution-consensus --lib — 41 passed.
  • cargo test -p base-execution-evm --lib — 25 passed.
  • cargo test -p base-builder-core --lib flashblocks::payload::tests — 9 passed.
  • cargo clippy -p base-execution-consensus -p base-execution-evm -p base-builder-core --lib --tests -- -D warnings
  • cargo check -p base-execution-consensus --no-default-features --lib
  • Nightly rustfmt on changed Rust files and git diff --check.

No latency improvement is claimed without benchmarking.

Generated with Toshi

@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@depot-code-access

depot-code-access Bot commented Sep 14, 2026

Copy link
Copy Markdown

✅ All benchmarks green — 14 within ±2% (deterministic instruction counts). View run

Benchmark details (14)
Benchmark Base (target) Head (this PR) Δ instructions
batch_queue/drain/drain_cached_span_batches 242,027 242,027 +0.0%
batch_transaction/encode_in_place/encode_in_place 4,199,759 4,199,759 +0.0%
batch_transaction/temporary_frame_buffers/temporary_frame_buffers 8,408,350 8,408,350 +0.0%
flashblock_decode/decode/brotli 3,296,484 3,296,484 +0.0%
flashblock_decode/decode/plain_json 2,280,194 2,280,194 +0.0%
flz/compress_len/real_contract_call 43,148 43,148 +0.0%
flz/compress_len/synthetic_0 38,205 38,205 +0.0%
flz/compress_len/synthetic_1 54,682 54,682 +0.0%
flz/compress_len/synthetic_2 147,976 147,976 +0.0%
flz/data_gas 43,059 43,059 +0.0%
flz/tx_estimated_size 43,056 43,056 +0.0%
frame_parse/decode/single_4kib 1,031 1,031 +0.0%
frame_parse/parse_frames/few_large 1,053,062 1,053,062 +0.0%
frame_parse/parse_frames/many_small 154,763 154,763 +0.0%

@BrianBland
BrianBland marked this pull request as ready for review September 14, 2026 22:55
@depot-code-access

Copy link
Copy Markdown

Base Std historical fork tests

Fork Result Passed Failed Skipped base/base base-anvil base-std
Beryl pass 616 0 13 a3139b7e 98e7839c 520d069b
Cobalt pass 725 0 14 a3139b7e 98e7839c 4571b325

View run

@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

PR: perf(builder): reuse unchanged withdrawals roots

This PR adds a fast path in WithdrawalsRoot::compute that skips the L2ToL1MessagePasser storage root recomputation when the predeploy's storage hasn't changed since the parent block. Both the standard BaseBlockAssembler and the flashblocks build_block path now use this shared helper.

Analysis

Correctness of the optimization gate (withdrawals.rs:29-36): The three conditions — post-Isthmus parent, parent has a withdrawals_root, and no changed/destroyed MessagePasser storage — are sound. StorageSlot::is_changed() in revm's BundleState compares the original DB value against the present value, so net-zero writes are correctly detected as unchanged. The Isthmus activation boundary is properly handled by checking the parent's timestamp, avoiding reuse of the pre-Isthmus withdrawals root (which had a different semantic meaning). The was_destroyed() guard prevents reuse after SELFDESTRUCT.

Flashblocks path: build_block calls merge_transitions before computing the withdrawals root, so state.bundle_state correctly contains cumulative changes relative to the parent across all flashblocks. The new test (flashblocks_preserve_cumulative_withdrawals_root) validates that once a MessagePasser write occurs in any flashblock, subsequent flashblocks don't revert to the parent root.

Fallback consistency: When the MessagePasser account is absent from the bundle, the fast path returns the parent root. The existing isthmus::withdrawals_root fallback also returns the parent root in this case (via HashedStorage::default() with wiped: false), so the two paths are consistent.

H: BlockHeader bound on assemble_block: This tightens the generic bound, but the BlockAssembler trait impl already constrains H through BlockAssemblerInput, so this is not a breaking change in practice.

Block production risk: No critical findings. The optimization is a pure fast-path skip of an expensive storage root computation, with a correct fallback to the existing calculation when any condition is not met. Error propagation from the fallback path is preserved.

Minor observation

lib.rs:41-42 re-exports withdrawals::tests as withdrawals_root_test_utils under #[cfg(test)], but this alias is not consumed by any code in the crate or workspace. It's harmless dead code — consider removing if it's not intended for future use.

No blocking issues found.

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.

2 participants