Skip to content

refactor(test-benchmark): split stateful setup transactions across blocks - #3282

Open
LouisTsai-Csie wants to merge 5 commits into
ethereum:forks/amsterdamfrom
LouisTsai-Csie:split-setup-tx
Open

refactor(test-benchmark): split stateful setup transactions across blocks#3282
LouisTsai-Csie wants to merge 5 commits into
ethereum:forks/amsterdamfrom
LouisTsai-Csie:split-setup-tx

Conversation

@LouisTsai-Csie

@LouisTsai-Csie LouisTsai-Csie commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Description

Problem 1: Gas Limit Mismatch Between EELS and synthetic state

EELS configures the block gas limit to 1 Trillion, while pre-state generated via state-actor uses 1 Gigagas. This mismatch prevents early detection of any exceeding block gas limit issues.

Example: In test_auth_transaction with empty_authority_False parametrization, the setup phase requires many authorization transactions. The total gas exceeds 1 Gigagas but stays under 1 Trillion. EELS construction succeeds, but the snapshot rejects it by default.

Root Cause

Currently, blockchain.py packs all setup transactions (fund_eoa, deploy_contract, etc.) into a single block. After state repricing, this easily exceeds 1 Gigagas (some test would create hundreds of thousands of slots, accounts, delegation). Since setup gas isn't verified, a setup phase can exceed the benchmark's own gas limit and still pass in fill mode. However, when filling against a live client with fill-stateful, the client's block gas limit rejects the block.

Solution

This PR splits setup transactions across multiple blocks, ensuring each strictly respects the block gas limit. This could fix the following broken cases:

  • test_ether_transfers_to_precompile
  • test_ether_transfers
  • test_auth_transaction
  • test_storage_access_cold
  • test_ext_account_query_cold
  • test_mixed_dependency_graph
  • test_state_root_computation (initcode prefix too long)
  • test_deploy_then_interact

Problem 2: RPC request exceeds size limit

Certain RPC calls have request body size limits. The test_selfdestructing_existing benchmark exceeds this limit because it calls get_alloc with a large batch of data at once. This PR splits the requests into smaller batches to stay within the limit.

Additional Fixes

  • Two tests now fund a single sender instead of one per transaction, this avoids creating thousands of account during setup phase.
  • test_block_full_data funds recipients with an explicit amount (no deferred balances)

Related Issues or PRs

issue #3281

Checklist

  • Ran fast static checks to avoid CI fails, see Code Standards & Verifying Changes: just static
  • PR title has the form <type>(<area>): <title>, where <type> and <area> come from an appropriate C-<type>, respectively A-<area>, label. The title should match the target squash commit message.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@LouisTsai-Csie LouisTsai-Csie self-assigned this Aug 3, 2026
@LouisTsai-Csie LouisTsai-Csie added A-test-benchmark Area: execution_testing.benchmark and tests/benchmark C-refactor Category: refactor labels Aug 3, 2026
@LouisTsai-Csie LouisTsai-Csie changed the title fix(test-benchmark): split stateful setup transactions across blocks refactor(test-benchmark): split stateful setup transactions across blocks Aug 3, 2026
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.49%. Comparing base (9d6e6f8) to head (3af71ea).
⚠️ Report is 2 commits behind head on forks/amsterdam.

Additional details and impacted files
@@               Coverage Diff                @@
##           forks/amsterdam    #3282   +/-   ##
================================================
  Coverage            93.49%   93.49%           
================================================
  Files                  625      625           
  Lines                37032    37039    +7     
  Branches              3385     3392    +7     
================================================
+ Hits                 34623    34630    +7     
  Misses                1653     1653           
  Partials               756      756           
Flag Coverage Δ
unittests 93.49% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@LouisTsai-Csie

Copy link
Copy Markdown
Contributor Author

Please carefully review this refactor, or it would slow down the entire benchmark process a lot!

@spencer-tb spencer-tb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of comments, I think they should make sense :)

Comment thread packages/testing/src/execution_testing/specs/blockchain.py Outdated
Comment thread packages/testing/src/execution_testing/specs/blockchain.py
# between query timing and tx submission (basefee can climb a few blocks
# between the two; the bump keeps txs landing without per-tx requeries).
FEE_BUMP_MULTIPLIER = 1.5
FEE_BUMP_MULTIPLIER = 3.0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The max_fee_per_gas and other gas configuration values adjust based on network status. However, the FEE_MULTIPLIER is only 1.5x. For benchmarks with many blocks, the cumulative gas limit can exceed the value calculated at the start of the test, leading to lower base fee issues.

Comment on lines +1132 to +1137
responses: List[JSONRPCResponse] = []
batch_size = self.max_transactions_per_batch
for i in range(0, len(all_calls), batch_size):
responses.extend(
self.post_batch_request(calls=all_calls[i : i + batch_size])
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of sending RPC requests all at once, this PR batches them to reduce server load.

Comment on lines 305 to 310
Transaction(
to=op_address,
data=calldata,
gas_limit=gas_limit,
sender=pre.fund_eoa(),
sender=execution_sender,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better than the original version:

execution_txs.append(
    Transaction(
        to=op_address,
        data=calldata,
        gas_limit=gas_limit,
        sender=pre.fund_eoa(), # This requires pre-funding for EVERY account
    )
)

Pre-funding a sender per transaction is unnecessary and slows setup. Empty account creation is now much more expensive after repricing.

Comment on lines +169 to +174
state_cost = fork.transaction_top_frame_state_gas(
sends_value=sends_value,
recipient_type=recipient_type,
)
iteration_cost = regular_cost + state_cost
iteration_count = gas_benchmark_value // max(regular_cost, state_cost)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual worst case for ether transfer, the iteration cost is sum(R, S), while the gas usage in the header is max(R, S), not sum(R, S).

The original approach used ~90% of the block gas limit, but now it reaches 99%.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-test-benchmark Area: execution_testing.benchmark and tests/benchmark C-refactor Category: refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants