Skip to content

Fix erc20_bloater running out of gas on Amsterdam - #269

Open
damilolaedwards wants to merge 2 commits into
ethpandaops:masterfrom
damilolaedwards:fix/erc20-bloater-amsterdam-gas
Open

Fix erc20_bloater running out of gas on Amsterdam#269
damilolaedwards wants to merge 2 commits into
ethpandaops:masterfrom
damilolaedwards:fix/erc20-bloater-amsterdam-gas

Conversation

@damilolaedwards

Copy link
Copy Markdown
Contributor

Fixes #260

Under Amsterdam (EIP-8037), creating a new storage slot also charges state-creation gas from a second, separate budget. A transaction only receives a share of that budget once its total requested gas exceeds the EIP-7825 ceiling; anything at or below that ceiling gets a state-gas reservoir of exactly zero, so every new slot's state-creation cost spills entirely into regular gas instead.

bloatStorage() deliberately stayed just under that ceiling to satisfy EIP-7825, which is exactly what starved it of any state-gas reservoir. Every bloat transaction then needed roughly 4-5x the gas the fixed batch size assumed, and reverted out of gas partway through the loop.

The per-transaction gas limit and address count are now computed from the chain's live fork state and gas ceiling instead of a fixed constant, mirroring the approach already used in storagerefundtx. Batches are smaller than before on Amsterdam chains since the real per-address cost is much higher than the old model assumed, so reaching the same target storage size takes correspondingly more transactions.

That alone isn't quite enough on its own, though. TxPool.IsAmsterdam() is a static, operator-set preference that defaults to true, not a live check against what the connected chain has actually activated. On a chain that is Osaka-active but not yet Amsterdam-active, a transaction whose gas exceeds the EIP-7825 ceiling is rejected outright before it ever reaches a block, so a run spanning the Amsterdam activation boundary would have every pre-fork transaction rejected, which is a worse failure than the original revert.

To handle that, the per-round send logic is now retried once with the pre-Amsterdam sizing whenever a round is rejected specifically for exceeding the gas limit. Nothing is cached across rounds, so the next round tries the Amsterdam sizing again and throughput recovers on its own once the chain actually activates Amsterdam. Nonces from rejected-at-submission transactions are released so the retry can reuse them instead of leaving a gap; transactions that revert on-chain are left alone since those nonces are already legitimately consumed.

Pre-Amsterdam behavior (no override, chain never touches the Amsterdam path) is unchanged throughout.

Under Amsterdam (EIP-8037), creating a new storage slot also charges
state-creation gas from a second, separate budget. A transaction only
receives a share of that budget once its total requested gas exceeds the
EIP-7825 ceiling; anything at or below that ceiling gets a state-gas
reservoir of exactly zero, so every new slot's state-creation cost spills
entirely into regular gas instead.

bloatStorage() deliberately stayed just under that ceiling to satisfy
EIP-7825, which is exactly what starves it of any state-gas reservoir. Every
bloat transaction then needs roughly 4-5x the gas the fixed batch size
assumed, and reverts out of gas partway through the loop.

The per-transaction gas limit and address count are now computed from the
chain's live fork state and gas ceiling instead of a fixed constant,
mirroring the approach already used in storagerefundtx. Pre-Amsterdam
behavior is unchanged. Batches are smaller than before on Amsterdam chains
since the real per-address cost is much higher than the old model assumed,
so reaching the same target storage size takes correspondingly more
transactions.
TxPool.IsAmsterdam() is a static, operator-set preference that defaults to
true - it does not reflect whether the connected chain has actually
activated Amsterdam. On a chain that is Osaka-active but has not yet
activated Amsterdam, a transaction whose Gas exceeds the EIP-7825 ceiling is
rejected outright by the node before it ever reaches a block. The previous
fix computed exactly such a value by default, so a run spanning the
Amsterdam activation boundary (the scenario this fix exists for) would have
every bloat transaction rejected during the pre-fork portion of the run,
which is a worse failure than the original out-of-gas revert it replaced.

The per-round build-and-send logic is now a standalone function so it can be
retried with different parameters. When a round's transactions are rejected
specifically for exceeding the gas limit, the scenario logs why and retries
that one round with the pre-Amsterdam sizing. Nothing is cached across
rounds: the next round tries the Amsterdam sizing again, so throughput
recovers automatically once the chain actually activates Amsterdam, without
operator intervention in either direction.

Rejected-at-submission transactions never reach the chain, so the nonces
they allocated are released for reuse instead of leaving a permanent gap;
transactions that revert on-chain already consumed their nonce legitimately
and are left alone.

Adds a test proving the exact fallback values are never subject to the
rejection this is recovering from, and a test covering the error-detection
helper against the real wrapped error chain and against unrelated errors
that happen to mention gas or limits.
@damilolaedwards
damilolaedwards marked this pull request as ready for review August 6, 2026 21:04
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.

[bug] erc20_bloater: bloat transactions revert with out-of-gas on Amsterdam (EIP-8037)

1 participant