diff --git a/docs/filling_tests/test_ids.md b/docs/filling_tests/test_ids.md index 7005ab639b..069518bb06 100644 --- a/docs/filling_tests/test_ids.md +++ b/docs/filling_tests/test_ids.md @@ -55,8 +55,8 @@ The test framework can also generate blockchain tests containing blocks that spa Each Python test case is also typically parametrized by test type, respectively fixture format. For example, if the test is implemented as a `state_test`, the test framework will additionally generate the following blockchain test fixtures (consisting of a single block with a single transaction): -- a `blockchain_test` which can be tested via the Hive `eest/consume-rlp` simulator (or directly via a dedicated client interface). -- a `blockchain_engine_test` (for post-merge forks) which can be tested via the Hive `eest/consume-engine` simulator. +- a `blockchain_test` which can be tested via the Hive `eels/consume-rlp` simulator (or directly via a dedicated client interface). +- a `blockchain_engine_test` (for post-merge forks) which can be tested via the Hive `eels/consume-engine` simulator. ### Example: The Test IDs generated for `test_chainid` diff --git a/docs/running_tests/consume/exceptions.md b/docs/running_tests/consume/exceptions.md index 453284aedf..a5a82c499a 100644 --- a/docs/running_tests/consume/exceptions.md +++ b/docs/running_tests/consume/exceptions.md @@ -98,7 +98,7 @@ uv run consume engine --disable-strict-exception-matching=nimbus-el Enable verbose client output: ```bash -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --docker.output \ --sim.loglevel 5 ``` diff --git a/docs/running_tests/execute/hive.md b/docs/running_tests/execute/hive.md index 6af210f909..c4d89e48c0 100644 --- a/docs/running_tests/execute/hive.md +++ b/docs/running_tests/execute/hive.md @@ -2,7 +2,7 @@ Tests can be executed on a local hive-controlled single-client network by running the `execute hive` command. -## The `eest/execute-blobs` Simulator +## The `eels/execute-blobs` Simulator The `blob_transaction_test` execute test spec sends blob transactions to a running client. Blob transactions are fully supported in execute mode: diff --git a/docs/running_tests/hive/client_config.md b/docs/running_tests/hive/client_config.md index be04107f11..e2713780e9 100644 --- a/docs/running_tests/hive/client_config.md +++ b/docs/running_tests/hive/client_config.md @@ -102,17 +102,17 @@ cp -r /path/to/your/go-ethereum ./clients/go-ethereum/go-ethereum-local Force rebuild base images: ```bash -./hive --docker.pull --sim ethereum/eest/consume-engine +./hive --docker.pull --sim ethereum/eels/consume-engine ``` Force rebuild specific client: ```bash -./hive --docker.nocache "clients/go-ethereum" --sim ethereum/eest/consume-engine +./hive --docker.nocache "clients/go-ethereum" --sim ethereum/eels/consume-engine ``` Show the docker container build output: ```bash -./hive --docker.buildoutput --sim ethereum/eest/consume-engine +./hive --docker.buildoutput --sim ethereum/eels/consume-engine ``` diff --git a/docs/running_tests/hive/common_options.md b/docs/running_tests/hive/common_options.md index ff3d313252..2272e7d058 100644 --- a/docs/running_tests/hive/common_options.md +++ b/docs/running_tests/hive/common_options.md @@ -1,15 +1,15 @@ # Common Simulator Options -All EEST Hive simulators share common command-line options and patterns. +All execution-specs (EELS) Hive simulators share common command-line options and patterns. ## Basic Usage -While they may be omitted, it's recommended to specify the `fixtures` and `branch` simulator build arguments when running EEST simulators. +While they may be omitted, it's recommended to specify the `fixtures` and `branch` simulator build arguments when running execution-specs simulators. For example, this runs "stable" fixtures from the v4.3.0 [latest stable release](../releases.md#standard-releases) and builds the simulator at the v4.3.0 tag: ```bash -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --sim.buildarg fixtures=stable@v4.3.0 \ --sim.buildarg branch=v4.3.0 \ --client go-ethereum @@ -20,7 +20,7 @@ For example, this runs "stable" fixtures from the v4.3.0 [latest stable release] Run a subset of tests by filtering tests using `--sim.limit=` to perform a regular expression match against test IDs: ```bash -./hive --sim ethereum/eest/consume-engine --sim.limit ".*eip4844.*" +./hive --sim ethereum/eels/consume-engine --sim.limit ".*eip4844.*" ``` ### Collect Only/Dry-Run @@ -28,7 +28,7 @@ Run a subset of tests by filtering tests using `--sim.limit=` to perform The `collectonly:` prefix can be used to inspect which tests would match an expression (dry-run), `--docker.output` must be specified to see the simulator's collection result: ```bash -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --sim.buildarg fixtures=stable@v4.3.0 \ --sim.buildarg branch=v4.3.0 \ --docker.output \ @@ -40,7 +40,7 @@ The `collectonly:` prefix can be used to inspect which tests would match an expr The `id:` prefix can be used to select a single test via its ID (this will automatically escape any special characters in the test case ID): ```console -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --sim.buildarg fixtures=stable@v4.3.0 \ --sim.buildarg branch=v4.3.0 \ --docker.output \ @@ -52,7 +52,7 @@ The `id:` prefix can be used to select a single test via its ID (this will autom To run multiple tests in parallel, use `--sim.parallelism`: ```bash -./hive --sim ethereum/eest/consume-rlp --sim.parallelism 4 +./hive --sim ethereum/eels/consume-rlp --sim.parallelism 4 ``` ### Output Options @@ -60,7 +60,7 @@ To run multiple tests in parallel, use `--sim.parallelism`: See hive log output in the console: ```bash -./hive --sim ethereum/eest/consume-engine --sim.loglevel 5 +./hive --sim ethereum/eels/consume-engine --sim.loglevel 5 ``` ### Container Issues @@ -68,5 +68,5 @@ See hive log output in the console: Increase client timeout: ```bash -./hive --client.checktimelimit=180s --sim ethereum/eest/consume-engine +./hive --client.checktimelimit=180s --sim ethereum/eels/consume-engine ``` diff --git a/docs/running_tests/hive/dev_mode.md b/docs/running_tests/hive/dev_mode.md index 61b382ff44..965a7e1855 100644 --- a/docs/running_tests/hive/dev_mode.md +++ b/docs/running_tests/hive/dev_mode.md @@ -1,6 +1,6 @@ # Hive Development Mode -This section explains how to run EEST simulators using their EEST commands, e.g., `uv run consume engine`, against a Hive "development" server as apposed to using the standalone `./hive` command. +This section explains how to run EELS simulators using their Python-based commands, e.g., `uv run consume engine`, against a Hive "development" server as apposed to using the standalone `./hive` command. This avoids running the simulator in a dockerized environment and has several advantages: @@ -18,7 +18,7 @@ This avoids running the simulator in a dockerized environment and has several ad ### Prerequisites -- EEST is installed, see [Installation](../../getting_started/installation.md) +- The execution-specs repo is setup in development mode, see [Installation](../../getting_started/installation.md) - Hive is built, see [Hive](../hive/index.md#quick-start). ## Hive Dev Setup on Linux @@ -29,7 +29,7 @@ This avoids running the simulator in a dockerized environment and has several ad ./hive --dev --client go-ethereum --client-file clients.yaml --docker.output ``` -2. In a separate shell, configure environment for EEST: +2. In a separate shell, configure environment for execution-specs: === "bash/zsh" @@ -43,7 +43,7 @@ This avoids running the simulator in a dockerized environment and has several ad set -x HIVE_SIMULATOR http://127.0.0.1:3000 ``` -3. Run EEST consume commands +3. Run execution-specs `consume` commands ```bash uv run consume engine --input ./fixtures -k "test_chainid" @@ -56,37 +56,36 @@ Due to Docker running within a VM on macOS, the host machine and Docker containe 1. Linux VM: Run a Linux virtual machine on your macOS and execute the standard development workflow above from within the VM. 2. Remote Linux: SSH into a remote Linux environment (server or cloud instance) and run development mode there. -3. **Docker Development Image**: Create a containerized EEST environment that runs within Docker's network namespace (recommended). +3. **Docker Development Image**: Create a containerized execution-specs environment that runs within Docker's network namespace (recommended). The following section details the setup and usage of option 3. -### EEST Docker Development Image +### EELS Docker Development Image -Within the [`eest/`](https://github.com/ethereum/hive/tree/master/simulators/ethereum/eest) directory of hive, a new dockerfile must be created: `Dockerfile.dev`, with the following contents: +Within the [`eels/`](https://github.com/ethereum/hive/tree/master/simulators/ethereum/eels) directory of hive, a new dockerfile must be created: `Dockerfile.dev`, with the following contents: ```docker FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim -ARG branch=main -ENV GIT_REF=${branch} +ARG branch="" RUN apt-get update && apt-get install -y git -RUN git init execution-spec-tests && \ - cd execution-spec-tests && \ - git remote add origin https://github.com/ethereum/execution-spec-tests.git && \ - git fetch --depth 1 origin $GIT_REF && \ - git checkout FETCH_HEAD; - -WORKDIR /execution-spec-tests +RUN git clone --depth 1 https://github.com/ethereum/execution-specs.git && \ + cd execution-specs && \ + if [ -n "$branch" ]; then \ + git fetch --depth 1 origin "$branch" && \ + git checkout FETCH_HEAD; \ + fi +WORKDIR /execution-specs/packages/testing RUN uv sync ENTRYPOINT ["/bin/bash"] ``` -This dockerfile will be our entry point for running EEST commands. +This dockerfile will be our entry point for running simulator commands. -### `eest/` Hive Directory Structure +### `eels/` Hive Directory Structure ```tree -├── eest +├── eels │ ├── Dockerfile.dev │ ├── consume-block-rlp │ │ └── Dockerfile @@ -108,10 +107,10 @@ This dockerfile will be our entry point for running EEST commands. ./hive --dev --dev.addr :3000 --client go-ethereum --client-file clients.yaml ``` -3. In a separate terminal session, build the EEST development image: +3. In a separate terminal session, build the EELS development image: ```bash - cd simulators/ethereum/eest/ + cd simulators/ethereum/eels/ docker build -t macos-consume-dev -f Dockerfile.dev . ``` @@ -136,7 +135,7 @@ When Hive runs in dev mode: 3. Keeps the Hive Proxy container running between test executions. 4. Waits for external simulator connections via the API. -This allows EEST's consume commands to connect to the running Hive instance and execute tests interactively. +This allows the EELS's consume commands to connect to the running Hive instance and execute tests interactively. ## More Options Available diff --git a/docs/running_tests/hive/index.md b/docs/running_tests/hive/index.md index 81f75be5ee..aba902eb49 100644 --- a/docs/running_tests/hive/index.md +++ b/docs/running_tests/hive/index.md @@ -1,6 +1,6 @@ # Hive -@ethereum/hive is a containerized testing framework that helps orchestrate test execution against Ethereum clients. Hive is incredibly extensible; new test suites can be implemented in a module manner as "simulators" that interact with clients to test certain aspects of their behavior. EEST implements several simulators, see [Running Tests](../running.md) for an overview. +@ethereum/hive is a containerized testing framework that helps orchestrate test execution against Ethereum clients. Hive is incredibly extensible; new test suites can be implemented in a module manner as "simulators" that interact with clients to test certain aspects of their behavior. The execution-specs `testing` package implements several simulators, see [Running Tests](../running.md) for an overview. ## Quick Start diff --git a/docs/running_tests/releases.md b/docs/running_tests/releases.md index 2136f0401a..7f7ea06cef 100644 --- a/docs/running_tests/releases.md +++ b/docs/running_tests/releases.md @@ -7,10 +7,10 @@ | Format | Consumed by the client | Location in `.tar.gz` release | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | [State Tests](./test_formats/state_test.md) | - directly via a `statetest`-like command
(e.g., [go-ethereum/cmd/evm/staterunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/staterunner.go)) | `./fixtures/state_tests/` | -| [Blockchain Tests](./test_formats/blockchain_test.md) | - directly via a `blocktest`-like command
(e.g., [go-ethereum/cmd/evm/blockrunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/blockrunner.go))
- using the [RLPeest/consume-rlp Simulator](./running.md#rlp) via block import | `./fixtures/blockchain_tests/` | -| [Blockchain Engine Tests](./test_formats/blockchain_test_engine.md) | - using the [eest/consume-engine Simulator](./running.md#engine) and the Engine API | `./fixtures/blockchain_tests_engine/` | +| [Blockchain Tests](./test_formats/blockchain_test.md) | - directly via a `blocktest`-like command
(e.g., [go-ethereum/cmd/evm/blockrunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/blockrunner.go))
- using the [eels/consume-rlp Simulator](./running.md#rlp) via block import | `./fixtures/blockchain_tests/` | +| [Blockchain Engine Tests](./test_formats/blockchain_test_engine.md) | - using the [eels/consume-engine Simulator](./running.md#engine) and the Engine API | `./fixtures/blockchain_tests_engine/` | | [Transaction Tests](./test_formats/transaction_test.md) | - using a new simulator coming soon | None; executed directly from Python source,
using a release tag | -| Blob Transaction Tests | - using the [eest/execute-blobs Simulator](./execute/hive.md#the-eestexecute-blobs-simulator) and | None; executed directly from Python source,
using a release tag | +| Blob Transaction Tests | - using the [eels/execute-blobs Simulator](./execute/hive.md#the-eelsexecute-blobs-simulator) and | None; executed directly from Python source,
using a release tag | ## Release URLs and Tarballs diff --git a/docs/running_tests/running.md b/docs/running_tests/running.md index 94272d7d4e..efd98c340d 100644 --- a/docs/running_tests/running.md +++ b/docs/running_tests/running.md @@ -23,9 +23,9 @@ Both `consume` and `execute` provide sub-commands which correspond to different The following sections describe the different methods in more detail. -!!! note "`./hive --sim=eest/consume-engine` vs `consume engine`" +!!! note "`./hive --sim=eels/consume-engine` vs `consume engine`" - EEST simulators can be ran either standalone using the `./hive` command or via an EEST command against a `./hive --dev` backend, more details are [provided below](#two-methods-to-run-eest-simulators). + The execution-specs simulators can be ran either standalone using the `./hive` command or via a `uv`/Python-based command against a `./hive --dev` backend, more details are [provided below](#two-methods-to-run-eels-simulators). ## Direct @@ -48,7 +48,7 @@ The EEST `consume direct` command is a small wrapper around client direct interf | Nomenclature | | | -------------- | ------------------------ | | Command | `consume engine` | -| Simulator | `eest/consume-engine` | +| Simulator | `eels/consume-engine` | | Fixture format | `blockchain_test_engine` | The consume engine method tests execution clients via the Engine API by sending block payloads and verifying the response (post-merge forks only). This method provides the most realistic testing environment for production Ethereum client behavior, covering consensus integration, payload validation, and state synchronization. @@ -67,7 +67,7 @@ The `consume engine` command: | Nomenclature | | | -------------- | ------------------ | | Command | `consume rlp` | -| Simulator | `eest/consume-rlp` | +| Simulator | `eels/consume-rlp` | | Fixture format | `blockchain_test` | The RLP consumption method tests execution clients by providing them with RLP-encoded blocks to load upon startup, similar to the block import process during historical synchronization. This method tests the client's core block processing logic without the overhead of network protocols. @@ -103,15 +103,15 @@ The `consume sync` command: ## Engine vs RLP Simulator -The RLP Simulator (`eest/consume-rlp`) and the Engine Simulator (`eest/consume-engine`) should be seen as complimentary to one another. Although they execute the same underlying EVM test cases, the block validation logic is executed via different client code paths (using different [fixture formats](./test_formats/index.md)). Therefore, ideally, **both simulators should be executed for full coverage**. +The RLP Simulator (`eels/consume-rlp`) and the Engine Simulator (`eels/consume-engine`) should be seen as complimentary to one another. Although they execute the same underlying EVM test cases, the block validation logic is executed via different client code paths (using different [fixture formats](./test_formats/index.md)). Therefore, ideally, **both simulators should be executed for full coverage**. ### Code Path Choices -Clients consume fixtures in the `eest/consume-engine` simulator via the Engine API's `EngineNewPayloadv*` endpoint; a natural way to validate, respectively invalidate, block payloads. In this case, there is no flexibility in the choice of code path - it directly harnesses mainnet client functionality. The `eest/consume-rlp` Simulator, however, allows clients more freedom, as the rlp-encoded blocks are imported upon client startup. Clients are recommended to try and hook the block import into the code path used for historical syncing. +Clients consume fixtures in the `eels/consume-engine` simulator via the Engine API's `EngineNewPayloadv*` endpoint; a natural way to validate, respectively invalidate, block payloads. In this case, there is no flexibility in the choice of code path - it directly harnesses mainnet client functionality. The `eels/consume-rlp` Simulator, however, allows clients more freedom, as the rlp-encoded blocks are imported upon client startup. Clients are recommended to try and hook the block import into the code path used for historical syncing. ### Differences -| | `eest/consume-rlp` | `eest/consume-engine` | +| | `eels/consume-rlp` | `eels/consume-engine` | | ----------------------- | ----------------------------------------------------- | ------------------------------------------------------------------ | | **Fixture Format Used** | [`BlockchainTest`](./test_formats/blockchain_test.md) | [`BlockchainTestEngine`](./test_formats/blockchain_test_engine.md) | | **Fork support** | All forks (including pre-merge) | Post-merge forks only (Paris+) | @@ -128,9 +128,9 @@ Clients consume fixtures in the `eest/consume-engine` simulator via the Engine A See [Execute Command](./execute/index.md). -## Two Methods to Run EEST Simulators +## Two Methods to Run EELS Simulators -Many of the methods use the Hive Testing Environment to interact with clients and run tests against them. These methods are also called Hive simulators. While Hive is always necessary to run simulators, they can be called in two different ways. Both of these commands execute the same simulator code, but in different environments, we take the example of the `eest/consume-engine` simulator: +Many of the methods use the Hive Testing Environment to interact with clients and run tests against them. These methods are also called Hive simulators. While Hive is always necessary to run simulators, they can be called in two different ways. Both of these commands execute the same simulator code, but in different environments, we take the example of the `eels/consume-engine` simulator: -1. `./hive --sim=eest/consume-engine` is a standalone command that installs EEST and the `consume` command in a dockerized container managed by Hive. This is the standard method to execute EEST [fixture releases](./releases.md) against clients in CI environments and is the method to generate the results at [hive.ethpandaops.io](https://hive.ethpandaops.io). See [Hive](./hive/index.md) and its [Common Options](./hive/common_options.md) for help with this method. -2. `uv run consume engine` requires the user to clone and [install EEST](../getting_started/installation.md) and start a Hive server in [development mode](./hive/dev_mode.md). In this case, the simulator runs on the native system and communicate to the client via the Hive API. This is particularly useful during test development as fixtures on the local disk can be specified via `--input=fixtures/`. As the simulator runs natively, it is easy to drop into a debugger and inspect the simulator or client container state. See [Hive Developer Mode](./hive/dev_mode.md) for help with this method. +1. `./hive --sim=eels/consume-engine` is a standalone command that installs and configures execution-specs and its `consume` command in a dockerized container managed by Hive. This is the standard method to execute EEST [fixture releases](./releases.md) against clients in CI environments and is the method to generate the results at [hive.ethpandaops.io](https://hive.ethpandaops.io). See [Hive](./hive/index.md) and its [Common Options](./hive/common_options.md) for help with this method. +2. `uv run consume engine` requires the user to clone and [configure execution-specs](../getting_started/installation.md) and start a Hive server in [development mode](./hive/dev_mode.md). In this case, the simulator runs on the native system and communicate to the client via the Hive API. This is particularly useful during test development as fixtures on the local disk can be specified via `--input=fixtures/`. As the simulator runs natively, it is easy to drop into a debugger and inspect the simulator or client container state. See [Hive Developer Mode](./hive/dev_mode.md) for help with this method. diff --git a/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md b/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md new file mode 100644 index 0000000000..3e2730e2ec --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md @@ -0,0 +1,93 @@ +# Depth Benchmark Tests + +This directory contains tests for worst-case depth attacks on Ethereum state and account tries. + +## Scenario Description + +These benchmarks test the worst-case scenario for Ethereum clients when dealing with extremely deep state and account tries. The attack involves: + +1. **Pre-deployed contracts** with deep storage tries that maximize trie traversal costs +2. **CREATE2-based addressing** for deterministic contract addresses across test runs +3. **Optimized batched attacks** using an AttackOrchestrator contract that can execute up to 1,980 attacks per transaction +4. **Account trie depth** increased by funding auxiliary accounts that make the path deeper + +The test measures the performance impact of state root recomputation and IO when modifying deep storage slots across thousands of contracts, simulating the maximum theoretical load on the state trie. + +## Contract Sources + +- **AttackOrchestrator.sol** and **Verifier.sol**: https://gist.github.com/CPerezz/8686da933fa5c045fbdf7c31e20e6c71 +- **Pre-mined assets** (depth_*.sol, s*_acc*.json): https://github.com/CPerezz/worst_case_miner/tree/master/mined_assets + +For complete deployment setup and instructions, see the gist: https://gist.github.com/CPerezz/44d521c0f9e6adf7d84187a4f2c11978 + +## Prerequisites + +- Python with `uv` package manager +- Anvil (Ethereum node implementation) or another EVM client +- Nick's factory deployed at `0x4e59b44847b379578588920ca78fbf26c0b4956c` + +## Workflow + +### Step 1: Start the Node (Anvil in this example) + +```bash +# Start Anvil with high gas limit and auto-mining +anvil --hardfork prague --block-time 6 --steps-tracing --gas-limit 500000000 --balance 99999999999999 --port 8545 +``` + +### Step 2: Deploy Contracts + +Deploy contracts using the provided script with batched transactions: + +```bash +# Deploy contracts (example for depth 10, account depth 6) +uv run python deploy_deep_branches.py \ + --rpc-url http://localhost:8546 \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --storage-depth 10 \ + --account-depth 6 \ + --num-contracts 1000 \ + --output deployed_contracts.json +``` + +The script: +- Funds auxiliary accounts in batches +- Deploys contracts via CREATE2 for deterministic addresses +- Dynamically calculates batch sizes based on network gas limit + +### Step 3: Run Attack Test + +Execute the worst-case depth attack test: + +```bash +# Run the attack test +uv run execute remote \ + --rpc-endpoint=http://localhost:8546 \ + --rpc-seed-key=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --rpc-chain-id=31337 \ + --gas-benchmark-values 60 \ + --fork Prague \ + -m stateful \ + deep_branch_testing.py::test_worst_depth_stateroot_recomp +``` + +## Asset Downloads + +The test automatically downloads required assets from GitHub: +- `s{storage_depth}_acc{account_depth}.json` - Pre-mined CREATE2 addresses and auxiliary accounts +- `depth_{storage_depth}.sol` - Solidity contract source (used to extract deep storage slot) + +Downloaded assets are cached locally in `.cache/` directory. + +## Available Configurations + +Currently available pre-mined assets from [worst_case_miner](https://github.com/CPerezz/worst_case_miner/tree/master/mined_assets): + +| Storage Depth | Account Depth | File | +|--------------|---------------|------| +| 10 | 6 | s10_acc6.json | +| 10 | 7 | s10_acc7.json | +| 11 | 6 | s11_acc6.json | +| 11 | 7 | s11_acc7.json | + +To generate new configurations, use [worst_case_miner](https://github.com/CPerezz/worst_case_miner). diff --git a/tests/benchmark/stateful/bloatnet/depth_benchmarks/deep_branch_testing.py b/tests/benchmark/stateful/bloatnet/depth_benchmarks/deep_branch_testing.py new file mode 100644 index 0000000000..ea8d0f5a9f --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/depth_benchmarks/deep_branch_testing.py @@ -0,0 +1,508 @@ +""" +abstract: BloatNet worst-case attack benchmark for maximum SSTORE stress. + +This test implements a worst-case scenario for Ethereum block processing +that exploits the computational complexity of Patricia Merkle Trie +operations. It uses CREATE2 to deploy contracts at pre-mined addresses +with shared prefixes, maximizing trie traversal depth. + +Key features: +- Attacks pre-deployed contracts via CREATE2 address derivation +- Each contract has deep storage slots with configurable trie depth +- Executes optimized attack bytecode with multiple SSTORE operations +- Respects Fusaka tx gas limit (16M gas) and fills blocks fully +- Verifies attack success via a verification transaction at block end + +Test parameters: +- storage_depth: Depth of storage slots (e.g., 10, 11) +- account_depth: Account address prefix sharing depth (e.g., 6, 7) +- NUM_CONTRACTS: Dynamically computed based on gas_benchmark_value +- Gas per attack call: ~8,050 gas (~2,742 overhead + 5,300 forwarded) + +Contract sources: +- AttackOrchestrator.sol and Verifier.sol: + https://gist.github.com/CPerezz/8686da933fa5c045fbdf7c31e20e6c71 +- Pre-mined assets (depth_*.sol, s*_acc*.json): + https://github.com/CPerezz/worst_case_miner/tree/master/mined_assets +""" + +import json +import re +import urllib.error +import urllib.request +import warnings +from pathlib import Path +from typing import Any + +import pytest +import rlp # type: ignore[import-untyped] +from eth_utils import keccak +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Transaction, +) + +# Maximum gas per transaction (Fusaka EIP limit) +MAX_GAS_PER_TX = 16_000_000 + +# Nick's deterministic deployer address (must be pre-deployed in execute mode) +NICK_DEPLOYER = Address("0x4e59b44847b379578588920ca78fbf26c0b4956c") + +# Gas costs for attack calculations +# Measured empirically: 1 attack = 29,340 gas, 1990 attacks = 16,025,257 gas +# Per-attack gas = (16,025,257 - 29,340) / 1989 ≈ 8,042 +# TX overhead = 29,340 - 8,042 ≈ 21,298 +TX_BASE_GAS = 21_000 +TX_CALLDATA_GAS = 1_600 # 4-byte selector + 3 uint256s +TX_OVERHEAD = TX_BASE_GAS + TX_CALLDATA_GAS # ~22,600 total + +# Per-iteration gas cost in AttackOrchestrator (measured empirically) +# Breakdown: ~2,742 overhead (cold 2600 + call 100 + loop ~42) + 5,300 fwd +GAS_PER_ATTACK = 8_050 # 8,050 for margin over measured 8,042 + +# Maximum attacks per transaction at 16M gas limit +# MAX_ATTACKS_PER_TX = floor((16,000,000 - 22,600) / 8,050) = 1,985 +MAX_ATTACKS_PER_TX = 1980 # Use 1,980 for safety margin + +# GitHub raw URL base for downloading mined assets +MINED_ASSETS_URL = ( + "https://raw.githubusercontent.com/CPerezz/" + "worst_case_miner/master/mined_assets" +) + +# Gas limits for deployment and verification transactions +ORCHESTRATOR_DEPLOY_GAS = 2_000_000 +VERIFIER_DEPLOY_GAS = 500_000 +VERIFICATION_GAS = 100_000 + +# Transaction fee parameters (in wei) +MAX_FEE_PER_GAS = 10_000_000_000 # 10 gwei +MAX_PRIORITY_FEE_PER_GAS = 1_000_000_000 # 1 gwei + +# Initial balance for the deployer EOA +DEPLOYER_INITIAL_BALANCE = 10_000 * 10**18 # 10,000 ETH + +# Arbitrary value written to storage slots during attack +DEFAULT_ATTACK_VALUE = 42 + +# AttackOrchestrator deployment bytecode (without constructor args) +# Compiled with: solc --bin --optimize --optimize-runs 200 --metadata-hash none +# Source: https://gist.github.com/CPerezz/8686da933fa5c045fbdf7c31e20e6c71 +ATTACK_ORCHESTRATOR_BYTECODE = bytes.fromhex( + "60c060405234801561000f575f5ffd5b5060405161025138038061025183398101604081905261002e91" + "610044565b6001600160a01b0390911660805260a05261007b565b5f5f60408385031215610055575f5f" + "fd5b82516001600160a01b038116811461006b575f5ffd5b6020939093015192949293505050565b6080" + "5160a0516101ab6100a65f395f81816059015260f001525f81816093015260cf01526101ab5ff3fe6080" + "60405234801561000f575f5ffd5b506004361061003f575f3560e01c8063407f85f814610041578063db" + "4c545e14610054578063efdee94f1461008e575b005b61003f61004f366004610175565b6100cd565b61" + "007b7f00000000000000000000000000000000000000000000000000000000000000008156" + "5b6040519081526020015b60405180910390f35b6100b57f000000000000000000000000000000000000" + "0000000000000000000000000000815" + "65b6040516001600160a01b039091168152602001610085565b7f00000000000000000000000000000000" + "000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000" + "0000000000008181855b8581101561016b575f60ff81538360601b6001820152816015820152826035" + "820152506001600160a01b0360555f2016608063326ec48d60e11b81528960048201525f5f6024835f86" + "6114b4f1505050600101610113565b5050505050505050565b5f5f5f60608486031215610187575f5ffd" + "5b50508135936020830135935060409092013591905056fea164736f6c634300081e000a" +) + +# Verifier deployment bytecode +# Compiled with: solc --bin --optimize --optimize-runs 200 --metadata-hash none +# Source: https://gist.github.com/CPerezz/8686da933fa5c045fbdf7c31e20e6c71 +VERIFIER_BYTECODE = bytes.fromhex( + "6080604052348015600e575f5ffd5b5061018b8061001c5f395ff3fe608060405234801561000f575f5f" + "fd5b5060043610610029575f3560e01c80636704fe9f1461002d575b5f5ffd5b61004061003b36600461" + "011c565b610054565b604051901515815260200160405180910390f35b60408051600481526024810182" + "526020810180516001600160e01b0316633bdadbf360e11b17905290515f91829182916001600160a01b" + "038716916100999190610151565b5f60405180830381855afa9150503d805f81146100d1576040519150" + "601f19603f3d011682016040523d82523d5f602084013e6100d6565b606091505b50915091508115806100" + "ea57508051602014155b156100f9575f92505050610116565b5f8180602001905181019061010e919061" + "0167565b851493505050505b92915050565b5f5f6040838503121561012d575f5ffd5b82356001600160" + "a01b0381168114610143575f5ffd5b946020939093013593505050565b5f82518060208501845e5f9201" + "91825250919050565b5f60208284031215610177575f5ffd5b505191905056fea164736f6c634300081e" + "000a" +) + + +def download_mined_asset(filename: str) -> str: + """ + Download a mined asset file from GitHub if not cached locally. + + Args: + filename: Name of the file (e.g., "s9_acc5.json" or "depth_9.sol") + + Returns: + str: Content of the file + + """ + cache_dir = Path(__file__).parent / ".cache" + cache_dir.mkdir(exist_ok=True) + cache_path = cache_dir / filename + + if cache_path.exists(): + with open(cache_path, "r") as f: + return f.read() + + url = f"{MINED_ASSETS_URL}/{filename}" + print(f" Downloading {filename} from {url}...") + + try: + with urllib.request.urlopen(url, timeout=30) as response: + content = response.read().decode("utf-8") + # Cache the file locally + with open(cache_path, "w") as f: + f.write(content) + return content + except urllib.error.URLError as e: + raise RuntimeError(f"Failed to download {filename}: {e}") from e + + +def load_create2_data( + storage_depth: int, account_depth: int +) -> dict[str, Any]: + """ + Load the pre-mined CREATE2 data for given depth parameters. + + Downloads from GitHub if not available locally. + + Args: + storage_depth: Depth of storage slots in the contract (e.g., 9) + account_depth: Depth of account address prefix sharing (e.g., 5) + + Returns dict with: + - init_code_hash: Expected hash for reproducible compilation + - deployer: Nick's deployer address + - contracts: List of dicts with 'salt' and 'auxiliary_accounts' + + """ + json_filename = f"s{storage_depth}_acc{account_depth}.json" + content = download_mined_asset(json_filename) + return json.loads(content) + + +def get_deep_slot_from_sol(storage_depth: int) -> int: + """ + Extract the deepest storage slot from the contract source. + + Downloads the .sol file from GitHub if not available locally. + + Args: + storage_depth: Storage depth to find the corresponding .sol file + + Returns: + int: The deepest storage slot value + + """ + sol_filename = f"depth_{storage_depth}.sol" + content = download_mined_asset(sol_filename) + + # Find all sstore operations in the constructor + sstore_pattern = r"sstore\((0x[0-9a-fA-F]+),\s*1\)" + sstores = re.findall(sstore_pattern, content) + if sstores: + # The last sstore is the deepest slot + return int(sstores[-1], 16) + else: + raise RuntimeError(f"No sstore operations in {sol_filename}") + + +def calculate_create2_address( + deployer_addr: Address, salt: int, init_code_hash: bytes +) -> Address: + """ + Calculate CREATE2 address for a given salt and init code hash. + + Args: + deployer_addr: The deployer contract address (Nick's factory) + salt: The salt value (monotonically increasing integer) + init_code_hash: The keccak256 hash of the init code + + Returns: + Address: The CREATE2 address + + """ + deployer_bytes = bytes.fromhex(str(deployer_addr)[2:]) + salt_bytes = salt.to_bytes(32, "big") + + # CREATE2 preimage: 0xff ++ deployer ++ salt ++ keccak256(init_code) + preimage = b"\xff" + deployer_bytes + salt_bytes + init_code_hash + address_bytes = keccak(preimage)[12:] + return Address("0x" + address_bytes.hex()) + + +def calculate_num_contracts(gas_benchmark_value: int) -> int: + """ + Calculate the number of contracts to attack based on gas budget. + + The total gas budget needs to cover: + - Orchestrator deployment transaction (~2M gas) + - Verifier deployment transaction (~500k gas) + - Attack transactions (up to 16M gas each, ~2510 attacks per tx) + - Verification transaction (~100k gas) + + Args: + gas_benchmark_value: Total gas budget for the block(s) + + Returns: + int: Number of contracts that can be attacked + + """ + # Reserve gas for orchestrator, verifier deployment, and verification + reserved_gas = ( + ORCHESTRATOR_DEPLOY_GAS + VERIFIER_DEPLOY_GAS + VERIFICATION_GAS + ) + + available_gas = gas_benchmark_value - reserved_gas + if available_gas <= 0: + return 1 + + # Calculate how many attacks fit in the available gas + # Each 16M tx can fit MAX_ATTACKS_PER_TX attacks + num_full_txs = available_gas // MAX_GAS_PER_TX + total_attacks = num_full_txs * MAX_ATTACKS_PER_TX + + # Add partial transaction attacks if there's remaining gas + remaining_gas = available_gas - (num_full_txs * MAX_GAS_PER_TX) + if remaining_gas > TX_OVERHEAD: + additional_attacks = (remaining_gas - TX_OVERHEAD) // GAS_PER_ATTACK + total_attacks += additional_attacks + + return max(1, total_attacks) + + +@pytest.mark.valid_from("Prague") +@pytest.mark.parametrize( + "storage_depth,account_depth", + [ + (10, 6), # From worst_case_miner/mined_assets + ], +) +def test_worst_depth_stateroot_recomp( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + _fork: Fork, + gas_benchmark_value: int, + storage_depth: int, + account_depth: int, +) -> None: + """ + BloatNet worst-case SSTORE attack benchmark with pre-deployed contracts. + + This test: + 1. Derives CREATE2 addresses from init_code_hash + Nick's deployer + 2. Deploys AttackOrchestrator that calls attack() on each target + 3. Fills blocks with 16M gas transactions attacking contracts + 4. Adds a verification transaction at the end to confirm success + + Args: + blockchain_test: The blockchain test filler + pre: Pre-state allocation + _fork: The fork to test on (unused, provided by pytest fixture) + gas_benchmark_value: Gas budget for benchmark + storage_depth: Depth of storage slots in the contract + account_depth: Account address prefix sharing depth + + """ + # Dynamically calculate number of contracts based on gas budget + num_contracts = calculate_num_contracts(gas_benchmark_value) + + print("\nTesting with pre-deployed contracts:") + print(f" Storage depth: {storage_depth}") + print(f" Account depth: {account_depth}") + print(f" Gas benchmark value: {gas_benchmark_value:,}") + print(f" Calculated NUM_CONTRACTS: {num_contracts}") + + # Load the CREATE2 data to get the init code hash + create2_data = load_create2_data(storage_depth, account_depth) + init_code_hash_hex = create2_data.get("init_code_hash") + if not init_code_hash_hex: + json_name = f"s{storage_depth}_acc{account_depth}.json" + raise ValueError(f"No init_code_hash found in {json_name}") + + # Remove 0x prefix if present and convert to bytes + if init_code_hash_hex.startswith("0x"): + init_code_hash_hex = init_code_hash_hex[2:] + init_code_hash = bytes.fromhex(init_code_hash_hex) + + # Verify we have enough contracts in the JSON + available_contracts = len(create2_data.get("contracts", [])) + if available_contracts == 0: + json_name = f"s{storage_depth}_acc{account_depth}.json" + raise ValueError(f"No contracts available in {json_name}") + if num_contracts > available_contracts: + warnings.warn( + f"Requested {num_contracts} contracts but only " + f"{available_contracts} available, using {available_contracts}", + stacklevel=2, + ) + num_contracts = available_contracts + + print(f" Final NUM_CONTRACTS: {num_contracts}") + + # Create an EOA with funds for the deployer + deployer_eoa = pre.fund_eoa(amount=DEPLOYER_INITIAL_BALANCE) + + # Get the deep storage slot for verification (downloads .sol if needed) + deep_slot = get_deep_slot_from_sol(storage_depth) + print(f" Deep storage slot: {hex(deep_slot)}") + + # ABI encode constructor parameters for AttackOrchestrator + # Constructor: constructor(address _deployer, bytes32 _initCodeHash) + nick_deployer_bytes = bytes(NICK_DEPLOYER) + constructor_args = nick_deployer_bytes.rjust(32, b"\x00") + init_code_hash + orchestrator_init_code = ATTACK_ORCHESTRATOR_BYTECODE + constructor_args + + print(f" Using init code hash from JSON: 0x{init_code_hash.hex()}") + + # Calculate orchestrator address (nonce 0) + deployer_addr_bytes = bytes.fromhex(str(deployer_eoa)[2:]) + orch_addr_bytes = keccak(rlp.encode([deployer_addr_bytes, 0]))[12:] + orchestrator_address = Address("0x" + orch_addr_bytes.hex()) + print(f" Orchestrator will be deployed at: {orchestrator_address}") + + # Calculate verifier address (nonce 1) + verifier_address_bytes = keccak(rlp.encode([deployer_addr_bytes, 1]))[12:] + verifier_address = Address("0x" + verifier_address_bytes.hex()) + print(f" Verifier will be deployed at: {verifier_address}") + + # Create deployment transactions + orchestrator_deploy_tx = Transaction( + to=None, + data=orchestrator_init_code, + gas_limit=ORCHESTRATOR_DEPLOY_GAS, + sender=deployer_eoa, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS, + ) + + verifier_deploy_tx = Transaction( + to=None, + data=VERIFIER_BYTECODE, + gas_limit=VERIFIER_DEPLOY_GAS, + sender=deployer_eoa, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS, + ) + + # Build attack transactions + attack_txs: list[Transaction] = [] + attack_value = DEFAULT_ATTACK_VALUE + + for batch_start in range(0, num_contracts, MAX_ATTACKS_PER_TX): + batch_end = min(batch_start + MAX_ATTACKS_PER_TX, num_contracts) + batch_size = batch_end - batch_start + + # Create calldata: attack(value, startIndex, endIndex) + # Selector: 0x407f85f8 = attack(uint256,uint256,uint256) + calldata = ( + bytes.fromhex("407f85f8") + + attack_value.to_bytes(32, "big") + + batch_start.to_bytes(32, "big") + + batch_end.to_bytes(32, "big") + ) + + # Calculate gas for this batch - aim for close to 16M + batch_gas = min( + MAX_GAS_PER_TX, batch_size * GAS_PER_ATTACK + TX_OVERHEAD + ) + + attack_tx = Transaction( + to=orchestrator_address, + gas_limit=batch_gas, + sender=deployer_eoa, + data=calldata, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS, + ) + attack_txs.append(attack_tx) + + print(f" Created {len(attack_txs)} attack transactions") + + # Calculate the last attacked contract address for verification + last_contract_salt = num_contracts - 1 + last_contract_address = calculate_create2_address( + NICK_DEPLOYER, last_contract_salt, init_code_hash + ) + print( + f" Last contract (salt={last_contract_salt}): {last_contract_address}" + ) + + # Create verification transaction + # Verifier.verify(address target, uint256 expectedValue) + # Selector: bytes4(keccak256("verify(address,uint256)")) = 0x6be45db7 + verify_calldata = ( + bytes.fromhex("6be45db7") # verify(address,uint256) selector + + bytes.fromhex(str(last_contract_address)[2:]).rjust(32, b"\x00") + + attack_value.to_bytes(32, "big") + ) + + verification_tx = Transaction( + to=verifier_address, + gas_limit=VERIFICATION_GAS, + sender=deployer_eoa, + data=verify_calldata, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS, + ) + + # Build blocks - pack transactions to maximize block gas usage + # Order: orchestrator deploy, verifier deploy, attack txs, verification + all_txs = ( + [orchestrator_deploy_tx, verifier_deploy_tx] + + attack_txs + + [verification_tx] + ) + + blocks: list[Block] = [] + current_block_txs: list[Transaction] = [] + current_block_gas = 0 + max_block_gas = gas_benchmark_value + + for tx in all_txs: + tx_gas = tx.gas_limit if tx.gas_limit else 21000 + + # Ensure no single tx exceeds 16M gas limit + if tx_gas > MAX_GAS_PER_TX: + raise ValueError( + f"Tx exceeds MAX_GAS_PER_TX ({MAX_GAS_PER_TX:,}): {tx_gas:,}" + ) + + # Check if adding this tx would exceed block gas limit + if current_block_gas + tx_gas > max_block_gas and current_block_txs: + blocks.append(Block(txs=current_block_txs)) + n_txs = len(current_block_txs) + print(f" Block {len(blocks)}: {n_txs} txs, {current_block_gas:,}") + current_block_txs = [tx] + current_block_gas = tx_gas + else: + current_block_txs.append(tx) + current_block_gas += tx_gas + + # Add the last block + if current_block_txs: + blocks.append(Block(txs=current_block_txs)) + n_txs = len(current_block_txs) + print(f" Block {len(blocks)}: {n_txs} txs, {current_block_gas:,}") + + print(f" Total blocks: {len(blocks)}") + + # Post-state verification - minimal check on the last attacked contract + post = { + # Verify the last attacked contract's deep slot was updated + last_contract_address: Account( + storage={ + deep_slot: attack_value, + } + ), + } + + blockchain_test( + pre=pre, + blocks=blocks, + post=post, + )