feat(benchmarks): add BAL benchmarks for the optimization strategies introduced by BALs#2197
Conversation
- test_prefetch_cold_storage: Cold SLOAD workload with sequential and hash-chain scattered access patterns. The scattered pattern is unpredictable without BAL but trivially prefetchable with one. - test_coinbase_serialization: Disjoint contracts where coinbase fee accumulation is the only shared state. Implicit (fees) and explicit (CALL to coinbase) variants. - test_deploy_then_interact: Deploy/call tx pairs in a single block. Independent pairs (parallelizable) and single-contract (serial) variants. - test_mixed_dependency_graph: Interleaved groups of serial keccak chains. Group sizes 1/2/5 control available parallelism.
|
Hi @jochem-brouwer, based on your BAL benchmark description, I put together a small refactor below. I’ve run it locally and it passes. @pytest.mark.valid_from("Amsterdam")
def test_tx_dependency(
benchmark_test: BenchmarkTestFiller,
pre: Alloc,
fork: Fork,
) -> None:
"""
Benchmark BAL with transaction-dependent execution.
Deploy a contract that reads storage slot 0, computes a
keccak256 hash chain until gas is nearly exhausted, and writes
the result back. Each transaction depends on the previous
one's storage write, preventing parallel execution.
"""
target_slot = 0
setup = Op.MSTORE(
0,
Op.SLOAD(target_slot, key_warm=False),
# gas accounting
old_memory_size=0,
new_memory_size=32,
)
loop_body = Op.MSTORE(
0,
Op.SHA3(0, 32, data_size=32),
# gas accounting
old_memory_size=32,
new_memory_size=32,
)
cleanup = (
Op.SSTORE(
target_slot,
Op.MLOAD(0),
# gas accounting
key_warm=True,
original_value=1,
current_value=1,
new_value=2,
)
)
reserve_gas = cleanup.gas_cost(fork) + 50
condition = Op.GT(Op.GAS, reserve_gas)
attack_code = setup + While(body=loop_body, condition=condition) + cleanup
attack_contract = pre.deploy_contract(
code=attack_code,
storage={0: 1},
)
benchmark_test(
tx=Transaction(
to=attack_contract,
sender=pre.fund_eoa(),
),
skip_gas_used_validation=True,
)Each transaction starts by reading slot 0, continues the hash chain, and then stores the final result back to slot 0. I didn’t change the overall design, but I simplified the per-transaction logic since our benchmark test wrapper already generates multiple transactions while respecting tx gas limit cap. |
|
@LouisTsai-Csie can you please help review jochem-brouwer#1 when you get a chance 👀? It builds on the TODOs here and adds more test cases. If it's not far off and only needs tweaks, I think we can at least merge it and work on subsequent updates here so that we have a more complete picture PR'd to |
|
|
||
| condition = Op.GT(Op.GAS, reserve_gas) | ||
|
|
||
| loop = While(body=keccak_body, condition=condition) |
There was a problem hiding this comment.
issue(perf, non-blocking): Loop wastes 5 gas per cycle
The while loop compiles to:
[setup][JUMPDEST][body][condition][compute jumpdest][JUMPI][cleanup]and the jumpdest at runtime using an offset:
JUMPDEST ← we want to jump back here
body
condition
PUSH4 <offset> ← "how far back is the JUMPDEST?"
PC ← "where am I right now?"
SUB ← PC - offset = JUMPDEST address
JUMPI So PC + SUB (= 5 gas) is a workaround ["I don't know where I am in absolute terms, but I know how far back to jump."]
i.e: the root cause is: While is not aware of setup.
Suggestion
Introduce an optional setup code for While generator ( = for loop primitive) so jumpdest can be computed at compile-time JUMPDEST = len(setup):
setup
JUMPDEST
body
condition
PUSH1 len(setup)
JUMPIThere was a problem hiding this comment.
This is a good point and it looks like this is part of the definition of While. I think it's possible to generalize this idea and thus to remove the calculate-jumpdest-at-runtime for all while loops. (Should be addressed in a refactor at some point)
|
I have tested against EthJS to verify that the tests fill and added some minor changes. The tests fill, and on a quick inspection it also looks like they execute the expected behavior (loops without OOGs). I think we can merge this one for BAL-specific benchmarks. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #2197 +/- ##
===================================================
- Coverage 86.07% 86.01% -0.07%
===================================================
Files 599 599
Lines 39472 36904 -2568
Branches 3780 3771 -9
===================================================
- Hits 33977 31744 -2233
+ Misses 4862 4551 -311
+ Partials 633 609 -24
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| body_gas = body.gas_cost(fork) | ||
| placeholder = Op.GT(Op.GAS, Op.PUSH1(0)) | ||
| per_iter_gas = While(body=body, condition=placeholder).gas_cost(fork) | ||
| exit_overhead = per_iter_gas - body_gas - Op.JUMPDEST.gas_cost(fork) |
There was a problem hiding this comment.
Just leave a note here, i hope PR #2103 could help when it is merged
fselmo
left a comment
There was a problem hiding this comment.
This lgtm and we should ideally get this in now so going to merge :). @LouisTsai-Csie you mentioned you'd like to refactor here. Can you create an issue to track the refactor? Going to ping you here so you do not forget :)
* ✨ feat(tests): EIP-7928 SELFDESTRUCT tests * feat: point to latest commit in BALs specs (resolver) * feat: Validate t8n BAL does not have duplicate entries for the same tx_index * 📄 docs: Changelog entry * chore: avoid extra fields in BAL classes, related to ethereum#2197 * Add tests for EIP-7928 around precompiles (doc) * fix(tests): Fix expectations for self-destruct tests --------- Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com> Co-authored-by: Toni Wahrstätter <51536394+nerolation@users.noreply.github.com>
* ✨ feat(tests): EIP-7928 SELFDESTRUCT tests * feat: point to latest commit in BALs specs (resolver) * feat: Validate t8n BAL does not have duplicate entries for the same tx_index * 📄 docs: Changelog entry * chore: avoid extra fields in BAL classes, related to ethereum#2197 * Add tests for EIP-7928 around precompiles (doc) * fix(tests): Fix expectations for self-destruct tests --------- Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com> Co-authored-by: Toni Wahrstätter <51536394+nerolation@users.noreply.github.com>
* ✨ feat(tests): EIP-7928 SELFDESTRUCT tests * feat: point to latest commit in BALs specs (resolver) * feat: Validate t8n BAL does not have duplicate entries for the same tx_index * 📄 docs: Changelog entry * chore: avoid extra fields in BAL classes, related to ethereum#2197 * Add tests for EIP-7928 around precompiles (doc) * fix(tests): Fix expectations for self-destruct tests --------- Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com> Co-authored-by: Toni Wahrstätter <51536394+nerolation@users.noreply.github.com>
🗒️ Description
This PR adds BAL tests, for more information and background see jochem-brouwer#1
Huge thanks to @fselmo for refactoring and adding tests!
🔗 Related Issues or PRs
N/A.
✅ Checklist
toxchecks to avoid unnecessary CI fails, see also Code Standards and Enabling Pre-commit Checks:uvx tox -e statictype(scope):.mkdocs servelocally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.@ported_frommarker.Cute Animal Picture