diff --git a/.github/configs/fork-ranges.yaml b/.github/configs/fork-ranges.yaml index dcb4ea65ba1..4195d3b853d 100644 --- a/.github/configs/fork-ranges.yaml +++ b/.github/configs/fork-ranges.yaml @@ -13,9 +13,6 @@ - label: osaka from: Osaka until: Osaka -- label: bpo - from: BPO1 - until: BPO2 - label: amsterdam from: Amsterdam until: Amsterdam diff --git a/.github/configs/hive/latest.yaml b/.github/configs/hive/latest.yaml index 0ad950ce6dd..6dcfd75e351 100644 --- a/.github/configs/hive/latest.yaml +++ b/.github/configs/hive/latest.yaml @@ -2,7 +2,7 @@ # Geth (Go-Ethereum) # GHCR: https://ghcr.io/chetna-mittal/geth -- client: go-ethereum-gnosis +- client: go-ethereum nametag: "" build_args: baseimage: ghcr.io/chetna-mittal/geth @@ -10,7 +10,7 @@ # Nethermind # Docker Hub: https://hub.docker.com/r/nethermind/nethermind -- client: nethermind-gnosis +- client: nethermind nametag: "" build_args: baseimage: docker.io/nethermind/nethermind @@ -18,7 +18,7 @@ # Reth (Gnosis) # GHCR: https://ghcr.io/gnosischain/reth_gnosis -- client: reth-gnosis +- client: reth nametag: "" build_args: baseimage: ghcr.io/gnosischain/reth_gnosis @@ -26,7 +26,7 @@ # Erigon # Docker Hub: https://hub.docker.com/r/erigontech/erigon -- client: erigon-gnosis +- client: erigon nametag: "" build_args: baseimage: docker.io/erigontech/erigon diff --git a/.github/workflows/eest_hive_gnosis.yaml b/.github/workflows/eest_hive_gnosis.yaml index 042d05b04da..f18ccb91e47 100644 --- a/.github/workflows/eest_hive_gnosis.yaml +++ b/.github/workflows/eest_hive_gnosis.yaml @@ -6,11 +6,11 @@ on: fork: description: "Fork passed to --fork" required: true - default: "Osaka" + default: "Amsterdam" client: description: "Client passed to --client" required: true - default: "nethermind-gnosis" + default: "nethermind" test_dir: description: "Optional test directory under tests/ (e.g. osaka). Leave empty to fill all tests." required: false diff --git a/.github/workflows/eest_hive_gnosis_multi_client.yaml b/.github/workflows/eest_hive_gnosis_multi_client.yaml index bbda1092293..e01fe0f179e 100644 --- a/.github/workflows/eest_hive_gnosis_multi_client.yaml +++ b/.github/workflows/eest_hive_gnosis_multi_client.yaml @@ -6,7 +6,7 @@ on: fork: description: "Fork passed to --fork" required: true - default: "Osaka" + default: "Amsterdam" test_dir: description: "Optional test directory under tests/ (e.g. osaka). Leave empty to fill all tests." required: false @@ -52,10 +52,10 @@ jobs: fail-fast: false matrix: client: - - reth-gnosis - - go-ethereum-gnosis - - nethermind-gnosis - - erigon-gnosis + - reth + - go-ethereum + - nethermind + - erigon steps: - name: Checkout the repository diff --git a/.github/workflows/eest_hive_gnosis_multi_client_until.yaml b/.github/workflows/eest_hive_gnosis_multi_client_until.yaml index a6b1afadc36..fea312e5e69 100644 --- a/.github/workflows/eest_hive_gnosis_multi_client_until.yaml +++ b/.github/workflows/eest_hive_gnosis_multi_client_until.yaml @@ -52,10 +52,10 @@ jobs: fail-fast: false matrix: client: - - reth-gnosis - - go-ethereum-gnosis - - nethermind-gnosis - - erigon-gnosis + - reth + - go-ethereum + - nethermind + - erigon steps: - name: Checkout the repository diff --git a/.github/workflows/eest_hive_matrix.yaml b/.github/workflows/eest_hive_matrix.yaml index 1d313420c1b..c9a4f48475a 100644 --- a/.github/workflows/eest_hive_matrix.yaml +++ b/.github/workflows/eest_hive_matrix.yaml @@ -6,15 +6,15 @@ on: fork: description: "Fork passed to --fork" required: true - default: "Osaka" + default: "Amsterdam" client: description: "Client passed to --client" required: true - default: "reth-gnosis" + default: "nethermind" test_dirs: description: "Comma-separated list of EIP numbers or directory names" required: true - default: "osaka" + default: "amsterdam" jobs: # Prepare matrix from input diff --git a/.github/workflows/hive-consume.yaml b/.github/workflows/hive-consume.yaml index 4f85eccdcc6..47e39fd79f1 100644 --- a/.github/workflows/hive-consume.yaml +++ b/.github/workflows/hive-consume.yaml @@ -33,9 +33,9 @@ on: required: false default: "ghcr.io/chetna-mittal/geth:latest docker.io/alpine:latest docker.io/library/golang:1-alpine" client: - description: "Hive client to test (e.g., go-ethereum-gnosis, reth-gnosis)" + description: "Hive client to test (e.g., go-ethereum, reth)" required: false - default: "go-ethereum-gnosis" + default: "go-ethereum" client_file: description: "Client config file name under .github/configs/hive/ (e.g., latest.yaml, master.yaml)" required: false @@ -48,10 +48,10 @@ on: type: string default: "ghcr.io/chetna-mittal/geth:latest docker.io/alpine:latest docker.io/library/golang:1-alpine" client: - description: "Hive client to test (e.g., go-ethereum-gnosis, reth-gnosis)" + description: "Hive client to test (e.g., go-ethereum, reth)" required: false type: string - default: "go-ethereum-gnosis" + default: "go-ethereum" client_file: description: "Client config file name under .github/configs/hive/ (e.g., latest.yaml, master.yaml)" required: false @@ -84,7 +84,7 @@ jobs: needs: cache-docker-images runs-on: ubuntu-latest env: - CLIENT: ${{ inputs.client || 'go-ethereum-gnosis' }} + CLIENT: ${{ inputs.client || 'go-ethereum' }} CLIENT_FILE: ${{ inputs.client_file || 'latest.yaml' }} strategy: fail-fast: true diff --git a/.github/workflows/hive-execute.yaml b/.github/workflows/hive-execute.yaml index bc170d73b76..2abbb0c3a37 100644 --- a/.github/workflows/hive-execute.yaml +++ b/.github/workflows/hive-execute.yaml @@ -86,7 +86,7 @@ jobs: id: start-hive uses: ./execution-specs/.github/actions/start-hive-dev with: - clients: go-ethereum-gnosis + clients: go-ethereum client-file: execution-specs/.github/configs/hive/latest.yaml hive-path: hive timeout: "180" diff --git a/.github/workflows/hive-fusaka.yaml b/.github/workflows/hive-fusaka.yaml deleted file mode 100644 index 49c16dfc543..00000000000 --- a/.github/workflows/hive-fusaka.yaml +++ /dev/null @@ -1,200 +0,0 @@ -name: Hive - Gnosis (Fusaka) - -on: - schedule: - - cron: '0 0 * * *' # Run every day at 00:00 UTC - workflow_dispatch: - inputs: - client: - type: string - default: '"go-ethereum-gnosis","reth-gnosis","nethermind-gnosis","erigon-gnosis"' - description: Comma-separated list of clients to test e.g. go-ethereum-gnosis, reth-gnosis, nethermind-gnosis, erigon-gnosis - simulator: - type: string - default: >- - "gnosis/eels/consume-engine", - "gnosis/eels/consume-rlp", - "gnosis/eels/consume-sync" - description: >- - Comma-separated list of simulators to test - e.g. gnosis/eels/consume-engine, gnosis/eels/consume-rlp, gnosis/eels/consume-sync - hive_version: - type: string - default: gnosischain/hive@master - description: GitHub repository and tag for hive (repo@tag) - client_source: - type: choice - description: >- - How client images should be sourced. - 'git' will use the github repo and tag (See client_repos). - 'docker' will use the docker registry and tag (See client_images). - options: - - docker - - git - client_repos: - type: string - default: | - { - "geth": "gnosischain/go-ethereum@v1.16.8-gc", - "reth": "gnosischain/reth_gnosis@master", - "nethermind": "NethermindEth/nethermind@master", - "erigon": "erigontech/erigon@main" - } - description: 'JSON object containing client versions in format {"client": "repo@tag", ...}' - client_images: - type: string - default: | - { - "geth": "ghcr.io/gnosischain/geth:latest", - "reth": "ghcr.io/gnosischain/reth_gnosis:latest", - "nethermind": "nethermind/nethermind:1.36.1", - "erigon": "erigontech/erigon:latest" - } - description: 'JSON object containing client docker images in format {"client": "registry:tag", ...}' - -env: - GOPROXY: "${{ vars.GOPROXY }}" - GCS_BUCKET: gnosis-hive-ui-staging - GCS_PATH: fusaka - GCS_PUBLIC_URL: https://storage.googleapis.com/gnosis-hive-ui-staging/fusaka - INSTALL_RCLONE_VERSION: v1.68.2 - EELS_BUILD_ARG_FIXTURES: https://github.com/gnosischain/execution-spec-tests/releases/download/v0.1.0/fixtures_osaka.tar.gz - EELS_BUILD_ARG_BRANCH: forks/amsterdam - # Flags used for all simulators - GLOBAL_EXTRA_FLAGS: >- - --client.checktimelimit=300s - --docker.buildoutput - # Flags used for the gnosis/eels/consume-engine simulator - EELS_ENGINE_FLAGS: >- - --sim.parallelism=6 - --sim.buildarg fixtures=${EELS_BUILD_ARG_FIXTURES} - --sim.buildarg branch=${EELS_BUILD_ARG_BRANCH} - --sim.loglevel=3 - --sim.limit=.*Osaka.* - # Flags used for the gnosis/eels/consume-rlp simulator - EELS_RLP_FLAGS: >- - --sim.parallelism=6 - --sim.buildarg fixtures=${EELS_BUILD_ARG_FIXTURES} - --sim.buildarg branch=${EELS_BUILD_ARG_BRANCH} - --sim.loglevel=3 - --sim.limit=.*Osaka.* - -jobs: - prepare: - runs-on: ubuntu-latest - outputs: - hive_repo: >- - ${{ - steps.client_config_schedule.outputs.hive_repo || - steps.client_config_dispatch.outputs.hive_repo - }} - hive_tag: >- - ${{ - steps.client_config_schedule.outputs.hive_tag || - steps.client_config_dispatch.outputs.hive_tag - }} - client_config: >- - ${{ - steps.client_config_schedule.outputs.client_config || - steps.client_config_dispatch.outputs.client_config - }} - steps: - - uses: gnosischain/hive-github-action/helpers/client-config@9f65ec1bd266757a681c38d38b7fc9e341b08f0a - if: github.event_name == 'schedule' - name: 'Client config: schedule' - id: client_config_schedule - with: - client_source: 'git' - hive_version: 'gnosischain/hive@master' - goproxy: ${{ env.GOPROXY }} - - - uses: gnosischain/hive-github-action/helpers/client-config@9f65ec1bd266757a681c38d38b7fc9e341b08f0a - if: github.event_name == 'workflow_dispatch' - name: 'Client config: workflow_dispatch' - id: client_config_dispatch - with: - client_repos: ${{ inputs.client_repos }} - client_images: ${{ inputs.client_images }} - client_source: ${{ inputs.client_source }} - hive_version: ${{ inputs.hive_version }} - goproxy: ${{ env.GOPROXY }} - - test: - timeout-minutes: 2160 - needs: prepare - if: >- - github.event_name == 'schedule' || - !contains(inputs.simulator, 'consume-sync') || - (contains(inputs.simulator, 'consume-engine') || contains(inputs.simulator, 'consume-rlp')) - runs-on: ubuntu-latest - concurrency: - group: >- - ${{ github.head_ref || inputs }}-${{ matrix.client }}-${{ matrix.simulator }} - strategy: - fail-fast: false - matrix: - client: >- - ${{ fromJSON(format('[{0}]', inputs.client || ' - "go-ethereum-gnosis", - "reth-gnosis", - "nethermind-gnosis", - "erigon-gnosis" - '))}} - simulator: >- - ${{ fromJSON(format('[{0}]', inputs.simulator || ' - "gnosis/eels/consume-engine", - "gnosis/eels/consume-rlp" - '))}} - exclude: - - simulator: 'gnosis/eels/consume-sync' - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: gnosischain/hive-github-action@1d88652e508626aaefa085d830ea05f422384300 - with: - hive_repository: ${{ needs.prepare.outputs.hive_repo }} - hive_version: ${{ needs.prepare.outputs.hive_tag }} - client: ${{ matrix.client }} - simulator: ${{ matrix.simulator }} - client_config: ${{ needs.prepare.outputs.client_config }} - extra_flags: >- - ${{ env.GLOBAL_EXTRA_FLAGS }} - ${{ matrix.simulator == 'gnosis/eels/consume-engine' && env.EELS_ENGINE_FLAGS || '' }} - ${{ matrix.simulator == 'gnosis/eels/consume-rlp' && env.EELS_RLP_FLAGS || '' }} - gcs_upload: true - gcs_bucket: ${{ env.GCS_BUCKET }} - gcs_path: ${{ env.GCS_PATH }} - gcs_public_url: ${{ env.GCS_PUBLIC_URL }} - rclone_config: ${{ secrets.HIVE_RCLONE_CONFIG }} - rclone_version: ${{ env.INSTALL_RCLONE_VERSION }} - workflow_artifact_upload: true - website_upload: true - - test-consume-sync-matrix: - timeout-minutes: 2160 - needs: prepare - runs-on: ubuntu-latest - concurrency: - group: ${{ github.head_ref || inputs }}-sync-matrix - if: contains(inputs.simulator || 'gnosis/eels/consume-sync', 'gnosis/eels/consume-sync') - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: gnosischain/hive-github-action@1d88652e508626aaefa085d830ea05f422384300 - with: - hive_repository: ${{ needs.prepare.outputs.hive_repo }} - hive_version: ${{ needs.prepare.outputs.hive_tag }} - client: >- - ${{ - inputs.client || '"go-ethereum-gnosis","reth-gnosis","nethermind-gnosis","erigon-gnosis"' - }} - simulator: 'gnosis/eels/consume-sync' - client_config: ${{ needs.prepare.outputs.client_config }} - extra_flags: >- - ${{ env.GLOBAL_EXTRA_FLAGS }} - gcs_upload: true - gcs_bucket: ${{ env.GCS_BUCKET }} - gcs_path: ${{ env.GCS_PATH }} - gcs_public_url: ${{ env.GCS_PUBLIC_URL }} - rclone_config: ${{ secrets.HIVE_RCLONE_CONFIG }} - rclone_version: ${{ env.INSTALL_RCLONE_VERSION }} - workflow_artifact_upload: true - website_upload: true diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 168322671f7..d8edfb1d7b4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -94,9 +94,9 @@ jobs: - label: osaka from_fork: Osaka until_fork: Osaka - # - label: amsterdam - # from_fork: Amsterdam - # until_fork: Amsterdam + - label: amsterdam + from_fork: Amsterdam + until_fork: Amsterdam steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/CLAUDE.md b/CLAUDE.md index fafc2ddc12f..08bda7eddca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,7 +71,7 @@ There are two phases in the test pipeline: **fill** (generate fixtures from the **Core test pipeline** (`test.yaml`): Runs on PRs. Fill only — no consume. Jobs: `static`, `py3` (fill Paris->Osaka), `pypy3`, `tests_pytest_py3`, `tests_pytest_pypy3`. Setup action (`.github/actions/setup-env/`) installs Rust, build-essential, tox, and downloads geth. -**Hive integration** (`hive-consume.yaml`): Runs on PRs touching hive paths or `forks/**` pushes. Intended to consume fixtures against `go-ethereum-gnosis` via Hive (4 modes: Engine, RLP, Sync, Dev Mode). Uses `gnosischain/hive` repo (branch `master`) and `latest.yaml` client config. +**Hive integration** (`hive-consume.yaml`): Runs on PRs touching hive paths or `forks/**` pushes. Intended to consume fixtures against `go-ethereum` via Hive (4 modes: Engine, RLP, Sync, Dev Mode). Uses `gnosischain/hive` repo (branch `master`) and `latest.yaml` client config. **Manual hive workflows** (workflow_dispatch only, not automated on PRs): @@ -84,7 +84,7 @@ There are two phases in the test pipeline: **fill** (generate fixtures from the |------------------------------------|-----------------------------------|-------------------------------------------------------------------------------------| | test.yaml | PR, push to master | Core pipeline: static checks, py3 fill, pypy3 fill, framework unit tests | | test-docs.yaml | PR, push | mkdocs build, markdownlint, changelog validation | -| hive-consume.yaml | PR (hive paths), push to forks/** | Hive integration: Engine/RLP/Sync simulators + Dev Mode against go-ethereum-gnosis | +| hive-consume.yaml | PR (hive paths), push to forks/** | Hive integration: Engine/RLP/Sync simulators + Dev Mode against go-ethereum | | benchmark.yaml | push to forks/** | Gas benchmarks, fixed opcode benchmarks | | eest_hive_gnosis.yaml | manual | Fill + consume against a single Gnosis client | | eest_hive_gnosis_multi_client.yaml | manual | Fill once, then consume against 4 Gnosis clients (reth/geth/nethermind/erigon) | diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/ruleset.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/ruleset.py index 10593157fee..b7806f44cb0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/ruleset.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/ruleset.py @@ -5,10 +5,10 @@ from execution_testing.forks import ( ALL_FORKS_WITH_TRANSITIONS, Amsterdam, - BPO2ToAmsterdamAtTime15k, Byzantium, Fork, London, + OsakaToAmsterdamAtTime15k, TransitionFork, ) @@ -23,7 +23,7 @@ def ruleset_format(fork: Fork | TransitionFork) -> Dict[str, int]: if fork > London: default_values["HIVE_TERMINAL_TOTAL_DIFFICULTY"] = 0 entries = default_values | fork.ruleset() - if fork in [Amsterdam, BPO2ToAmsterdamAtTime15k]: + if fork in [Amsterdam, OsakaToAmsterdamAtTime15k]: entries.pop("HIVE_AMSTERDAM_BLOB_BASE_FEE_UPDATE_FRACTION") entries.pop("HIVE_AMSTERDAM_BLOB_MAX") entries.pop("HIVE_AMSTERDAM_BLOB_TARGET") diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_forks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_forks.py index d94f2e37376..cc6030e02c2 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_forks.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_forks.py @@ -10,8 +10,6 @@ LabeledFixtureFormat, ) from execution_testing.forks import ( - BPO1, - BPO2, Amsterdam, ArrowGlacier, Fork, @@ -20,8 +18,8 @@ get_forks, ) from execution_testing.forks.forks.transition import ( - BPO2ToAmsterdamAtTime15k, - OsakaToBPO1AtTime15k, + OsakaToAmsterdamAtTime15k, + PragueToOsakaAtTime15k, ) from execution_testing.specs import StateTest @@ -295,9 +293,9 @@ def test_fork_range({StateTest.pytest_parameter_name()}): "pytest-fill.ini", "-v", "--from", - "OsakaToBPO1AtTime15k", + "PragueToOsakaAtTime15k", "--until", - "BPO2ToAmsterdamAtTime15k", + "OsakaToAmsterdamAtTime15k", ) stdout = "\n".join(result.stdout.lines) # The header line lists the selected fork set; parse it. @@ -314,7 +312,6 @@ def test_fork_range({StateTest.pytest_parameter_name()}): # Strip ANSI codes from the last element. fork_names[-1] = fork_names[-1].split("\x1b")[0] assert Amsterdam.name() not in fork_names - assert BPO1.name() in fork_names - assert BPO2.name() in fork_names - assert BPO2ToAmsterdamAtTime15k.name() in fork_names - assert OsakaToBPO1AtTime15k.name() in fork_names + assert "Osaka" in fork_names + assert OsakaToAmsterdamAtTime15k.name() in fork_names + assert PragueToOsakaAtTime15k.name() in fork_names diff --git a/packages/testing/src/execution_testing/forks/__init__.py b/packages/testing/src/execution_testing/forks/__init__.py index cd333c559e8..5b49d2f6132 100644 --- a/packages/testing/src/execution_testing/forks/__init__.py +++ b/packages/testing/src/execution_testing/forks/__init__.py @@ -29,12 +29,10 @@ ) from .forks.transition import ( BerlinToLondonAt5, - BPO1ToBPO2AtTime15k, - BPO2ToAmsterdamAtTime15k, BPO2ToBPO3AtTime15k, BPO3ToBPO4AtTime15k, CancunToPragueAtTime15k, - OsakaToBPO1AtTime15k, + OsakaToAmsterdamAtTime15k, ParisToShanghaiAtTime15k, PragueToOsakaAtTime15k, ShanghaiToCancunAtTime15k, @@ -115,12 +113,10 @@ "Prague", "PragueToOsakaAtTime15k", "Osaka", - "OsakaToBPO1AtTime15k", + "OsakaToAmsterdamAtTime15k", "BPO1", - "BPO1ToBPO2AtTime15k", "BPO2", "BPO2ToBPO3AtTime15k", - "BPO2ToAmsterdamAtTime15k", "BPO3", "BPO3ToBPO4AtTime15k", "BPO4", diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py index d4b1b9a6e17..d6ce8384013 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py @@ -44,13 +44,15 @@ def empty_block_bal_item_count(cls) -> int: """ Return the BAL item count for an empty EIP-7928 block. - Four system contracts produce 15 items: + Six system contracts produce 17 items: EIP-4788 beacon roots: 1 address + 1 write + 1 read = 3 EIP-2935 history storage: 1 address + 1 write = 2 EIP-7002 withdrawal requests: 1 address + 4 reads = 5 EIP-7251 consolidation requests: 1 address + 4 reads = 5 + Withdrawal system call: 1 read = 1 + Block rewards system call: 1 read = 1 """ - return 15 + return 17 @classmethod def engine_execution_payload_block_access_list(cls) -> bool: diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 4e5a1f9622b..41fbea35954 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1610,7 +1610,7 @@ class BPO5( class Amsterdam( AmsterdamEIPs, - BPO2, + Osaka, deployed=False, ): """Amsterdam fork.""" diff --git a/packages/testing/src/execution_testing/forks/forks/transition.py b/packages/testing/src/execution_testing/forks/forks/transition.py index ac3e50786d4..806323939e9 100644 --- a/packages/testing/src/execution_testing/forks/forks/transition.py +++ b/packages/testing/src/execution_testing/forks/forks/transition.py @@ -2,7 +2,6 @@ from ..transition_base_fork import TransitionBaseClass, transition_fork from .forks import ( - BPO1, BPO2, BPO3, BPO4, @@ -53,27 +52,9 @@ class PragueToOsakaAtTime15k(TransitionBaseClass): pass -@transition_fork(to_fork=BPO1, from_fork=Osaka, at_timestamp=15_000) -class OsakaToBPO1AtTime15k(TransitionBaseClass): - """Osaka to BPO1 transition at Timestamp 15k.""" - - pass - - -@transition_fork(to_fork=BPO2, from_fork=BPO1, at_timestamp=15_000) -class BPO1ToBPO2AtTime15k(TransitionBaseClass): - """BPO1 to BPO2 transition at Timestamp 15k.""" - - pass - - -@transition_fork(to_fork=Amsterdam, from_fork=BPO2, at_timestamp=15_000) -class BPO2ToAmsterdamAtTime15k(TransitionBaseClass): - """BPO2 to Amsterdam transition at Timestamp 15k.""" - - # TODO: We may need to adjust which BPO Amsterdam inherits from as the - # related Amsterdam specs change over time, and before Amsterdam is - # live on mainnet. +@transition_fork(to_fork=Amsterdam, from_fork=Osaka, at_timestamp=15_000) +class OsakaToAmsterdamAtTime15k(TransitionBaseClass): + """Osaka to Amsterdam transition at Timestamp 15k.""" pass diff --git a/packages/testing/src/execution_testing/forks/tests/test_forks.py b/packages/testing/src/execution_testing/forks/tests/test_forks.py index e40f100b709..13116b3049a 100644 --- a/packages/testing/src/execution_testing/forks/tests/test_forks.py +++ b/packages/testing/src/execution_testing/forks/tests/test_forks.py @@ -28,12 +28,10 @@ ) from ..forks.transition import ( BerlinToLondonAt5, - BPO1ToBPO2AtTime15k, - BPO2ToAmsterdamAtTime15k, BPO2ToBPO3AtTime15k, BPO3ToBPO4AtTime15k, CancunToPragueAtTime15k, - OsakaToBPO1AtTime15k, + OsakaToAmsterdamAtTime15k, ParisToShanghaiAtTime15k, PragueToOsakaAtTime15k, ShanghaiToCancunAtTime15k, @@ -590,8 +588,7 @@ def test_bpo_fork() -> None: # noqa: D103 assert BPO2.bpo_fork() is True assert BPO3.bpo_fork() is True assert BPO4.bpo_fork() is True - assert OsakaToBPO1AtTime15k.fork_at().bpo_fork() is False - assert BPO1ToBPO2AtTime15k.fork_at().bpo_fork() is True + assert OsakaToAmsterdamAtTime15k.fork_at().bpo_fork() is False assert BPO2ToBPO3AtTime15k.fork_at().bpo_fork() is True assert BPO3ToBPO4AtTime15k.fork_at().bpo_fork() is True @@ -627,22 +624,21 @@ def test_transition_from_and_until(self) -> None: """Test range with transition forks as both boundaries.""" result = get_selected_fork_set( single_fork=set(), - forks_from={OsakaToBPO1AtTime15k}, # type: ignore[arg-type] - forks_until={BPO2ToAmsterdamAtTime15k}, # type: ignore[arg-type] + forks_from={PragueToOsakaAtTime15k}, # type: ignore[arg-type] + forks_until={OsakaToAmsterdamAtTime15k}, # type: ignore[arg-type] ) - assert self._normal_forks(result) == {BPO1, BPO2} + assert self._normal_forks(result) == {Osaka} assert self._transition_forks(result) == { - OsakaToBPO1AtTime15k, - BPO1ToBPO2AtTime15k, - BPO2ToAmsterdamAtTime15k, + PragueToOsakaAtTime15k, + OsakaToAmsterdamAtTime15k, } def test_transition_until_excludes_target(self) -> None: """Transition fork `--until` must not include `transitions_to()`.""" result = get_selected_fork_set( single_fork=set(), - forks_from={OsakaToBPO1AtTime15k}, # type: ignore[arg-type] - forks_until={BPO2ToAmsterdamAtTime15k}, # type: ignore[arg-type] + forks_from={PragueToOsakaAtTime15k}, # type: ignore[arg-type] + forks_until={OsakaToAmsterdamAtTime15k}, # type: ignore[arg-type] ) assert Amsterdam not in result @@ -675,13 +671,12 @@ def test_transition_from_normal_until(self) -> None: """Test transition `--from` with normal `--until`.""" result = get_selected_fork_set( single_fork=set(), - forks_from={OsakaToBPO1AtTime15k}, # type: ignore[arg-type] - forks_until={BPO2}, + forks_from={PragueToOsakaAtTime15k}, # type: ignore[arg-type] + forks_until={Osaka}, ) - assert self._normal_forks(result) == {BPO1, BPO2} - assert OsakaToBPO1AtTime15k in result - assert BPO1ToBPO2AtTime15k in result - assert BPO2ToAmsterdamAtTime15k not in result + assert self._normal_forks(result) == {Osaka} + assert PragueToOsakaAtTime15k in result + assert OsakaToAmsterdamAtTime15k not in result def test_blob_constants() -> None: # noqa: D103 diff --git a/plan.md b/plan.md index 3b77996c33b..82d5ee18d85 100644 --- a/plan.md +++ b/plan.md @@ -8,7 +8,7 @@ Gnosis fork of Ethereum EELS. Branch structure: `forks/osaka` (shipped), `forks/ ### 1. Extend Hive consume to multi-client -**Status**: Single-client consume (nethermind-gnosis) implemented in PR [#12](https://github.com/gnosischain/execution-specs/pull/12). Next step is extending to all 4 Gnosis clients (reth, geth, nethermind, erigon) — either as a matrix in `hive-consume.yaml` or keeping the multi-client variant as a separate manual workflow. +**Status**: Single-client consume (nethermind) implemented in PR [#12](https://github.com/gnosischain/execution-specs/pull/12). Next step is extending to all 4 Gnosis clients (reth, geth, nethermind, erigon) — either as a matrix in `hive-consume.yaml` or keeping the multi-client variant as a separate manual workflow. ### 2. Optimize fill scope for PR vs release @@ -58,4 +58,4 @@ The `gnosis-osaka` branch is based on upstream `forks/osaka`. As upstream evolve - [x] CI workflow adaptation to Gnosis infrastructure (hive repo, client configs, Docker images) - [x] CLAUDE.md created with full project documentation - [x] Branch structure: `forks/osaka`, `forks/amsterdam`, `mainnet` (upstream merge strategy) -- [x] Hive consume fix: fill+consume with nethermind-gnosis — PR [#12](https://github.com/gnosischain/execution-specs/pull/12) +- [x] Hive consume fix: fill+consume with nethermind — PR [#12](https://github.com/gnosischain/execution-specs/pull/12) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index d546f651cff..68e37fff359 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -9,11 +9,20 @@ ------------ Entry point for the Ethereum specification. + +Gnosis diff +----------- + + - Added logic to collect base fee into a collector address + - Added system call into block rewards contract that may mint tokens + - Modified withdrawals processing to make a system call + - Added logic to collect blob fee into a collector address """ from dataclasses import dataclass from typing import Final, List, Optional, Tuple +from eth_abi import decode, encode from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable @@ -31,6 +40,7 @@ from ethereum.forks.bpo5.blocks import Header as PreviousHeader from ethereum.merkle_patricia_trie import root, trie_set from ethereum.state import ( + EMPTY_ACCOUNT, EMPTY_CODE_HASH, Address, BlockDiff, @@ -71,6 +81,7 @@ from .state_tracker import ( BlockState, TransactionState, + account_exists, account_exists_and_is_empty, create_ether, destroy_account, @@ -79,6 +90,7 @@ get_code, incorporate_tx_into_block, increment_nonce, + set_account, set_account_balance, ) from .transactions import ( @@ -111,6 +123,16 @@ ELASTICITY_MULTIPLIER = Uint(2) EMPTY_OMMER_HASH = keccak256(rlp.encode([])) SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe") +DEPOSIT_CONTRACT_ADDRESS = hex_to_address( + "0xbabe2bed00000000000000000000000000000003" +) +BLOCK_REWARDS_CONTRACT_ADDRESS = hex_to_address( + "0x2000000000000000000000000000000000000001" +) +FEE_COLLECTOR_ADDRESS = hex_to_address( + "0x1559000000000000000000000000000000000000" +) +MAX_FAILED_WITHDRAWALS_TO_PROCESS = 4 BEACON_ROOTS_ADDRESS = hex_to_address( "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) @@ -119,8 +141,9 @@ GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB ) VERSIONED_HASH_VERSION_KZG = b"\x01" -GWEI_TO_WEI = U256(10**9) - +BLOB_FEE_COLLECTOR = hex_to_address( + "0x1559000000000000000000000000000000000000" +) WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( "0x00000961Ef480Eb55e80D19ad83579A64c007002" ) @@ -840,6 +863,8 @@ def apply_body( """ block_output = vm.BlockOutput() + process_block_rewards(block_env) + process_unchecked_system_transaction( block_env=block_env, target_address=BEACON_ROOTS_ADDRESS, @@ -855,12 +880,22 @@ def apply_body( for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) + # Gnosis: populate withdrawals trie here because + # process_withdrawals() is a system call that doesn't + # receive block_output (upstream does this internally). + for i, wd in enumerate(withdrawals): + trie_set( + block_output.withdrawals_trie, + rlp.encode(Uint(i)), + rlp.encode(wd), + ) + # EIP-7928: Post-execution operations use index N+1 block_env.block_access_list_builder.block_access_index = BlockAccessIndex( ulen(transactions) + Uint(1) ) - process_withdrawals(block_env, block_output, withdrawals) + process_withdrawals(block_env, withdrawals) process_general_purpose_requests( block_env=block_env, @@ -1069,6 +1104,29 @@ def process_transaction( tx_state, block_env.coinbase, coinbase_balance_after_mining_fee ) + # transfer base fee to fee collector address + base_fee = U256(tx_gas_used * block_env.base_fee_per_gas) + if base_fee != 0: + fee_collector_balance = get_account( + tx_state, FEE_COLLECTOR_ADDRESS + ).balance + set_account_balance( + tx_state, + FEE_COLLECTOR_ADDRESS, + fee_collector_balance + base_fee, + ) + + # transfer blob fee to fee collector address + if blob_gas_fee != 0: + blob_fee_collector_balance = get_account( + tx_state, BLOB_FEE_COLLECTOR + ).balance + set_account_balance( + tx_state, + BLOB_FEE_COLLECTOR, + blob_fee_collector_balance + U256(blob_gas_fee), + ) + # EIP-7708: Emit burn logs for balances held by accounts marked for # deletion AFTER miner fee transfer. finalization_logs: List[Log] = [] @@ -1124,24 +1182,94 @@ def process_transaction( def process_withdrawals( block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, withdrawals: Tuple[Withdrawal, ...], ) -> None: """ - Increase the balance of the withdrawing account. + Make a system call to the deposit contract to process withdrawals. + + Spec: https://github.com/gnosischain/specs/blob/master/execution/withdrawals.md """ wd_state = TransactionState(parent=block_env.state) + deposit_contract = get_account(wd_state, DEPOSIT_CONTRACT_ADDRESS) + if deposit_contract.code_hash == EMPTY_CODE_HASH: + return + + if not account_exists(wd_state, SYSTEM_ADDRESS): + set_account(wd_state, SYSTEM_ADDRESS, EMPTY_ACCOUNT) + + amounts = [] + addresses = [] + for w in withdrawals: + amounts.append(int(w.amount)) + addresses.append(w.address) + + payload = encode( + ["uint256", "uint64[]", "address[]"], + [MAX_FAILED_WITHDRAWALS_TO_PROCESS, amounts, addresses], + ) - for i, wd in enumerate(withdrawals): - trie_set( - block_output.withdrawals_trie, - rlp.encode(Uint(i)), - rlp.encode(wd), - ) + out = process_unchecked_system_transaction( + block_env=block_env, + target_address=DEPOSIT_CONTRACT_ADDRESS, + data=bytes.fromhex("79d0c0bc") + payload, + ) + if out.error: + raise InvalidBlock(f"Withdrawal system call failed: {out.error}") - create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI) + incorporate_tx_into_block( + wd_state, block_env.block_access_list_builder + ) - incorporate_tx_into_block(wd_state, block_env.block_access_list_builder) + +def process_block_rewards( + block_env: vm.BlockEnvironment, +) -> None: + """ + Call BlockRewardAuRaBase contract reward function. + + Spec: https://github.com/gnosischain/specs/blob/master/execution/posdao-post-merge.md + Contract: https://github.com/gnosischain/posdao-contracts/blob/0315e8ee854cb02d03f4c18965584a74f30796f7/contracts/base/BlockRewardAuRaBase.sol#L234C14-L234C20 + """ + # reward(address[],uint16[]) with benefactors=[coinbase], kind=[0] + coinbase_padded = b"\x00" * 12 + bytes(block_env.coinbase) + data = ( + bytes.fromhex("f91c2898") + + (64).to_bytes(32, "big") # offset of address[] arg + + (128).to_bytes(32, "big") # offset of uint16[] arg + + (1).to_bytes(32, "big") # length of address[] = 1 + + coinbase_padded # address[0] = coinbase + + (1).to_bytes(32, "big") # length of uint16[] = 1 + + (0).to_bytes(32, "big") # kind[0] = 0 (RewardAuthor) + ) + + reward_state = TransactionState(parent=block_env.state) + account = get_account(reward_state, BLOCK_REWARDS_CONTRACT_ADDRESS) + if account.code_hash == EMPTY_CODE_HASH: + return + + if not account_exists(reward_state, SYSTEM_ADDRESS): + set_account(reward_state, SYSTEM_ADDRESS, EMPTY_ACCOUNT) + + out = process_unchecked_system_transaction( + block_env=block_env, + target_address=BLOCK_REWARDS_CONTRACT_ADDRESS, + data=data, + ) + if out.error: + raise InvalidBlock(f"Block rewards system call failed: {out.error}") + + if len(out.return_data) == 0: + return + + addresses, amounts = decode(["address[]", "uint256[]"], out.return_data) + for addr, amount in zip(addresses, amounts, strict=True): + address = hex_to_address(addr) + balance = get_account(reward_state, address).balance + U256(amount) + set_account_balance(reward_state, address, balance) + + incorporate_tx_into_block( + reward_state, block_env.block_access_list_builder + ) def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 29097c49453..d29b56a1b49 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -3169,6 +3169,682 @@ def test_bal_cross_tx_funding_chain( ) +def test_bal_cross_tx_storage_chain( + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + Verify clients apply BAL state changes from prior transactions before + executing later transactions in the same block. + + Each Tx i seeds slots 0 and 1 with `1`, then computes a + Fibonacci-style sum into slot i: `slot[i] = SLOAD(i-1) + SLOAD(i-2)`. + Every Tx i>=2 depends on the two immediately preceding writes, so + any parallelization that fails to apply a prior Tx's BAL storage + change cascades into a wrong slot value and a different state root. + """ + chain_length = 8 + # i<2 seeds slot i with 1; i>=2 computes the Fibonacci sum. + contract = pre.deploy_contract( + code=Conditional( + condition=Op.LT(Op.CALLDATALOAD(0), 2), + if_true=Op.SSTORE(Op.CALLDATALOAD(0), 1), + if_false=Op.SSTORE( + Op.CALLDATALOAD(0), + Op.ADD( + Op.SLOAD(Op.SUB(Op.CALLDATALOAD(0), 1)), + Op.SLOAD(Op.SUB(Op.CALLDATALOAD(0), 2)), + ), + ), + ), + ) + + fib = [1, 1] + for i in range(2, chain_length): + fib.append(fib[i - 1] + fib[i - 2]) + + txs = [] + senders = [] + for i in range(chain_length): + sender = pre.fund_eoa() + senders.append(sender) + txs.append( + Transaction( + sender=sender, + to=contract, + data=Hash(i), + gas_limit=100_000, + ) + ) + + account_expectations: dict = { + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=i + 1, post_nonce=1) + ], + ) + for i, sender in enumerate(senders) + } + account_expectations[contract] = BalAccountExpectation( + storage_changes=[ + BalStorageSlot( + slot=i, + slot_changes=[ + BalStorageChange( + block_access_index=i + 1, post_value=fib[i] + ), + ], + ) + for i in range(chain_length) + ], + nonce_changes=[], + balance_changes=[], + code_changes=[], + storage_reads=[], + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations + ), + ) + ], + post={ + contract: Account( + storage={i: fib[i] for i in range(chain_length)} + ), + }, + ) + + +@pytest.mark.with_all_create_opcodes +def test_bal_cross_tx_deploy_then_call( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + create_opcode: Op, +) -> None: + """ + Verify clients apply Tx1's CREATE to their state view before + executing Tx2's CALL in the same block. Tx1 deploys a contract at a + deterministic address whose runtime code writes a sentinel to slot 0. + Tx2 CALLs that address. A client that parallelizes Tx2 without + applying Tx1's `code_changes` would hit an empty account, the CALL + would no-op, and slot 0 would remain 0. + """ + sentinel = 0x42 + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + runtime = Op.SSTORE(0, sentinel) + Op.STOP + initcode = Initcode(deploy_code=runtime) + initcode_bytes = bytes(initcode) + + salt = 0 + is_create2 = create_opcode == Op.CREATE2 + if is_create2: + deploy_op = Op.CREATE2( + value=0, offset=0, size=Op.CALLDATASIZE, salt=salt + ) + else: + deploy_op = Op.CREATE(value=0, offset=0, size=Op.CALLDATASIZE) + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(0, deploy_op) + + Op.STOP + ) + factory = pre.deploy_contract(code=factory_code) + target = compute_create_address( + address=factory, + nonce=1, + salt=salt, + initcode=initcode_bytes, + opcode=create_opcode, + ) + + tx_deploy = Transaction( + sender=alice, + to=factory, + data=initcode_bytes, + gas_limit=500_000, + ) + tx_call = Transaction( + sender=bob, + to=target, + gas_limit=100_000, + ) + + account_expectations = { + target: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + code_changes=[ + BalCodeChange(block_access_index=1, new_code=bytes(runtime)) + ], + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=2, post_value=sentinel + ), + ], + ), + ], + ), + } + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx_deploy, tx_call], + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations + ), + ) + ], + post={ + target: Account( + nonce=1, code=bytes(runtime), storage={0: sentinel} + ), + factory: Account(nonce=2, storage={0: target}), + }, + ) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("none", id="no_failure"), + pytest.param("collision", id="mid_chain_collision"), + pytest.param("oog", id="mid_chain_oog"), + ], +) +@pytest.mark.pre_alloc_mutable() +def test_bal_cross_tx_factory_nonce_create_chain( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + fork: Fork, + failure_mode: str, +) -> None: + """ + Cross-tx CREATE chain: 8 senders share a factory whose CREATE + address derives solely from `factory.nonce`. `collision` and `oog` + test opposite parallelization hazards mid-chain — collision still + bumps factory.nonce (later txs slide forward), OOG does not (later + txs slide backward, reusing the OOG'd slot). + """ + chain_length = 8 + failure_index = 3 if failure_mode in ("collision", "oog") else None + + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.CREATE(0, 0, Op.CALLDATASIZE) + + Op.STOP + ) + factory = pre.deploy_contract(code=factory_code) + factory_pre_nonce = 1 + + deploy_code = Op.STOP + initcode = Initcode(deploy_code=deploy_code) + collision_code = Op.PUSH1(0x42) + Op.STOP + + targets = [ + compute_create_address(address=factory, nonce=factory_pre_nonce + k) + for k in range(chain_length) + ] + + if failure_mode == "collision": + assert failure_index is not None + pre[targets[failure_index]] = Account(code=collision_code) + + sequence: list[dict] = [] + factory_nonce = factory_pre_nonce + for i in range(chain_length): + block_idx = i + 1 + if failure_mode == "oog" and i == failure_index: + sequence.append( + {"block_idx": block_idx, "target_idx": None, "deployed": False} + ) + else: + target_idx = factory_nonce - factory_pre_nonce + factory_nonce += 1 + deployed = not (failure_mode == "collision" and i == failure_index) + sequence.append( + { + "block_idx": block_idx, + "factory_post_nonce": factory_nonce, + "target_idx": target_idx, + "deployed": deployed, + } + ) + + senders = [pre.fund_eoa() for _ in range(chain_length)] + # OOG tx: intrinsic + 1 — valid to include but no gas to run CREATE. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(initcode), contract_creation=False, access_list=[] + ) + txs = [ + Transaction( + sender=senders[i], + to=factory, + data=initcode, + gas_limit=( + intrinsic + 1 + if failure_mode == "oog" and i == failure_index + else fork.transaction_gas_limit_cap() + ), + ) + for i in range(chain_length) + ] + + account_expectations: dict = { + senders[i]: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=i + 1, post_nonce=1) + ], + ) + for i in range(chain_length) + } + # Factory: only txs that bumped its nonce contribute entries. + account_expectations[factory] = BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=s["block_idx"], + post_nonce=s["factory_post_nonce"], + ) + for s in sequence + if s["target_idx"] is not None + ], + ) + for s in sequence: + if s["target_idx"] is None: + continue + target = targets[s["target_idx"]] + if s["deployed"]: + account_expectations[target] = BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=s["block_idx"], post_nonce=1 + ) + ], + code_changes=[ + BalCodeChange( + block_access_index=s["block_idx"], + new_code=deploy_code, + ) + ], + ) + else: + # Collision: accessed during EIP-684 check, no state change. + account_expectations[target] = BalAccountExpectation.empty() + + touched_target_idxs = { + s["target_idx"] for s in sequence if s["target_idx"] is not None + } + final_factory_nonce = factory_pre_nonce + len(touched_target_idxs) + post: dict = { + factory: Account(nonce=final_factory_nonce), + **{sender: Account(nonce=1) for sender in senders}, + } + for s in sequence: + if s["target_idx"] is None: + continue + target = targets[s["target_idx"]] + post[target] = ( + Account(nonce=1, code=deploy_code) + if s["deployed"] + else Account(code=collision_code) + ) + for k, target in enumerate(targets): + if k not in touched_target_idxs: + post[target] = Account.NONEXISTENT + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations + ), + ) + ], + post=post, + ) + + +@pytest.mark.parametrize( + "funding_method", + ["direct_call", "selfdestruct"], +) +def test_bal_cross_tx_balance_dependency( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + funding_method: str, +) -> None: + """ + Verify clients apply Tx1's balance change before executing Tx2 in + the same block. Tx1 routes value into a contract; Tx2 invokes the + contract which records its `SELFBALANCE` to storage. A client that + parallelizes Tx2 without applying Tx1's `balance_changes` would + record the pre-block balance, yielding a different state root. The + `selfdestruct` variant routes the funds via SELFDESTRUCT from a + pre-funded killer contract so the recipient's bytecode never runs + in Tx1 — catching any client optimization that ties balance + tracking to code execution. + """ + transferred = 1 + alice = pre.fund_eoa() + bob = pre.fund_eoa() + + # Any non-empty calldata triggers the SELFBALANCE record path; + # empty calldata is the value-receiver path. + contract = pre.deploy_contract( + code=Conditional( + condition=Op.ISZERO(Op.CALLDATASIZE), + if_true=Op.STOP, + if_false=Op.SSTORE(0, Op.SELFBALANCE), + ), + ) + + if funding_method == "direct_call": + tx_send = Transaction( + sender=alice, + to=contract, + value=transferred, + gas_limit=100_000, + ) + send_expectations: dict = {} + elif funding_method == "selfdestruct": + killer = pre.deploy_contract( + code=Op.SELFDESTRUCT(contract), + balance=transferred, + ) + tx_send = Transaction( + sender=alice, + to=killer, + gas_limit=100_000, + ) + send_expectations = { + killer: BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=0), + ], + ), + } + else: + raise ValueError(f"unknown funding_method: {funding_method}") + + tx_read = Transaction( + sender=bob, + to=contract, + data=b"\x01", + gas_limit=100_000, + ) + + account_expectations = { + contract: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, post_balance=transferred + ), + ], + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=2, post_value=transferred + ), + ], + ), + ], + ), + **send_expectations, + } + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx_send, tx_read], + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations + ), + ) + ], + post={ + contract: Account(balance=transferred, storage={0: transferred}), + }, + ) + + +@pytest.mark.parametrize( + "eunice_outcome", + [ + pytest.param("success", id="success"), + pytest.param("oog_minus_1", id="oog_minus_1"), + pytest.param( + "insufficient_funds", + id="insufficient_funds", + marks=pytest.mark.exception_test, + ), + ], +) +def test_bal_cross_tx_funding_chain( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + fork: Fork, + eunice_outcome: str, +) -> None: + """ + Funding chain: alice → bob → charlie → dan → eunice → target. Each + intermediate starts empty and must receive the prior tx's forwarded + value to afford its own upfront gas + outgoing transfer. A client + that parallelizes any later tx against pre-block state would see a + zero balance on its sender and wrongly reject the block. The + `oog_minus_1` variant funds eunice with exactly `gas_limit - 1` + worth of gas so her SSTORE OOGs at the boundary (target's BAL flips + from `storage_changes` to `storage_reads`). The `insufficient_funds` + variant has dan forward one wei short of eunice's `gas_limit * + gas_price`, so eunice's tx is rejected pre-execution and the entire + block MUST be rejected with `INSUFFICIENT_ACCOUNT_FUNDS` — a sanity + check on the off-by-one boundary of the upfront balance check. + """ + gas_price = 0xA + + target_code = Op.SSTORE( + 0, 0xC0FFEE, key_warm=False, original_value=0, new_value=0xC0FFEE + ) + target = pre.deploy_contract(code=target_code) + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_calc() + eunice_exact_gas = intrinsic_gas + target_code.gas_cost(fork) + eunice_gas_limit = ( + eunice_exact_gas - 1 + if eunice_outcome == "oog_minus_1" + else eunice_exact_gas + ) + eunice_upfront = eunice_gas_limit * gas_price + transfer_cost = intrinsic_gas * gas_price + + # Each sender (including alice) starts with or receives exactly what + # the next forward + its own gas demands; everyone ends at zero in + # the success/oog variants. `insufficient_funds` shorts eunice by + # one wei via dan, leaving her unable to cover upfront gas. + dan_value = ( + eunice_upfront - 1 + if eunice_outcome == "insufficient_funds" + else eunice_upfront + ) + charlie_value = transfer_cost + dan_value + bob_value = transfer_cost + charlie_value + alice_value = transfer_cost + bob_value + alice_pre_balance = transfer_cost + alice_value + + alice = pre.fund_eoa(amount=alice_pre_balance) + bob = pre.fund_eoa(amount=0) + charlie = pre.fund_eoa(amount=0) + dan = pre.fund_eoa(amount=0) + eunice = pre.fund_eoa(amount=0) + + eunice_error = ( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS + if eunice_outcome == "insufficient_funds" + else None + ) + + txs = [ + Transaction( + sender=alice, + to=bob, + value=alice_value, + gas_limit=intrinsic_gas, + gas_price=gas_price, + ), + Transaction( + sender=bob, + to=charlie, + value=bob_value, + gas_limit=intrinsic_gas, + gas_price=gas_price, + ), + Transaction( + sender=charlie, + to=dan, + value=charlie_value, + gas_limit=intrinsic_gas, + gas_price=gas_price, + ), + Transaction( + sender=dan, + to=eunice, + value=dan_value, + gas_limit=intrinsic_gas, + gas_price=gas_price, + ), + Transaction( + sender=eunice, + to=target, + gas_limit=eunice_gas_limit, + gas_price=gas_price, + error=eunice_error, + ), + ] + + if eunice_outcome == "insufficient_funds": + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + exception=( + TransactionException.INSUFFICIENT_ACCOUNT_FUNDS + ), + ) + ], + post={}, + ) + return + + if eunice_outcome == "oog_minus_1": + target_bal = BalAccountExpectation( + storage_reads=[0], + nonce_changes=[], + balance_changes=[], + code_changes=[], + storage_changes=[], + ) + target_post = Account(storage={}) + elif eunice_outcome == "success": + target_bal = BalAccountExpectation( + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=5, post_value=0xC0FFEE + ), + ], + ), + ], + nonce_changes=[], + balance_changes=[], + code_changes=[], + storage_reads=[], + ) + target_post = Account(storage={0: 0xC0FFEE}) + else: + raise ValueError(f"unknown eunice_outcome: {eunice_outcome}") + + account_expectations = { + alice: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=0), + ], + ), + bob: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=2, post_nonce=1)], + balance_changes=[ + BalBalanceChange( + block_access_index=1, post_balance=alice_value + ), + BalBalanceChange(block_access_index=2, post_balance=0), + ], + ), + charlie: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=3, post_nonce=1)], + balance_changes=[ + BalBalanceChange(block_access_index=2, post_balance=bob_value), + BalBalanceChange(block_access_index=3, post_balance=0), + ], + ), + dan: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=4, post_nonce=1)], + balance_changes=[ + BalBalanceChange( + block_access_index=3, post_balance=charlie_value + ), + BalBalanceChange(block_access_index=4, post_balance=0), + ], + ), + eunice: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=5, post_nonce=1)], + balance_changes=[ + BalBalanceChange(block_access_index=4, post_balance=dan_value), + BalBalanceChange(block_access_index=5, post_balance=0), + ], + ), + target: target_bal, + } + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations, + ), + ) + ], + post={ + alice: Account(nonce=1, balance=0), + bob: Account(nonce=1, balance=0), + charlie: Account(nonce=1, balance=0), + dan: Account(nonce=1, balance=0), + eunice: Account(nonce=1, balance=0), + target: target_post, + }, + ) + + def test_bal_cross_block_ripemd160_state_leak( pre: Alloc, blockchain_test: BlockchainTestFiller, @@ -3357,7 +4033,7 @@ def test_bal_all_transaction_types( gas_limit=100_000, max_fee_per_gas=50, max_priority_fee_per_gas=5, - max_fee_per_blob_gas=10, + max_fee_per_blob_gas=10**10, blob_versioned_hashes=blob_hashes, data=Hash(0x04), ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py index d72f262c240..7975a73b657 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py @@ -41,11 +41,11 @@ def test_bal_withdrawal_empty_block( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures withdrawal balance changes in empty block. + Ensure BAL produces no entry for withdrawal in an empty block. Charlie starts with 1 gwei balance (existing account). Block with 0 transactions and 1 withdrawal of 10 gwei to Charlie. - Charlie ends with 11 gwei balance. + Charlie does not appear in BAL and balance is not credited. """ charlie = pre.fund_eoa(amount=1 * GWEI) @@ -59,24 +59,14 @@ def test_bal_withdrawal_empty_block( amount=10, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, post_balance=11 * GWEI - ) - ], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( pre=pre, blocks=[block], post={ - charlie: Account(balance=11 * GWEI), + charlie: Account(balance=1 * GWEI), }, ) @@ -87,12 +77,12 @@ def test_bal_withdrawal_and_transaction( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures both transaction and withdrawal balance changes. + Ensure BAL captures transaction changes but not withdrawal. Alice starts with 1 ETH, Bob starts with 0, Charlie starts with 0. Alice sends 5 wei to Bob. - Charlie receives 10 gwei withdrawal. - Bob ends with 5 wei, Charlie ends with 10 gwei. + Charlie receives 10 gwei withdrawal but is not captured in BAL. + Bob ends with 5 wei, Charlie's balance remains unchanged. """ alice = pre.fund_eoa() bob = pre.fund_eoa(amount=0) @@ -128,13 +118,6 @@ def test_bal_withdrawal_and_transaction( BalBalanceChange(block_access_index=1, post_balance=5) ], ), - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=2, post_balance=10 * GWEI - ) - ], - ), } ), ) @@ -145,7 +128,6 @@ def test_bal_withdrawal_and_transaction( post={ alice: Account(nonce=1), bob: Account(balance=5), - charlie: Account(balance=10 * GWEI), }, ) @@ -173,25 +155,13 @@ def test_bal_withdrawal_to_nonexistent_account( amount=10, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, post_balance=10 * GWEI - ) - ], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( pre=pre, blocks=[block], - post={ - charlie: Account(balance=10 * GWEI), - }, + post={}, ) @@ -200,7 +170,7 @@ def test_bal_withdrawal_no_evm_execution( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures withdrawal without triggering EVM execution. + Ensure withdrawal does not trigger EVM or appear in BAL. Oracle contract starts with 0 balance and storage slot 0x01 = 0x42. Oracle's code writes 0xFF to slot 0x01 when called. @@ -222,19 +192,7 @@ def test_bal_withdrawal_no_evm_execution( amount=10, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - oracle: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, post_balance=10 * GWEI - ) - ], - storage_reads=[], - storage_changes=[], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( @@ -242,7 +200,7 @@ def test_bal_withdrawal_no_evm_execution( blocks=[block], post={ oracle: Account( - balance=10 * GWEI, + balance=0, storage={0x01: 0x42}, ), }, @@ -254,12 +212,12 @@ def test_bal_withdrawal_and_state_access_same_account( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures both state access and withdrawal to same address. + Ensure BAL captures state access but not withdrawal to same address. Oracle contract starts with 0 balance and storage slot 0x01 = 0x42. Alice calls Oracle (reads slot 0x01, writes 0x99 to slot 0x02). Oracle receives withdrawal of 10 gwei. - Both state access and withdrawal are captured in BAL. + State access is captured in BAL but not withdrawal. """ alice = pre.fund_eoa() oracle = pre.deploy_contract( @@ -303,11 +261,6 @@ def test_bal_withdrawal_and_state_access_same_account( ], ) ], - balance_changes=[ - BalBalanceChange( - block_access_index=2, post_balance=10 * GWEI - ) - ], ), } ), @@ -319,7 +272,7 @@ def test_bal_withdrawal_and_state_access_same_account( post={ alice: Account(nonce=1), oracle: Account( - balance=10 * GWEI, + balance=0, storage={0x01: 0x42, 0x02: 0x99}, ), }, @@ -331,12 +284,12 @@ def test_bal_withdrawal_and_value_transfer_same_address( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures both value transfer and withdrawal to same address. + Ensure BAL captures value transfer but not withdrawal to same address. Alice starts with 1 ETH, Bob starts with 0. Alice sends 5 gwei to Bob. - Bob receives withdrawal of 10 gwei. - Bob ends with 15 gwei (5 from tx + 10 from withdrawal). + Bob receives withdrawal of 10 gwei but is not captured in BAL. + Bob ends with 5 gwei (tx only). """ alice = pre.fund_eoa() bob = pre.fund_eoa(amount=0) @@ -370,9 +323,6 @@ def test_bal_withdrawal_and_value_transfer_same_address( BalBalanceChange( block_access_index=1, post_balance=5 * GWEI ), - BalBalanceChange( - block_access_index=2, post_balance=15 * GWEI - ), ], ), } @@ -384,7 +334,7 @@ def test_bal_withdrawal_and_value_transfer_same_address( blocks=[block], post={ alice: Account(nonce=1), - bob: Account(balance=15 * GWEI), + bob: Account(balance=5 * GWEI), }, ) @@ -394,11 +344,11 @@ def test_bal_multiple_withdrawals_same_address( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL accumulates multiple withdrawals to same address. + Ensure multiple withdrawals to same address do not appear in BAL. Charlie starts with 0 balance. Block empty block with 3 withdrawals to Charlie: 5 gwei, 10 gwei, 15 gwei. - Charlie ends with 30 gwei balance (cumulative). + Charlie does not appear in BAL and balance is not credited. """ charlie = pre.fund_eoa(amount=0) @@ -408,25 +358,13 @@ def test_bal_multiple_withdrawals_same_address( Withdrawal(index=i, validator_index=i, address=charlie, amount=amt) for i, amt in enumerate([5, 10, 15]) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, post_balance=30 * GWEI - ) - ], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( pre=pre, blocks=[block], - post={ - charlie: Account(balance=30 * GWEI), - }, + post={}, ) @@ -435,12 +373,12 @@ def test_bal_withdrawal_and_selfdestruct( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures withdrawal to self-destructed contract address. + Ensure BAL captures SELFDESTRUCT balance changes but not withdrawal. Oracle contract starts with 100 gwei balance. Alice triggers Oracle to self-destruct, sending balance to Bob. Oracle receives withdrawal of 50 gwei after self-destructing. - Oracle ends with 50 gwei (funded by withdrawal). + Oracle ends with 0 balance (withdrawal is not captured in BAL). """ alice = pre.fund_eoa() bob = pre.fund_eoa(amount=0) @@ -483,9 +421,6 @@ def test_bal_withdrawal_and_selfdestruct( oracle: BalAccountExpectation( balance_changes=[ BalBalanceChange(block_access_index=1, post_balance=0), - BalBalanceChange( - block_access_index=2, post_balance=50 * GWEI - ), ], ), } @@ -498,7 +433,7 @@ def test_bal_withdrawal_and_selfdestruct( post={ alice: Account(nonce=1), bob: Account(balance=100 * GWEI), - oracle: Account(balance=50 * GWEI), + oracle: Account(balance=0), }, ) @@ -508,11 +443,11 @@ def test_bal_withdrawal_and_new_contract( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures withdrawal to newly created contract. + Ensure BAL captures contract creation with value but not withdrawal. Alice deploys Oracle contract with 5 gwei initial balance. Oracle receives withdrawal of 10 gwei in same block. - Oracle ends with 15 gwei (5 from deployment + 10 from withdrawal). + Oracle ends with 5 gwei (deployment only). """ alice = pre.fund_eoa() @@ -554,9 +489,6 @@ def test_bal_withdrawal_and_new_contract( BalBalanceChange( block_access_index=1, post_balance=5 * GWEI ), - BalBalanceChange( - block_access_index=2, post_balance=15 * GWEI - ), ], ), } @@ -568,7 +500,7 @@ def test_bal_withdrawal_and_new_contract( blocks=[block], post={ alice: Account(nonce=1), - oracle: Account(balance=15 * GWEI, code=code), + oracle: Account(balance=5 * GWEI, code=code), }, ) @@ -586,11 +518,11 @@ def test_bal_zero_withdrawal( initial_balance: int, ) -> None: """ - Ensure BAL handles zero-amount withdrawal correctly. + Ensure zero-amount withdrawal does not appear in BAL. Charlie either exists with initial balance or is non-existent. Block with 0 transactions and 1 zero-amount withdrawal to Charlie. - Charlie appears in BAL but with empty changes, balance unchanged. + Charlie does not appear in BAL and balance is unchanged. """ if initial_balance > 0: charlie = pre.fund_eoa(amount=initial_balance) @@ -607,11 +539,7 @@ def test_bal_zero_withdrawal( amount=0, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - charlie: BalAccountExpectation.empty(), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( @@ -638,10 +566,10 @@ def test_bal_withdrawal_to_precompiles( precompile: Address, ) -> None: """ - Ensure BAL captures withdrawal to precompile addresses. + Ensure withdrawal to precompile address does not appear in BAL. Block with 0 transactions and 1 withdrawal of 10 gwei to precompile. - Precompile ends with 10 gwei balance. + Precompile does not appear in BAL and balance is not credited. """ block = Block( txs=[], @@ -653,27 +581,13 @@ def test_bal_withdrawal_to_precompiles( amount=10, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - precompile: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, post_balance=10 * GWEI - ) - ], - storage_reads=[], - storage_changes=[], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( pre=pre, blocks=[block], - post={ - precompile: Account(balance=10 * GWEI), - }, + post={}, ) @@ -682,11 +596,11 @@ def test_bal_withdrawal_largest_amount( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures withdrawal with largest amount. + Ensure withdrawal with largest amount does not appear in BAL. Block with 0 transactions and 1 withdrawal of maximum uint64 value (2^64-1)Gwei to Charlie. - Charlie ends with (2^64-1) Gwei. + Charlie does not appear in BAL and balance is not credited. """ charlie = pre.fund_eoa(amount=0) max_amount = 2**64 - 1 @@ -701,26 +615,13 @@ def test_bal_withdrawal_largest_amount( amount=max_amount, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, - post_balance=max_amount * GWEI, - ) - ], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( pre=pre, blocks=[block], - post={ - charlie: Account(balance=max_amount * GWEI), - }, + post={}, ) @@ -730,10 +631,11 @@ def test_bal_withdrawal_to_coinbase( fork: Fork, ) -> None: """ - Ensure BAL captures withdrawal to coinbase address. + Ensure BAL captures tx fee but not withdrawal to coinbase address. Block with 1 transaction and 1 withdrawal to coinbase/fee recipient. - Coinbase receives both transaction fees and withdrawal. + Coinbase receives both transaction fees. + Withdrawal is not captured in BAL and balance is not credited. """ alice = pre.fund_eoa() bob = pre.fund_eoa(amount=0) @@ -760,7 +662,6 @@ def test_bal_withdrawal_to_coinbase( parent_gas_limit=genesis_env.gas_limit, ) tip_to_coinbase = (gas_price - base_fee_per_gas) * intrinsic_gas - coinbase_final_balance = tip_to_coinbase + (10 * GWEI) block = Block( txs=[tx], @@ -791,10 +692,6 @@ def test_bal_withdrawal_to_coinbase( BalBalanceChange( block_access_index=1, post_balance=tip_to_coinbase ), - BalBalanceChange( - block_access_index=2, - post_balance=coinbase_final_balance, - ), ], ), } @@ -807,7 +704,7 @@ def test_bal_withdrawal_to_coinbase( post={ alice: Account(nonce=1), bob: Account(balance=5), - coinbase: Account(balance=coinbase_final_balance), + coinbase: Account(balance=tip_to_coinbase), }, genesis_environment=genesis_env, ) @@ -818,10 +715,10 @@ def test_bal_withdrawal_to_coinbase_empty_block( blockchain_test: BlockchainTestFiller, ) -> None: """ - Ensure BAL captures withdrawal to coinbase when there are no transactions. + Ensure withdrawal to coinbase does not appear in BAL. Empty block with 1 withdrawal of 10 gwei to coinbase/fee recipient. - Coinbase receives only withdrawal (no transaction fees). + Coinbase does not appear in BAL and balance is not credited. """ coinbase = pre.fund_eoa(amount=0) @@ -836,23 +733,11 @@ def test_bal_withdrawal_to_coinbase_empty_block( amount=10, ) ], - expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - coinbase: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, post_balance=10 * GWEI - ) - ], - ), - } - ), + expected_block_access_list=BlockAccessListExpectation(), ) blockchain_test( pre=pre, blocks=[block], - post={ - coinbase: Account(balance=10 * GWEI), - }, + post={}, ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py index 08ab12a9e79..1ad726f9007 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py @@ -1334,9 +1334,6 @@ def test_bal_selfdestruct_to_7702_delegation( ) -GWEI = 10**9 - - def test_bal_withdrawal_to_7702_delegation( pre: Alloc, blockchain_test: BlockchainTestFiller, @@ -1381,10 +1378,6 @@ def test_bal_withdrawal_to_7702_delegation( ], ) - alice_final_balance = alice_initial_balance + ( - withdrawal_amount_gwei * GWEI - ) - account_expectations = { alice: BalAccountExpectation( # tx1: nonce change for auth, code change for delegation @@ -1395,12 +1388,8 @@ def test_bal_withdrawal_to_7702_delegation( new_code=Spec7702.delegation_designation(oracle), ) ], - # tx2 (withdrawal): balance change - balance_changes=[ - BalBalanceChange( - block_access_index=2, post_balance=alice_final_balance - ) - ], + # NO balance_changes: on Gnosis, withdrawals go through a system + # contract call — there is no direct balance credit in the BAL. ), bob: BalAccountExpectation( balance_changes=[ @@ -1434,7 +1423,7 @@ def test_bal_withdrawal_to_7702_delegation( alice: Account( nonce=1, code=Spec7702.delegation_designation(oracle), - balance=alice_final_balance, + balance=alice_initial_balance, ), bob: Account(balance=10), relayer: Account(nonce=1), diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index 52d366019d8..1dd5f362f55 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -655,19 +655,19 @@ def test_bal_invalid_missing_account( @pytest.mark.valid_from("Amsterdam") @pytest.mark.exception_test -def test_bal_invalid_missing_withdrawal_account( +def test_bal_invalid_missing_tx_account( blockchain_test: BlockchainTestFiller, pre: Alloc, ) -> None: """ Test that clients reject blocks where BAL is missing an account - that was modified only by a withdrawal. + that was modified by a transaction. Alice sends 5 wei to Bob (1 transaction). - Charlie receives 10 gwei withdrawal. - BAL is corrupted by removing Charlie's entry entirely. - Clients must detect that Charlie's balance was modified by the - withdrawal but has no corresponding BAL entry. + Charlie receives 10 gwei withdrawal but is not captured in BAL. + BAL is corrupted by removing Bob's entry (the tx-modified account). + Clients must detect that Bob's balance was modified by the tx but has no + corresponding BAL entry because Charlie's withdrawal is a no-op. """ alice = pre.fund_eoa() bob = pre.fund_eoa(amount=0) @@ -715,33 +715,25 @@ def test_bal_invalid_missing_withdrawal_account( ) ], ), - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=2, - post_balance=10 * 10**9, - ) - ], - ), } - ).modify(remove_accounts(charlie)), + ).modify(remove_accounts(bob)), ) ], ) @pytest.mark.valid_from("Amsterdam") -@pytest.mark.exception_test def test_bal_invalid_missing_withdrawal_account_empty_block( blockchain_test: BlockchainTestFiller, pre: Alloc, ) -> None: """ - Test that clients reject blocks where BAL is missing an account - that was modified only by a withdrawal, in a block with no transactions. + Test that an empty block containing only a withdrawal produces + a valid block with an empty BAL. - Charlie receives 10 gwei withdrawal in an empty block. - BAL is corrupted by removing Charlie's entry entirely. + Charlie receives 10 gwei withdrawal in an empty block (no transactions). + Charlie is not captured in BAL because the withdrawal is a no-op. + The block is valid with an empty BAL. """ charlie = pre.fund_eoa(amount=0) @@ -763,17 +755,15 @@ def test_bal_invalid_missing_withdrawal_account_empty_block( ], exception=BlockException.INVALID_BLOCK_ACCESS_LIST, expected_block_access_list=BlockAccessListExpectation( - account_expectations={ - charlie: BalAccountExpectation( - balance_changes=[ - BalBalanceChange( - block_access_index=1, - post_balance=10 * 10**9, - ) - ], - ), - } - ).modify(remove_accounts(charlie)), + account_expectations=beacon_root_system_call_expectations( + block_timestamp, + beacon_root, + ) + ).modify( + append_account( + BalAccountChange(address=SYSTEM_ADDRESS), + ) + ), ) ], )