Skip to content

refactor(spec-tools): use testing pydantic models in t8n - #2924

Merged
gurukamath merged 13 commits into
ethereum:forks/amsterdamfrom
gurukamath:refactor/testing-alloc-as-prestate
Jul 23, 2026
Merged

refactor(spec-tools): use testing pydantic models in t8n#2924
gurukamath merged 13 commits into
ethereum:forks/amsterdamfrom
gurukamath:refactor/testing-alloc-as-prestate

Conversation

@gurukamath

@gurukamath gurukamath commented May 27, 2026

Copy link
Copy Markdown
Contributor

🗒️ Description

The PR re-shapes the boundary between the testing package and
ethereum_spec_tools so that the T8N transition tool becomes a small
JSON-free engine that consumes testing pydantic types directly. Six
commits, one architectural thread per commit:

  1. Alloc becomes a PreState (fe3e14f9d3f). The testing-side
    Alloc now implements ethereum.state.PreState
    (get_account_optional, get_storage, get_code,
    account_has_storage, compute_state_root_and_trie_changes),
    removing the need for a bespoke adapter between testing's
    allocation model and the spec's state-transition entry point. A
    _Phase machine (CONSTRUCTION → LIVE → FROZEN) governs when free
    mutations are allowed; once any PreState method is called, the
    alloc transitions to LIVE and only apply_diff may mutate it.

  2. T8N rewritten to consume testing pydantic types
    (c8df6498de9). The bespoke Alloc / Env /
    Result / Txs JSON-parsing classes (t8n_types.py, 440 lines)
    are deleted. T8N no longer parses argparse output into custom
    containers — it accepts a TransitionTool.TransitionToolData
    directly.

  3. In-process EELS path stops round-tripping through JSON
    (7fb967e58e8).
    ExecutionSpecsTransitionTool._evaluate hands its
    TransitionToolData straight to T8N and assembles the
    TransitionToolOutput from T8N's in-memory state.

  4. CLI surface extracted into t8n/cli.py (e71254cc2e7). Argparse setup, --input.*/--output.* flags,
    stdin/file readers, tracer construction from CLI flags, and the
    t8n-output JSON serialization all live in a new module. Core T8N
    knows nothing about argparse, JSON, or stdin/stdout.

  5. Duplicate testing-package state machinery removed
    (0d242564689). The parallel State dataclass and
    set_account / set_storage / state_root / storage_root free
    helpers in account_types.py deleted. Alloc.state_root() now
    routes through spec_state.state_root(_materialize_state()). The
    testing package no longer maintains a fork of trie/state plumbing.

  6. Final cleanup pass (151062102f2) - Small miscellanous clean-ups.

🔗 Related Issues or PRs

#1883
#1359

✅ Checklist

  • All: Ran fast static checks to avoid unnecessary CI fails, see also Code Standards and Enabling Pre-commit Checks:
    just static
  • All: PR title adheres to the repo standard - it will be used as the squash commit message and should start type(scope):.
  • All: Set appropriate labels for the changes (only maintainers can apply labels).
  • Tests: Ran mkdocs serve locally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.

Cute Animal Picture

Cute Animals - 1 of 7

@gurukamath

Copy link
Copy Markdown
Contributor Author

Note to reviewers: I have already run uv run hasher compare between the pre and post changes commit and they match, implying consistency in generated fixtures

@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (forks/amsterdam@2282c75). Learn more about missing BASE report.

Additional details and impacted files
@@                Coverage Diff                 @@
##             forks/amsterdam    #2924   +/-   ##
==================================================
  Coverage                   ?   93.39%           
==================================================
  Files                      ?      624           
  Lines                      ?    37014           
  Branches                   ?     3386           
==================================================
  Hits                       ?    34568           
  Misses                     ?     1672           
  Partials                   ?      774           
Flag Coverage Δ
unittests 93.39% <ø> (?)

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

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

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

"""
Convert a testing ``Transaction`` into the fork's tx object.

TODO: Replace with ``self.fork.decode_transaction(tx.rlp())``

@gurukamath gurukamath May 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Transactions are the only part of the t8n which use some sort of a json round trip workaround in this PR. In order to avoid this workaround (and fully make use of the rlp that the testing framework generates) we will need to make some small changes on the specs side, particularly wrt. the decode_transaction function. I aim to tackle this in a subsequent PR.

See #2925

@gurukamath
gurukamath requested review from SamWilsn and marioevz May 27, 2026 09:51
@petertdavies
petertdavies self-requested a review May 28, 2026 10:01

@petertdavies petertdavies left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! I have a few minor comments.

Comment thread src/ethereum_spec_tools/evm_tools/t8n/__init__.py Outdated
Comment thread src/ethereum_spec_tools/evm_tools/t8n/block_environment.py Outdated
petertdavies
petertdavies previously approved these changes May 29, 2026
@gurukamath

Copy link
Copy Markdown
Contributor Author

Rebased on top of the latest forks/amsterdam

@marioevz

marioevz commented Jul 3, 2026

Copy link
Copy Markdown
Member

Wanted to pick this up at some point next week for review now that the devnet workload has finally started to subside.

@gurukamath

Copy link
Copy Markdown
Contributor Author

Wanted to pick this up at some point next week for review now that the devnet workload has finally started to subside.

Yeah. Let me rebase it early next week.

@gurukamath
gurukamath force-pushed the refactor/testing-alloc-as-prestate branch from f9e87ed to dc33465 Compare July 6, 2026 09:49
@danceratopz

Copy link
Copy Markdown
Member

PR to fix the problems in the json-loader job here:

gurukamath and others added 11 commits July 22, 2026 15:41
Add a CONSTRUCTION/LIVE/FROZEN lifecycle to the testing-side Alloc so
it directly satisfies ethereum.state.PreState. The first PreState
read transitions the alloc to LIVE and rejects further __setitem__/
__delitem__; apply_diff(BlockDiff) is the sole mutation entry point
in LIVE, and freeze() locks the allocation for assertion use.

Groundwork for the t8n refactor: with Alloc directly usable as a
PreState, fork.BlockState(pre_state=alloc) works without an adapter,
and t8n can drop its bespoke Alloc/Env/Result/Txs JSON types.
T8N now wires the testing-package types end-to-end:

* __init__ accepts an optional ``t8n_data`` and otherwise parses the
  JSON inputs into testing ``Alloc``/``Environment``/``Transaction``
  via ``model_validate``. The CLI ingress is structured so the future
  in-process lift only needs to swap the caller, not the constructor.
* ``env.py`` drops the bespoke ``Env`` class in favour of
  ``build_block_environment(fork, env, pre_state, chain_id, ommers,
  state_test)`` plus a handful of ``_resolve_*`` helpers. ``Ommer``
  stays as a small dataclass for the pre-PoS reward path.
* ``convert_transaction`` routes through ``TransactionLoad`` rather
  than ``rlp.decode_to`` so contract-creating typed txs (Blob /
  SetCode with ``to=null``) construct successfully and let
  ``check_transaction`` raise the canonical
  ``TransactionTypeContractCreationError``.
* The bespoke ``Result`` / ``Txs`` / ``Alloc`` classes are gone;
  ``build_result`` and ``get_receipts_from_output`` produce a
  ``cli_types.Result`` directly, and ``T8N.run()`` emits the
  ``TransitionToolOutput``-shaped JSON.
* The per-tx ``backup_state`` / ``restore_state`` pattern disappears
  with the snapshot-based State: a failed ``process_transaction`` no
  longer reaches ``incorporate_tx_into_block``, so ``BlockState`` is
  untouched without explicit rollback. After execution the block
  diff is applied in-place via ``Alloc.apply_diff``.

JSON ingress smooths two boundary mismatches: ``yParity`` on auth
tuples (duplicated by the testing serializer, rejected by the
validator) and ``secretKey`` left on already-signed txs (rejected by
``InvalidSignaturePrivateKeyError``). Unsigned txs with only a
``secretKey`` are signed post-validation; pre-Spurious-Dragon forks
get ``protected=False`` so the v-value stays in {27, 28}.
The testing-side EELS caller no longer marshals the input through a
JSON ``StringIO`` and back. ``_evaluate`` now hands the testing
``TransitionToolInput`` directly to ``T8N`` via the existing
``t8n_data`` kwarg and assembles the ``TransitionToolOutput`` from
``T8N``'s in-memory ``alloc``/``result``/``body``.

To make the in-process path symmetric with the CLI path, ``T8N``:

* pulls ``blob_params`` from ``t8n_data.blob_params`` (camelCase dump
  matches the existing parse), so BPO-fork blob schedules don't have
  to be re-serialized through ``--input.blobParams=stdin``;
T8N now takes a testing ``TransitionTool.TransitionToolData`` and
nothing else from the JSON/CLI surface. The CLI plumbing
(``argparse`` namespace, ``--input.*``/``--output.*`` flags, stdin,
file paths, tracer construction from CLI flags) lives in a new
``t8n.cli`` module:

* ``build_t8n_from_cli_options(options, in_file, cache) -> T8N``
  reads the JSON inputs (stdin / files), validates each piece into
  testing pydantic types, resolves the fork, bundles everything into
  a ``TransitionToolData``, builds tracers from the CLI flags, and
  hands them to ``T8N``.
* ``write_t8n_outputs(t8n, output, options, out_file)`` serialises
  the t8n output + opcode counts per ``--output.*``.
* ``run_t8n_cli(options, out_file, in_file, cache) -> int`` chains
  the two for the CLI entry point.

``T8N`` internally calls ``resolve_fork(t8n_data.fork_name,
t8n_data.env)`` to translate the testing-side fork name into a spec
``Hardfork`` + optional ``ByBlockNumber`` criteria (handles both
canonical names and CLI exception aliases like ``Paris``,
``ConstantinopleFix``, ``HomesteadToDaoAt5``). ``T8N.run()`` returns
the ``TransitionToolOutput`` directly — no more out_file writing.

Callers updated:

* ``evm_tools.__init__.main`` now calls ``run_t8n_cli``.
* ``statetest`` and ``tests/json_loader`` use
  ``build_t8n_from_cli_options``.
* ``tests/evm_tools/test_count_opcodes`` uses ``run_t8n_cli``.
* ``ExecutionSpecsTransitionTool._evaluate`` hands its
  ``transition_tool_data`` straight to ``T8N`` — no argparse dance.

The CLI ↔ testing fork-name mapping is title-case + a one-entry
override for ``DAOFork`` (testing's irregular capitalisation).
``state_reward=None`` is resolved to the fork's ``BLOCK_REWARD`` (or
``-1`` for PoS forks) in the wrapper before constructing
``TransitionToolData.reward: int``.
``Alloc`` used to maintain its own parallel ``State`` dataclass plus
``set_account``/``set_storage``/``state_root``/``storage_root`` free
functions to compute its root. Now that ``Alloc`` implements the
``PreState`` protocol, ``state_root()`` can route through
``_materialize_state()`` and ``ethereum.state.state_root``, so the
in-package trie machinery is redundant.

* ``Alloc.state_root()`` reduced to a one-liner over
  ``spec_state.state_root(self._materialize_state())``. The materialize
  call doesn't transition the alloc out of ``CONSTRUCTION``, so existing
  callers that compute a genesis root and then keep mutating the alloc
  are unaffected.
* Local ``State`` dataclass + trie helpers (``set_account``,
  ``set_storage``, ``storage_root``, ``state_root``) removed; they had
  no consumers outside the deleted ``Alloc.state_root`` body.
* ``test_types/trie.py`` deleted along with its now-tautological
  ``test_eest_trie_keccak256_matches_eels`` keccak-dispatch check
  (the module just re-exported ``ethereum.crypto.hash.keccak256``).
The CLI parser builds testing `Transaction` objects and RLP-encodes
them for the returned body. A tx with neither `v`/`r`/`s` nor
`secretKey` made `Transaction.rlp` auto-sign a key-less tx and die on
`assert signing_key is not None`, failing every json_loader case that
replays such a fixture (136 in CI, all `test_bad_v_r_s`).

Default the missing signature components to zero in
`_normalize_tx_json`, matching the previous parser
(`t8n_types.Txs.parse_json_tx`): the tx then executes with an invalid
signature and the fork rejects it, which is exactly what these
fixtures assert via `expectException`. Verified against locally
filled `bad_v_r_s` fixtures for Homestead and Prague.
@gurukamath
gurukamath force-pushed the refactor/testing-alloc-as-prestate branch from 84fd374 to 8f06590 Compare July 22, 2026 17:51

@marioevz marioevz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome job!

I just left a couple of comments, and also created a PR to your branch to see if we can get a refactor to LazyAlloc in this same PR: gurukamath#19

Comment thread src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py
Comment thread src/ethereum_spec_tools/evm_tools/t8n/__init__.py Outdated
Comment thread src/ethereum_spec_tools/evm_tools/t8n/__init__.py Outdated
marioevz and others added 2 commits July 23, 2026 15:31
* refactor(test-clis): Update LazyAlloc

* refactor(test-clis): Refactor LazyAlloc
@gurukamath
gurukamath force-pushed the refactor/testing-alloc-as-prestate branch from 99646bd to 537bab3 Compare July 23, 2026 13:44
@gurukamath
gurukamath merged commit a2e59a6 into ethereum:forks/amsterdam Jul 23, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Better abstraction in T8N class in src/ethereum_spec_tools/evm_tools/t8n/__init__.py

4 participants