Skip to content

feat(test-specs): Test function receives fork's genesis pre-alloc - #3830

Merged
marioevz merged 6 commits into
ethereum:forks/bogotafrom
marioevz:spec-seeds-pre-allocation-to-test
Oct 15, 2026
Merged

marioevz merged 6 commits into
ethereum:forks/bogotafrom
marioevz:spec-seeds-pre-allocation-to-test

Conversation

@marioevz

Copy link
Copy Markdown
Member

Description

The pre fixture now starts with the accounts the fork allocates at genesis, and the spec uses pre as the genesis allocation unchanged. Previously pre started empty and each spec merged the fork's pre-allocation underneath it
while filling, so a test could not see, and could only partly control, the genesis it ran on.

Example of how this is useful: #3600 requires being able to inspect the final pre it runs on because its tests depend on a specific state trie shape. With the spec filler modifying the pre-allocation after the test function finished, the test required calling specific methods in order to try to predict the modifications in advance, which was brittle but no other way was possible.

Behaviour changes for test writers

The test now owns pre and has the final call: it may inspect, replace or delete the seeded accounts, and the spec does not correct or re-add them.

  • pre[addr] = Account(...) replaces the whole account. Before, it was merged field by field over the fork's default, so unset fields kept the predeploy's values.
  • Writing an all-zero account to remove a predeploy no longer works, because the empty account now reaches genesis and is rejected. Use del pre[addr].
  • pre.deploy_contract(address=...) at a predeploy's address now fails with already in allocation (because the contract is already there). Use pre[addr] = Account(...) to replace the predeploy.

The tests that relied on the old merge are updated to say what they mean: del pre[addr] for removals, pre[addr] = Account(...) for replacements, and .copy(...) where only the code changes. The same applies to the system contract deployment generator in tools/utility/generators.py. Framework unit tests that build specs by hand seed pre explicitly.

Verification

All fixtures are unchanged:

  • fill tests --from Osaka --until Bogota --generate-all-formats, before and after, compared fixture by fixture across 258,660 fixtures and every pre-alloc group. The only differences came from the three modules that partially override predeploys. After moving them to .copy(...), those modules were filled again through Bogota with all formats, and they are identical too.
  • The test_contract_deployment.py modules, which fill transitions into Prague, are filled separately through Bogota with all formats: identical.
  • Unit tests: just test-tests passes.

Related Issues or PRs

Checklist

  • Ran fast static checks to avoid CI fails, see Code Standards & Verifying Changes: just static
  • PR title has the form <type>(<area>): <description>, where <type> and <area> come from an appropriate C-<type>, respectively A-<area>, label. The title should match the target squash commit message.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@marioevz
marioevz requested a review from fselmo October 14, 2026 20:23
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.99%. Comparing base (14d6b15) to head (58973d9).
⚠️ Report is 1 commits behind head on forks/bogota.

Additional details and impacted files
@@              Coverage Diff              @@
##           forks/bogota    #3830   +/-   ##
=============================================
  Coverage         94.99%   94.99%           
=============================================
  Files               673      673           
  Lines             40888    40888           
  Branches           3769     3769           
=============================================
+ Hits              38842    38843    +1     
+ Misses             1429     1428    -1     
  Partials            617      617           
Flag Coverage Δ
unittests 94.99% <ø> (+<0.01%) ⬆️

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.

@marioevz marioevz mentioned this pull request Oct 14, 2026
2 tasks done
@fselmo
fselmo force-pushed the spec-seeds-pre-allocation-to-test branch from 55c6028 to 8572d8f Compare October 15, 2026 17:51
pre now starts with the fork's genesis accounts, so the skill says assigning replaces the whole account and del pre[addr] removes one.
An allocation built outside the pre fixture must now opt into the fork's genesis accounts with with_genesis_accounts(), which the spec merges in per fixture format; a genesis built without them raises instead of silently missing the system contracts.

@fselmo fselmo 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.

Hey @marioevz. Just some comments and I pushed a fix to your branch... feel free to drop it or redo it another way. A spec built with a pre that isn't the fixture (blockchain_test(pre={...}) or the fuzzer bridge path) got a genesis without the system contracts. It fails from Prague on, but at Cancun it filled silently with the wrong state root. Now an allocation built outside the fixture has to opt in (I made this fail loudly) with Alloc(...).with_genesis_accounts(), otherwise the spec raises. Fixtures are unchanged after this.

I also think that fund_address() on a predeploy wipes its code. This naming might be a bit confusing in case you think you're just funding the address but now it will also wipe other fields out. That's a quirk that I think from a devex perspective it's not ideal maybe but we could tidy this up in another PR if we think it's misleading. Thoughts on that?

Nothing blocking here though if you approve of my commit. Again, feel free to change it up and feel free to re-request a review if you do or I trust your judgement there too :)

@marioevz
marioevz merged commit e9f6153 into ethereum:forks/bogota Oct 15, 2026
28 checks passed
fselmo added a commit to fselmo/execution-specs that referenced this pull request Oct 15, 2026
Uses the update_account helper from ethereum#3830 so the predeploy keeps its nonce and storage, as Mario suggested on ethereum#3835.
fselmo added a commit that referenced this pull request Oct 15, 2026
* feat(tests): pin withdrawals before request system calls

Replace each request predeploy's code with code that forwards its balance to a sink, so the post-state shows whether the withdrawal to it was credited before its system call ran. Runs from Prague.

* refactor(tests): swap predeploy code with update_account

Uses the update_account helper from #3830 so the predeploy keeps its nonce and storage, as Mario suggested on #3835.
CPerezz added a commit to CPerezz/execution-specs that referenced this pull request Oct 16, 2026
Since ethereum#3830, `pre` holds the whole genesis of the fixture being filled, so the tests stop rebuilding it from the fork's pre-allocations. Fixtures are unchanged.
marioevz pushed a commit that referenced this pull request Oct 19, 2026
* test(tests): add MPT structural test suite

Fixtures whose committed state root is the cross-client oracle for the
Merkle Patricia Trie itself: storage slots and addresses are mined so
their keccak256 hashes force specific node operations, and every test
asserts the node path it claims against the reference `patricialize`
at fill time. Valid from Osaka on so every client consumes one set.

Covers leaf and extension splits, branch collapses onto a leaf, an
extension and a branch, extension merges, 16-way branches, deletes of
missing keys, the 31/32/33-byte inlining boundary for storage and
withdrawal leaves, and account-leaf deletion through a same-transaction
CREATE2 + SELFDESTRUCT onto a pre-funded mined address (EIP-6780).

* test(tests): add MPT leaf split sharing one nibble

pre:  empty
post: ext(1) -> branch@1 -> {leaf(62), leaf(62)}
The shortest extension a leaf split can create, between the
four-nibble split and the prefix-free root branch.

* test(tests): add MPT leaf split below a root branch

pre:  branch@0 -> {leaf(63), leaf(63)}
post: branch@0 -> {ext(1) -> branch@2, leaf} or {branch@1, leaf}
The split result is re-parented into an existing branch slot instead of
becoming the root, a different frame in every node-walking client.

* test(tests): add MPT ext split at its first nibble

pre:  ext(4) -> branch@4 / ext(1) -> branch@1
post: branch@0 -> {ext(3) -> ..} / branch@0 -> {branch@1 -> ..}
A 4-nibble extension is shortened; a 1-nibble one vanishes without
leaving a zero-length extension behind.

* test(tests): add MPT collapse onto leaf under branch

pre:  branch@0 -> {branch@1 -> {leaf, leaf}, leaf}
post: branch@0 -> {leaf(63), leaf(63)}
The survivor gains the branch nibble and stays in its parent's slot;
nothing merges because the parent is a branch.

* test(tests): add MPT collapse onto branch under branch

pre:  branch@0 -> {branch@1 -> {leaf, branch@2}, leaf}
post: branch@0 -> {ext(1) -> branch@2, leaf}
A fresh 1-nibble extension appears between two branches instead of
merging into an existing one.

* test(tests): add MPT root branch collapse onto branch

pre:  branch@0 -> {branch@1 -> {leaf, leaf}, leaf}
post: ext(1) -> branch@1 -> {leaf, leaf}
The root changes kind from branch to extension.

* test(tests): add MPT collapse onto ext under branch

pre:  branch@0 -> {branch@1 -> {leaf, ext(2) -> branch@4}, leaf}
post: branch@0 -> {ext(3) -> branch@4, leaf}
The extension absorbs the branch nibble; nothing above it merges.

* test(tests): add MPT delete from a three-child branch

pre:  ext(4) -> branch@4 -> {leaf, leaf, leaf}
post: ext(4) -> branch@4 -> {leaf, leaf}
The smallest non-collapsing delete; a client that reduces at two
remaining children passes 16 -> 15 and fails here.

* test(tests): add MPT delete-missing ending at a leaf

pre:  leaf(64)
post: leaf(64)
Control: the walk for the absent key ends at a different leaf; the
root must equal the pre-state root.

* test(tests): add MPT mass delete leaving one child

pre:  ext(4) -> branch@4 -> {16 x leaf}
post: leaf(64)
One block diff takes the branch from 16 children to a collapsed and
merged leaf; the existing cases cover 16 -> 15 and 16 -> 0.

* test(tests): add MPT one-to-sixteen from a leaf

pre:  leaf(64)
post: ext(4) -> branch@4 -> {16 x leaf}
One split of a committed leaf followed by fourteen slot fills of the
branch it just created, all in one block.

* test(tests): add MPT insert then delete in one block

pre:  leaf(64)
post: leaf(64), storage root byte-identical to genesis
Control: the transient write would have split the leaf; the block
diff must leave no residue of it.

* test(tests): add MPT delete then re-insert in one block

pre:  ext(4) -> branch@4 -> {leaf, leaf}
post: the same shape, with the same or a new value
Control: a per-key delete marker must not shadow the later write.

* test(tests): add MPT collapse with an updated survivor

pre:  ext(4) -> branch@4 -> {leaf = 1, leaf = 2}
post: leaf(64) = 7
The collapsed leaf must carry the survivor's new value, not its
committed one.

* test(tests): add MPT child replacement in a branch slot

pre:  branch@0 -> {leaf a, leaf}
post: branch@0 -> {leaf b, leaf}
Child count unchanged while the slot's content is swapped; a
path-keyed cache must not serve the old leaf.

* test(tests): add MPT two shape changes in one block

pre:  ext(2) -> branch@2 -> {l3, ext(2) -> branch@5}
post: ext(3) -> branch@3 -> {new, ext(1) -> branch@5}
A collapse at depth 2 and a split at depth 3 in one diff; neither the
pre shape nor either single-step result.

* test(tests): add MPT inline flip by path growth

pre:  ext(4) -> branch@4 -> {c, ext(3) -> branch@8 -> {a, b}}, a and b
      31 B inline
post: a = leaf(59) 33 B hashed under branch@4, then inline again
The flip comes from the path growing on collapse, not from the value.

* test(tests): add MPT account resurrection in one block

pre:  .. -> branch@4 -> {survivor, doomed}
post: the same shape; doomed is a balance-only leaf again
A per-block destruction record must not shadow the later write.

* test(tests): add MPT storage-root flip with a collapse

pre:  .. -> branch@4 -> {survivor contract, doomed}
post: .. -> leaf whose value carries the flipped storage root
Storage root and account re-pathing change in one block diff.

* test(tests): add MPT withdrawals root under 32 bytes

pre:  no withdrawals
post: one 30-byte root leaf, withdrawalsRoot = keccak256 of its RLP
The root is hashed even below the inlining threshold; only the
withdrawals trie can reach the case.

* test(tests): add MPT withdrawals with empty-path leaves

pre:  no withdrawals
post: branch@0 -> {8: leaf(1), 0: branch@1 -> {leaf(0) ..}}, 3 or 16
      entries, inline and hashed leaves in one branch
Empty-path leaves (prefix 0x20) cannot occur in a state trie.

* test(tests): add MPT index tries by transaction count

pre:  one sender, one recipient
post: the transaction and receipt tries at 17, 129, 130 and 258
      transfers: nibble-1 slot, two-byte keys, a 1-nibble and a
      3-nibble extension
The only extension nodes an index trie can contain.

* test(tests): add MPT constants for new cases

Slots: siblings of TWO_SLOTS_EXT4 at depths 2 and 3 (extension splits
under a branch parent), a pair sharing exactly 10 nibbles (32-byte leaf
from the value side), a triple where two keys share 9 nibbles and a
third shares 8 (inline-to-inline collapse), and sixteen slots with
distinct first nibbles (root-level 16-way branch). Addresses: siblings
of TWO_ADDRS_EXT4 at depths 1 and 3, salts sharing exactly 2 and 3
nibbles with it, and fifteen salts filling the SIXTEEN_ADDRS_BRANCH4
prefix, all for account-trie collapse cells with a controlled parent.
ROOT_PAIR is now spelled through SINGLE_SLOT, the identity five tests
rely on.

* refactor(tests): restyle MPT suite, pin cells

Every docstring now states the node path as `pre:`/`post:` lines in one
notation; client symbol citations are gone. Shape helpers move to
`trie_shape` (`storage_shape`, `account_shape`, `index_shape`,
`covering`, `leaf_rlp_size`). Delete tests are named `test_delete_*`,
genesis-only account tests `test_genesis_*`.

Account shapes are computed over the full genesis set (pre-alloc plus
the fork's system contracts, plus the coinbase after the block) and pin
the parent kind around the mined group with `covering`; the previous
`all(... if d == 4)` was vacuous when no node sat at depth 4. The
storage-root flip test now collapses onto the contract itself, created
through the factory with a salt whose address shares four nibbles with
the doomed one, so storage root and account re-pathing change in one
diff.

The three delete-missing tests merge into one parametrized control (the
write never reaches trie code), the trie-identical per-transaction
variant of the mass delete is dropped, insert-then-delete now covers a
leaf split, an extension split and a branch slot, and the index-trie
test adds counts 145, 257 and 273. Withdrawal values are encoded as a
plain RLP tuple instead of a fork-pinned class.

* test(tests): add MPT root branch collapse onto ext

pre:  branch@0 -> {ext(3) -> branch@4 -> {p, q}, leaf(63)}
post: ext(4) -> branch@4 -> {p, q}
Closes the survivor-extension x parent-root cell; the root changes kind
from branch to extension. A client with a dedicated "extension becomes
root" path computes a different root here and nowhere else.

* test(tests): add MPT ext split under a branch parent

pre:  branch@0 -> {ext(3) -> branch@4 -> {p, q}, leaf(63)}
post: the extension split at offset 0, in the middle, at its last
      nibble, and the 1-nibble case where it vanishes
Every earlier extension split happened at the root; here the result is
re-parented into an existing branch slot, a different frame in every
node-walking client.

* test(tests): add MPT leaf collapse into a nested ext

pre:  ext(2) -> branch@2 -> {l3, ext(2) -> branch@5 -> {l1, l2}}
post: ext(2) -> branch@2 -> {l3, leaf(61)}
The only extension-parent collapse whose merged leaf does not become
the root: the merge and the update of the parent branch's slot happen
in the same step.

* test(tests): add MPT delete-all from every root kind

pre:  ext(4) -> branch@4 / branch@0 / leaf
post: empty
Adds root branch -> empty and root leaf -> empty next to the existing
root extension case, so every root kind reaches the empty trie root.

* test(tests): add MPT deletes around inline leaves

pre:  ext(4) -> branch@4 -> {c, ext(3) -> branch@8 -> {a, b}} with a,
      b inline (31 B) or hashed (33 B) by value size, c always hashed;
      and ext(8) -> branch@8 -> {s, branch@9 -> {x, y}} all inline
post: deleted inline / hashed x survivor flips / stays hashed / stays
      inline, each restored in a second block
Six cells of the (deleted child, survivor encoding) matrix; the
previous single flip test covered one. Hash-keyed stores decode inline
children from the parent and must re-inline a rebuilt survivor.

* test(tests): add MPT 32-byte leaf from the value side

pre:  empty
post: ext(10) -> branch@10 -> {leaf(53), leaf(53)}, 30 B with a 1-byte
      value and exactly 32 B with a 2-byte value
The existing 32-byte case comes from the key side (56 nibbles); a
client computing leaf lengths arithmetically has a separate branch for
a value byte >= 0x80, which only this cell reaches at the threshold.

* test(tests): add MPT inline leaf update, same size

pre:  ext(8) -> branch@8 -> {leaf(55) 31 B, leaf(55) 31 B}
post: the leaf holds a different 1-byte value, still 31 B inline
Next to the existing flip to 33 B: the inline case rewrites the leaf
inside its parent's RLP and never gives it a hash, so dirty tracking
keyed by child hash has nothing to key on.

* test(tests): add MPT cells on client-built nodes

pre:  empty; block 1 builds each geometry, block 2 deletes, block 3
      re-inserts
post: the geometry's shape after the round trip
Adds three-to-two, leaf/ext/branch survivors under a branch parent,
both root collapses and the nested-extension leaf to the three-block
test. Genesis lands in a client's base layer; block-1 nodes live in
its diff layer or dirty cache, which is where these cells were still
untested.

* test(tests): add MPT two paths, two tries, shared node

pre:  branch@0 -> {ext(3) -> branch@4 -> {p, q}, branch@1 -> {1, 14}};
      two contracts with ext(4)/ext(5) tries; two contracts holding the
      same single-leaf trie
post: both root children collapse, or one collapses while the other
      splits; two storage roots change in one diff; one contract's copy
      of the shared node is deleted, the other's grown next block
Touched-prefix sets with two entries under one root, parallel storage
roots, and hash-keyed stores that refcount or drop a node still used
by another trie.

* test(tests): add MPT nested split, root arity, dedup

pre:  ext(2) -> branch@2 -> {l3, ext(2) -> branch@5}; branch@0 with 16
      leaves; ext(4) -> branch@4 -> {p, q}
post: the inner extension replaced by a branch in one step; the root
      branch filled, reduced to 15 and to 1; q round-trips to its
      original value while p really changes
Extension-first-nibble splits so far happened at the root only; root
nodes take dedicated code in several clients; a touched-key applier
must neither skip the changed subtree nor rewrite the unchanged leaf.

* test(tests): add MPT account resurrection variants

pre:  .. -> branch@4 -> {survivor, doomed}
post: the survivor credited in the collapse block; the doomed leaf
      deleted, re-created by a transfer and deleted again across three
      blocks; re-created by a withdrawal, or not when it carries 0 Gwei
The collapse must read the survivor's updated balance; a resurrected
balance-only account is deployable again; withdrawals apply after the
transactions and a zero credit leaves no empty account.

* test(tests): add MPT account collapse cells and arity

pre:  mined siblings at depths 1 and 3 make the parent of the
      collapsing branch a controlled branch or extension; three and
      sixteen accounts under one branch
post: leaf, extension and branch survivors under a branch parent, a
      branch survivor under an extension, 3 -> 2, and 16 -> 1 via
      fifteen CREATE2 deletions in one block
Brings the account trie to parity with the storage cells; each cell's
parent kind is pinned over the full genesis set, so an uncontrolled
account sharing the prefix fails the fill instead of changing the cell.

* docs(tests): note empty genesis accounts inexpressible

`fund_address(addr, 0)` yields an empty account that the filler omits
from the genesis allocation, so EIP-161 clearing of a committed empty
leaf cannot be built through the public API. Recorded next to the other
inexpressible account cases.

* docs(tests): state the MPT coverage grids

The package docstring now carries the survivor x parent grid and the
enumerated classes (splits, root transitions, arity, encoding, one-block
sequences, index tries, account bookkeeping) so the "what is covered"
statement lives in the repository next to the tests.

* fix(tests): use the EIP-7997 genesis factory in MPT account tests

From Amsterdam the fork allocates the deterministic factory in genesis,
so the filler no longer injects it into the pre-alloc on first use of
`deterministic_deploy_contract`, and the `DETERMINISTIC_FACTORY_ADDRESS
in pre` check failed every account test that deletes through it.
Deploy through the factory only on forks without it, and check its
presence in the genesis instead of the pre-alloc.

* test(tests): pin MPT account shapes in every fixture genesis

A state test's own fixture starts from the pre-alloc plus
`pre_allocation()`, the blockchain fixtures derived from it from the
pre-alloc plus `pre_allocation_blockchain()`, but shapes were asserted
only in the second set. Two tests also funded the sender after taking
the snapshot, so its leaf was missing from every set they asserted.
Assert state-test shapes in both sets and take the snapshot once the
pre-alloc is complete. The fixtures are unchanged; 40 of the 92
account fixtures were previously not asserted on the genesis they
ship with.

* test(tests): add MPT collapses under a nested extension

pre:  branch@0 -> {ext(1) -> branch@2 -> {d, ext(1) -> ..}, ..}
      branch@0 -> {ext(2) -> branch@3 -> {d, branch@4}, ..}
post: branch@0 -> {ext(3) -> branch@4 -> {p, q}, leaf(63)}
Extension and branch survivors had an extension parent only at the
root, where the merged node becomes the new root; the nested case, in
which it is written into the parent branch's slot, existed only for a
leaf survivor. Both cells also run on client-built nodes. They reuse
the slots of the extension-split tests, so no new constants.

* docs(tests): state why the MPT account deletions need an ORIGIN beneficiary

The account tests delete a genesis-funded leaf by CREATE2-ing init code
that self-destructs to ORIGIN. From EIP-8246 a self-destructed account
keeps its balance and is only removed if that balance is zero, so the
deletion depends on the balance leaving the account. A self-destruct to
the contract itself would leave a balance-only account on Amsterdam and
later forks; state that next to the init code and in the module
docstring.

* fix(tests): assert the MPT shapes the docstrings claim

Four fill-time checks did not pin what their docstrings state:

- The inline-survivor test accepted 31 or 33 bytes for either deleted
  leaf; each case now asserts its own size.
- The storage-root flip test claimed an extension above the collapsing
  branch without asserting it.
- The withdrawal resurrection test asserted only the genesis shape; it
  now asserts the two-child branch when the withdrawal re-creates the
  account and the collapsed leaf when it credits nothing.
- The index test compared the root node of two paths of one trie,
  which are always equal.

No fixture changes.

* refactor(tests): share MPT test helpers and table-drive the deletes

- `SLOT_WRITER`, `DELETE_INITCODE` and `GWEI` live in `constants.py`
  next to the salts mined against them; both test modules import them
  and encode writer calldata the same way.
- The twelve genesis-trie storage deletes repeated the fourteen cases
  of the block-built delete test. One `DELETE_CASES` table now drives
  both tests and pins each case's path before and after the delete,
  where the block-built test only checked that the shape changed.
- The two standalone account collapses are rows of the parametrized
  collapse test. Its survivor kind is the node below the collapsing
  branch, and for extension and branch survivors it asserts that the
  merged extension ends at the survivor's branch. Four blockchain tests
  share one funding prologue.
- The sixteen-to-one storage delete is one transaction, so it is a
  state test.
- Withdrawal leaves are sized from `Withdrawal.to_serializable_list()`;
  a check of the reference trie's own root hashing is gone.
- `_alloc` gives way to the framework's `Storage`, `_shape` to
  `account_shape`, and the root arity test takes its committed storage
  as a parameter.

Fixture content is unchanged except for two cases: the account
extension-merge collapse funds its second address with 1 wei instead
of 2, and the sixteen-to-one storage delete gains state fixtures, its
blockchain fixtures now derived from them.

* docs(tests): tighten the MPT test docstrings

The package docstring states the method and defines the shape notation
once; the coverage inventory that duplicated the test list is gone.
Test docstrings give one sentence for the scenario and its rule, then
the `pre:`/`post:` paths, without per-case labels, `Control:` prefixes,
claims about client internals or comparisons with other software.
Helper docstrings use the imperative mood.

* fix(tests): fill the MPT account tests in separate pre-alloc groups

A shared group adds other tests' accounts to the genesis whose shape the tests assert, and the two storage-root flip cases collide on one address.

* refactor(tests): assert the MPT account shapes on the seeded pre

Since #3830, `pre` holds the whole genesis of the fixture being filled, so the tests stop rebuilding it from the fork's pre-allocations. Fixtures are unchanged.

* feat(tests): add MPT code-hash change on a collapse survivor

CREATE2 deploys code onto the balance-only survivor in the block that deletes its sibling, at the address `SURVIVOR_SALT` already places there.
fselmo added a commit that referenced this pull request Oct 19, 2026
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
fselmo added a commit that referenced this pull request Oct 20, 2026
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
github-actions Bot pushed a commit that referenced this pull request Oct 21, 2026
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
CPerezz added a commit to CPerezz/execution-specs that referenced this pull request Oct 26, 2026
One conflict, in the export list of specs/__init__.py: forks/bogota
added ReceiptStatusExpected next to this branch's Reorg* exports. The
resolution keeps both, in alphabetical order.

Two forks/bogota API changes break ReorgTest without conflicting:

- make_genesis takes the fixture format instead of
  apply_pre_allocation_blockchain (ethereum#3830). ReorgTest passes
  BlockchainEngineReorgFixture; the inherited genesis_pre_allocation
  gives the same blockchain pre-allocation as before.
- validate_receipt_status is replaced by run_block_verifications.
  ReorgTest calls it under the same guard as BlockchainTest.

A pre built outside the pre fixture must now opt into the genesis
accounts, so the reorg unit tests call with_genesis_accounts().
fselmo added a commit that referenced this pull request Oct 27, 2026
* Add EIP-7805 on top of Amsterdam (#1928)

* src(focil): add FOCIL src features + first set of FOCIL tests (#2643)

* feat: continue focil implementation

* feat: tests added

* fix(tests): derive 7702 set-code gas in FOCIL test from fork calculator

* feat: parametrize existing tests

* chore: move focil tests to bogota folder

* feat: update feature.yaml with focil release support

* feat: Bogota fork + EIP-7805 engine_newPayloadV6 bump (#3028)

* feat: Bogota fork + EIP-7805 engine_newPayloadV6 bump

The first tests-focil release fixtures were filling FOCIL tests with
`newPayloadVersion: 5` because no EIP class declared the version bump
and there was no fork class to host EIP-7805. As a result `consume engine`
appended `inclusionListTransactions` as a 5th positional arg to V5, which
isn't a wire format defined by any Engine API spec — all clients reject it.

This adds:
- EIP7805 mixin under eips/bogota/ with engine_new_payload_version_bump
  and engine_forkchoice_updated_version_bump
- Bogota fork class (Amsterdam + EIP7805), reusing Amsterdam's t8n
- Switches FOCIL tests' valid_from marker to Bogota

After refilling, fixtures now use engine_newPayloadV6 / forkchoiceUpdatedV5,
matching the canonical Engine API PR (ethereum/execution-apis#626).

* Address review: BogotaEIPs auto-discovery + feature.yaml --from=Bogota

- Add BogotaEIPs sentinel that auto-discovers eip_*.py modules under
  eips/bogota/, mirroring AmsterdamEIPs. Bogota fork class now inherits
  BogotaEIPs instead of referencing eips.EIP7805 explicitly.
- Switch focil entry in .github/configs/feature.yaml from
  --until=Amsterdam to --from=Bogota so the FOCIL job fills against
  the Bogota fork rather than ending at Amsterdam.

---------

Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>

* fix: focil release range

* feat: support bogota in build matrix script

* feat: print draft release url for easier discovery

* Rename inclusion_list_satisfied field (#3092)

* refactor(spec-specs): rename `inclusion_list_satisfied` part 2 (#3095)

* fix(specs): Rebase issue

* fix(specs): More rebase issues

* fix(tests,tools): make the EIP-7805 FOCIL tests fill on the current Amsterdam (#3370)

* fix(tests,tools): make the EIP-7805 FOCIL tests fill on the current Amsterdam

21 of the 24 FOCIL tests do not fill on this branch. Three independent causes,
none of them in the tests' assertions.

t8n passed decoded transactions where raw ones are expected.
check_inclusion_list_transactions takes Tuple[LegacyTransaction | Bytes, ...]
and calls get_transaction_hash, which asserts isinstance(tx, (LegacyTransaction,
Bytes)); the caller passed convert_transaction(...), which returns a decoded
fork transaction, so every typed-transaction case died on AssertionError. Both
tuples now go through encode_transaction.

The scenarios left no gas budget for the block's own access list. They size
block_gas_limit to their transactions alone, but the EIP-7928 budget is
block_gas_limit // GAS_BLOCK_ACCESS_LIST_ITEM, and the system-contract
predeploys touched every block plus senders, recipients and coinbase come to
about 30 items against a limit of about 22.

Raising the limit alone would change what the scenarios assert, because the gas
a pending inclusion-list transaction is allowed comes from block_gas_limit minus
the gas the included transactions use. So build_block raises the limit and
spends exactly the same amount on ballast transactions, leaving remaining_gas --
and therefore every "fits" and "does not fit" boundary -- unchanged. The ballast
is a whole number of empty transfers so the amount is always representable on
the fork's calldata-cost lattice.

One funding transfer predated value-carrying transactions costing extra
intrinsic gas. test_unsatisfied_when_block_tx_funds_pending_il_sender sizes and
funds alice's transfer with calc(), but it carries value, so it needs
calc(sends_value=True) and was failing INTRINSIC_GAS_BELOW_FLOOR_GAS_COST.

Assertions are untouched: of the changed lines in test_focil.py, none touch
expected_status, pytest.param, assertions, sender balances, nonces or
inclusion-list composition. Scenario ids are unchanged and the reference
implementation validates all 24 expected outcomes during fill.

* Apply suggestions from code review

Co-authored-by: Mario Vega <marioevz@gmail.com>

* fix: lint

---------

Co-authored-by: Mario Vega <marioevz@gmail.com>

* fix(specs): fix check_inclusion_list_transactions

* fix(tests): fix import

* refactor(test-specs,tests): Refactor inclusion-list spec definitions, existing tests (#3373)

* feat(test-specs): Add inclusion list satisfied verification to block

* fix(test-specs): fix framework on missing inclusion list

* refactor(tests): refactor existing focil tests

* nit(test-specs): Unnecessary comment

* Apply suggestions from code review

Co-authored-by: spencer <spencer.tb@ethereum.org>
Co-authored-by: Mario Vega <marioevz@gmail.com>

---------

Co-authored-by: spencer <spencer.tb@ethereum.org>

* feat(specs,test-specs,tests): Update Engine API to latest Bogota spec, implement IL inclusion_test (#3401)

* fix(specs,test-forks): Fix Bogota filling with EIP-7805

* fix(test-fixtures,test-specs): Update Engine API to latest version

* fix(test-fixtures): Label suffix fix (backport safe)

* fix(test-fixtures): Unit tests (backport safe)

* feat(test-specs): Implement inclusion list variant of inclusion_test marked tests

* bug(specs): Fix type-3, invalid RLP, and invalid chain id txs in inclusion lists

* bug(test-specs): Fix inclusion_test filling

* fix(tests): Blob test

* fix(tests): clear rlp_modifier when building inclusion-list variants (#3406)

The inclusion-list variant moves the last transaction of the last block
out of the block body and into the inclusion list, then clears the
expectations derived from it: header_verify, expected_gas_used and
expected_block_access_list. rlp_modifier was left in place.

rlp_modifier force-writes header fields onto the built block after the
transition tool has run, and tests compute it from the pre-move
transaction list. The moved transaction therefore keeps contributing to
the header even though it no longer executes in the block, producing
fixtures whose header contradicts their own body.

Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>

* fix(tests): count top-frame state gas once in FOCIL OOG scenarios

* chore(ci,test-consume,test-specs,tests): backport devnets/focil/0 fixes to eip-7805 (#3595)

* fix(github): EIP-7805 Devnet config

Backported without the whitespace-only runs-on reformatting.

(cherry picked from commit 5f46130)

* fix(consume): require null inclusionListSatisfied on non-VALID payloads (#3470)

Port the engine simulator's inclusionListSatisfied guard from #3410 to the
devnets/focil/0 simulator. The check previously asserted a non-null response
field whenever the fixture stamped one, regardless of the payload's expected
status; execution-apis bogota.md requires the field to be null for any payload
not deemed VALID, so the check demanded a spec violation. tests-focil-devnet
v0.2.0 stamps the field on 5,489 expected-INVALID engine fixtures, which fail
for every client on the Hive focil board with "expected
inclusion_list_satisfied in response".

The ported guard is verbatim from #3410: a payload not deemed VALID must
report null, and only VALID payloads have the fixture's expected verdict
enforced.

Fixes #3436 for the consume path independently of a fixture refill.

(cherry picked from commit 67a9345)

* fix(tests,test-specs): fix invalid inclusion-list engine fixture generation (#3471)

* fix(tests): clear rlp_modifier when building inclusion-list variants (#3406)

The inclusion-list variant moves the last transaction of the last block
out of the block body and into the inclusion list, then clears the
expectations derived from it: header_verify, expected_gas_used and
expected_block_access_list. rlp_modifier was left in place.

rlp_modifier force-writes header fields onto the built block after the
transition tool has run, and tests compute it from the pre-move
transaction list. The moved transaction therefore keeps contributing to
the header even though it no longer executes in the block, producing
fixtures whose header contradicts their own body.

Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>

* fix(focil): omit inclusion list result for invalid payloads

* fix(tests): give test_tx_gas_limit a block gas limit a valid block fits in

The test pinned the block gas limit to 21000 with a 21001-gas transaction to
trigger GAS_ALLOWANCE_EXCEEDED. On forks with the EIP-7928 block-access-list
item budget (gas_limit // 2000) a 21000-gas-limit block allows 10 items, which
is below the protocol-level writes of even an empty block, so no block in this
environment can be valid. The test itself never noticed - its block is expected
invalid - but its auto-generated inclusion-list variant moves the failing
transaction into the inclusion list and expects the emptied block to be VALID,
producing a fixture that the spec's own state transition rejects
(tests-focil-devnet@v0.2.0, blockchain_test_engine_inclusion_list, the one
non-blob entry in the 127 broken engine fixtures).

Scale the numbers to 100_000/100_001: the allowance check still fires the same
way, and the item budget (50) now accommodates an empty block, so the
inclusion-list variant fills to a genuinely valid block.

* fix(test-specs): splat the metadata regression block from untyped kwargs

The regression test from #3445 constructs a BuiltBlock through
model_construct with object() sentinels for fields that
get_fixture_engine_new_payload only forwards, which the typechecker rejects
for missing and mistyped named arguments. model_construct skips validation
and the sentinels are never read, so splat them from a dict[str, Any]
instead of passing them as checked named arguments.

* fix(tests): align test_tx_gas_limit with its #3566 counterpart

Byte-identical to the forks/amsterdam hunk so the next focil rebase resolves cleanly; fixture output is unchanged.

---------

Co-authored-by: Marc <Marchhill@users.noreply.github.com>
Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
Co-authored-by: chugarchugarr <josephlerma19@gmail.com>
Co-authored-by: Felipe Selmo <fselmo2@gmail.com>
(cherry picked from commit 65f4222)

---------

Co-authored-by: marioevz <marioevz@gmail.com>
Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com>
Co-authored-by: Marc <Marchhill@users.noreply.github.com>
Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
Co-authored-by: chugarchugarr <josephlerma19@gmail.com>

* fix(ci,test): fix issues with PR CI runs in focil branch (#3594)

* fix(tests): use a nonce collision in the FOCIL creation-collision scenario

* refactor(spec-specs,test-forks,tests): move EIP-7805 onto the Bogota fork module

* fix(test-forks): pin Bogota's FOCIL engine method versions

The EIP-7805 mixin bumps Bogota to engine_newPayloadV6 and engine_forkchoiceUpdatedV5; the fork test still pinned the pre-FOCIL newPayload version.

* docs(spec-specs): drop blob exclusion claim from the IL check

The inclusion list check never skipped blob transactions, and EIP-7805 does not ask it to; test_block_with_pending_blob_il_tx_is_valid expects an appendable blob transaction to leave the list unsatisfied.

* refactor(spec-specs): drop repeated sender checks in the IL check

check_transaction already recovers the sender and runs validate_transaction, so the IL check now mirrors process_transaction: the chain id check, then check_transaction.

* docs(spec-specs): keep the forks/bogota fork docstring

EIP branches that each rewrite the Bogota fork docstring conflict when the devnet workflow merges them, so this branch leaves it as forks/bogota has it; the EIP is listed there when it lands.

* fix(tests): skip inclusion-list variants whose entry cannot fit a list (#3502)

* fix(tests): skip inclusion-list variants whose entry cannot fit a list

* fix(test-specs,test-forks): fail oversized IL entries a test wrote

Only the inclusion-list variant moves a test's own transaction into the list, so only it skips an entry over the limit; a list the test wrote fails with a test correctness error. The limit is now a fork accessor named after MAX_TRANSACTIONS_BYTES_PER_INCLUSION_LIST, and the unit test pins both sides of it for both cases.

---------

Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
Co-authored-by: fselmo <fselmo2@gmail.com>

* feat(consume): check inclusionListSatisfied semantics and probe engine_getInclusionListV1 (#3410)

* feat(consume): check inclusionListSatisfied on forkchoice responses to a valid head

* feat(consume): probe engine_getInclusionListV1 after chain import on inclusion-list fixtures

* feat(test-forks): version engine_getInclusionList by fork

EIP-7805 introduces engine_getInclusionListV1, so its mixin now bumps the method version like the other Engine API methods, and the method version test pins it at Amsterdam and Bogota.

* fix(consume): check only what getInclusionList must return

The spec leaves transaction selection to the client and caps the sum of the transactions' bytes, so the probe no longer requires the seeded transactions back and no longer counts the list's RLP header. Its method version and size limit come from the head block's fork.

* fix(test-specs): omit inclusionListSatisfied on engine API errors

A payload rejected with an engine API error code is not deemed VALID, so its fixture carries no inclusionListSatisfied, as for payloads with an expected exception.

* fix(test-specs): limit inclusion-list variants to engine formats

Only the engine and engine X simulators check inclusionListSatisfied, so sync and stateful fixtures no longer get an inclusion-list variant. No inclusion test generates one today, so no fixture changes.

* fix(test-specs): reject inclusion tests the variant cannot use

An inclusion test whose last block has no transaction, or is invalid only through block exceptions, now fails as a test correctness error instead of crashing or filling a wrong satisfied expectation.

* fix(spec-specs): raise catchable errors for bad transaction bytes

decode_transaction indexed empty bytes and asserted on a 0xff first byte, so the inclusion list check crashed on such entries instead of skipping them. Both now raise an InvalidTransaction.

---------

Co-authored-by: fselmo <fselmo2@gmail.com>

* feat(tests): Increase coverage for EIP-7805 tests (#3804)

* feat(test-specs): send inclusion list entries as raw bytes

Entries reach the transition tool and the fixture as the same bytes, so the verdict is computed on what clients receive, including rlp_override. Block.inclusion_list_txs also accepts Bytes entries for malformed cases.

* feat(test-forks): add the inclusion list blob-entry predicate

The inclusion list variant now reads fork.inclusion_list_counts_blob_entries() for a moved blob transaction, so a spec change on blob entries is a one-line flip in the EIP-7805 mixin.

* feat(tests): skip undecodable inclusion list entries

Each malformed entry (empty, a 0x00 byte before a legacy transaction, type bytes 0x7f, 0xff and the first undefined type, a known type with another type's payload, a trailing byte, truncated legacy) leaves the payload satisfied alone and lets the check reach an appendable transaction after it; two well-formed controls show the reused payloads are appendable.

* feat(tests): extend FOCIL appendability coverage

Covers every non-blob type through with_all_typed_transactions, the fee cap and value in the balance check, withdrawals funding a sender only from the next block, priority fees funding the fee recipient, a delegated sender drained without a nonce change, an entry past one member's byte limit, and entries that count as included only by their exact bytes (access list or authorization list changed); the blob test reads the fork's blob-entry predicate.

* feat(tests): mark invalid-authorization set-code txs as inclusion tests

A set-code transaction whose authorizations all fail is still valid, so its inclusion list variant expects an omitted copy to leave the payload unsatisfied.

* feat(tests): cover more FOCIL appendability checks

Add L2 type bytes 0x7d and 0x7e to the undecodable-entry cases, gas limits above the transaction cap at each block room's edge, and one-field pairs for chain id, base fee and sender code.

* feat(spec-specs,test-forks): skip blob entries in the IL check

EIP-7805's author confirmed that a blob transaction in the inclusion list is ignored for satisfaction, so the check skips it and the fork's blob-entry predicate flips to false.

* feat(test-forks): add the merged inclusion list size limit

The consensus layer merges up to 16 committee members' lists into the one sent with a payload, so it carries at most 128 KiB; a test that writes a larger list fails to fill.

* feat(tests): pin FOCIL ordering, encoding and merged list size

Adds a system call funding a pending sender before and after the block's transactions, non-canonical RLP entries, and a full 128 KiB merged list whose last entry still counts.

* fix(tests): stop the FOCIL transfer size search from looping

Re-signing can shorten r or s by a byte, which left the length search stuck one byte either side of the target for about 1 in 80 sender keys. A miss now also changes the calldata byte, which draws a new signature; all 400 probed keys converge within four tries.

* refactor(test-specs): send malformed IL entries through rlp_override

Block.inclusion_list_txs takes Transactions again; a malformed entry is a transaction with rlp_override, the idiom malformed block transactions already use. The framework still passes each entry's rlp() bytes to the transition tool and the fixture, so the verdict is judged on exactly what clients receive.

* refactor(test-forks): drop the inclusion list blob-entry predicate

EIP-7805's check now skips blob transactions outright (ethereum/EIPs#12470), so the fork method that hedged it was False on every fork. The inclusion list variant expects a satisfied payload when the moved transaction is a blob transaction, and its unit test no longer monkeypatches the predicate.

* refactor(test-forks): derive the merged IL size from the list limit

The merged inclusion list limit is now the committee size times the per-list limit, so 8 KiB has one source.

* feat(tests): pin FOCIL blob and chain id skips

Blob and wrong-chain entries now also run before an appendable transaction, so a client that stops at a skipped entry fails. The blob test adds a control that includes the blob transaction in the block, and the chain id test adds type-2 and unprotected legacy entries.

* feat(tests): spend state gas before the FOCIL gas-cap state edge

The block now also creates an account, so the pending transaction's state room is the block gas limit minus state gas already used. A client that ignores block state gas used fails the one-over case.

* refactor(tests): name FOCIL type-2 fields and trim docstrings

type_2_entry keys its raw field overrides by RLP field name and reads chain id and to from the transaction instead of decoding its own encoding. Also trims the undecodable-entry docstring and sets the match-by-bytes expectation in each branch.

* refactor(tests): set FOCIL expectations in each branch

The new FOCIL tests now name the expected verdict in each branch and use match like the rest of the module. The base fee pair is named for its boundary, and the same_bytes case says it is the control. Fixtures are unchanged.

* docs(spec-specs): name every skip in the IL check docstring

The inclusion list check docstring named only the blob skip; it now also names undecodable entries and entries signed for another chain.

* refactor(test-specs): fund the IL unit test's signing sender

The IL verdict unit test funds the sender the default key signs for, instead of a hard-coded address that would leave the entry unfunded if that default changed.

* feat(tests): tighten FOCIL entry matching and drain cases

The bytes-matching test gains an entry that changes only the signature's s, and the drained-sender test takes one wei instead of the whole balance, so the pending tx misses being affordable by one wei. The sized transfer reads its nonce from the sender.

* feat(tests): skip FOCIL legacy entries with over-64-bit chain ids

A pending legacy IL tx whose v encodes this chain's id plus 2**64 has an invalid v and is skipped, so a client that truncates the chain id to 64 bits, and counts the entry, fails it.

* chore(ci): fill devnet releases for every fork through Bogota

Devnet releases from this branch now fill --until=Bogota, split across the fork ranges, as the frames and bogota devnet releases do (#3418), instead of Bogota alone.

* fix(test-specs): seed genesis accounts in FOCIL fixture tests

Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().

* ✨ feat(tests): Extend focil test coverage (#3868)

* ✨ feat(tests): Extend focil test coverage

* fix(tests): drop duplicated parametrize line

* fix(tests): keep the parity bit in the y_parity_2 IL case

With v=2, a client that reads only the low bit recovers an unfunded sender for types 2 and 4, so the list was satisfied for the wrong reason; v + 2 recovers the funded sender instead.

* test(tests): drop the IL authorization-list test

The inclusion_test variant of EIP-7702's test_empty_authorization_list already covers the empty list, and the type-4 base-fee case covers an appendable one.

* refactor(tests): derive the IL signature test's gas limit

The gas limit comes from the fork's intrinsic cost calculator, and each test builds the one authorization a type-4 tx needs.

* refactor(tests): run IL signature and base-fee cases on all typed txs

with_all_typed_transactions picks up every non-blob type a fork adds, where the hand-written type list would not.

* fix(tests): fund the key a lenient s_zero recovery yields

With s = 0, a client without the s > 0 check recovers the key -z / r and found that sender unfunded, so the case passed for the wrong reason.

---------

Co-authored-by: raxhvl <raxhvl@users.noreply.github.com>
Co-authored-by: fselmo <fselmo2@gmail.com>

* feat(spec-specs,test-forks,tests): implement EIP-8198 quick slots (#3703)

Co-authored-by: spencer-tb <spencer.tb@ethereum.org>

* fix(tests): price the value transfer in blob transition txs

The pre-fork blob transactions send value to an existing EOA, which EIP-2780 charges in the intrinsic gas, so every one was rejected once a transition from Amsterdam changed the blob schedule.

* docs(spec-specs): keep the forks/bogota fork docstring

EIP branches that each rewrite the Bogota fork docstring conflict when the devnet workflow merges them, so this branch leaves it as forks/bogota has it; the EIP is listed there when it lands.

* feat(tests): extend EIP-8198 coverage (#3812)

* feat(tests): reject the new blob fee fraction one block early

A client that switches to EIP-8198's update fraction on the last Amsterdam block prices blobs lower and accepts every existing fork test; the new case offers that lower price there and expects the transaction rejected.

* docs(tests): correct the EIP-8198 blob suites coverage entry

The outside blob suites run at the Amsterdam to Bogota transition through two tests only, and this branch changes one of their files, so the entry names them instead of saying they cover the transition unchanged.

* refactor(tests): derive the EIP-8198 base fee rounding edges

The base fees where an empty block stops lowering the fee and a full block's rise grows past one wei now come from the fork's numerator, denominator and elasticity, so a new ratio moves them; the values at 5/48 are unchanged.

* refactor(tests): parametrize the blob fee rejection by block index

The two cases now differ only in which block is rejected, as in test_base_fee_wrong_rule_across_fork, so the unreachable raise goes; the filled fixtures are unchanged.

* docs(tests): name the base fee case instead of its value

The comment now points at first_larger_rise_after_full_block, since the base fees are derived from the fork.

* fix(spec-specs,tests): keep the blob schedule unchanged at Bogota (#3871)

* fix(spec-specs,tests): keep the blob schedule unchanged at Bogota

EIP-8198 pairs a slot duration change with an EIP-7892 blob schedule entry
at the upgrade, so Bogota inherits the blob target, maximum and update
fraction like any named fork after Prague. Drop the provisional 12/18 values
from the fork and the tests that asserted them.

* feat(tests): pin the blob schedule unchanged at the Bogota fork

Rewrite the removed blob transition tests to check that the fork block keeps the parent fork's blob target, maximum, and update fraction, and mark the block-level constraint section not applicable.

* refactor(test-forks,tests): drop the blob schedule entry mentions

Remove the sentences describing the EIP-7892 entry, which this EIP no longer carries, and take the fork block's blob counts from the new fork as the other transition tests do.

---------

Co-authored-by: fselmo <fselmo2@gmail.com>

* feat(spec-specs,tests): derive Bogota base fee from slot spacing (EIPs#12552) (#3873)

* feat(spec-specs,tests): derive the bogota base fee change from slot spacing

* fix(spec-specs,test-forks): cap the Bogota base fee change at 1/8

Count a spacing longer than twelve seconds per slot as twelve, as EIPs#12552 now specifies, so the change never exceeds the Amsterdam rule's. The framework reads the denominator from the parent fork instead of the 8 * 12 literal.

* feat(test-exceptions): add INVALID_SLOT_NUMBER

EIP-8198 rejects a block whose slot number does not exceed its parent's, so tests need an exception to expect for it.

* feat(tests): pin the EIP-8198 fork block, cap and slot rule

Check that the fork block keeps the 1/8 rule, including after a missed first slot, that spacing above twelve seconds per slot is capped, that a slot gap whose product exceeds 64 bits is handled, and that the slot number must increase from the fork block on.

* fix(tests): restore the osaka BPO transition parent timestamp

With the blob schedule unchanged at Bogota, this test no longer reaches a Bogota transition, so the earlier timestamp change is not needed.

* feat(test-forks,test-specs): add header_slot_number_must_increase

Name the EIP-8198 slot ordering rule as its own fork accessor, and give a state test's block one slot number shared by its state and blockchain fixtures, defaulting to one slot after the genesis block.

* refactor(tests): gate the SLOTNUM slot cases on slot ordering

Branch on header_slot_number_must_increase instead of the base fee predicate, since slot ordering is the rule the cases work around.

* feat(tests): cover a fork block after two missed slots

Add a fork block whose spacing per slot is not a whole number of seconds, derive the cap cases from the reference slot duration, and space the missed-slot case at the usual slot duration.

* docs(spec-specs): note the slot number rule in validate_header

* feat(tests): cover uneven slot spacing and the 64-bit cap

Add base fee cases whose spacing per slot is not a whole number of seconds, and a pair either side of where the twelve-second cap exceeds 64 bits.

---------

Co-authored-by: fselmo <fselmo2@gmail.com>

* fix(tooling): build the docker images on uv's trixie base (#3882)

Astral stopped publishing bookworm uv images after 0.9.30, so the python3.12-bookworm-slim base is stuck below the uv 0.13.4 that #3880 requires and the repository image fails to build. Use the python3.12-trixie-slim tag, which follows current uv.

* Besu: add BlockException.INVALID_SLOT_NUMBER mapping

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.com>

---------

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.com>
Co-authored-by: Jihoon Song <jihoonsong@users.noreply.github.com>
Co-authored-by: felix <felix314159@users.noreply.github.com>
Co-authored-by: Marc <Marchhill@users.noreply.github.com>
Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
Co-authored-by: marioevz <marioevz@gmail.com>
Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com>
Co-authored-by: spencer <spencer.tb@ethereum.org>
Co-authored-by: fselmo <fselmo2@gmail.com>
Co-authored-by: chugarchugarr <josephlerma19@gmail.com>
Co-authored-by: spencer <spencer.taylor-brown@ethereum.org>
Co-authored-by: raxhvl <10168946+raxhvl@users.noreply.github.com>
Co-authored-by: raxhvl <raxhvl@users.noreply.github.com>
Co-authored-by: Barnabé Monnot <b@barnabemonnot.com>
Co-authored-by: Barnabas Busa <busa.barnabas@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
fselmo added a commit that referenced this pull request Oct 27, 2026
Since #3830, a pre built outside the pre fixture must opt into the fork's genesis accounts, so the inclusion-list fixture tests call with_genesis_accounts().
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.

2 participants