Skip to content

fix(execute): derive the EOA start key instead of randomising it - #3504

Open
jochem-brouwer wants to merge 1 commit into
ethereum:forks/amsterdamfrom
jochem-brouwer:pr/derive-eoa-start
Open

fix(execute): derive the EOA start key instead of randomising it#3504
jochem-brouwer wants to merge 1 commit into
ethereum:forks/amsterdamfrom
jochem-brouwer:pr/derive-eoa-start

Conversation

@jochem-brouwer

Copy link
Copy Markdown
Member

--eoa-start defaulted to randint(0, 2**256), so every run handed pre.fund_eoa() a different set of addresses and nothing in the configuration could pin them. fill-stateful loads this plugin too, which makes a filled prestate unreproducible: two builds from one snapshot agree until the first test EOA appears and diverge on every block after it. Measured on snapshot 24,402,727, two 7,736-payload builds shared blocks 24,402,728 and 24,402,729 -- neither uses a test EOA -- then split at 24,402,730 on transactions identical in length, nonce, gas price, gas limit and value, differing only in recipient. A prestate can therefore only be shipped as a datadir or a replay bundle, and fixtures record snapshotBlockHash, so a suite filled against one build is rejected against an equivalent rebuild of it.

Derive the default instead. Two properties the randomness did provide have to survive being pinned, so both go into the derivation:

  • xdist workers. Each worker process evaluates the option default for itself, so a random one gave every worker a range of its own. Nothing else offsets them and session_worker_key takes the first key from this iterator, so a bare constant would have every worker funding and spending one account.
  • concurrent runs against a shared network. Two runs sharing a range would race each other's nonces. The seed key already has to belong to a single command, so it separates runs whenever one is set, by flag or by RPC_SEED_KEY.

keccak256(b"eoa-start:<worker>[:<seed key>]") >> 8 gives 248 bits: never the invalid key zero, and far enough below the secp256k1 group order that counting upwards from it stays valid for any run length. An explicit --eoa-start still wins, which is what reproduces a run made before this change.

Two fill-stateful sessions with RPC_SEED_KEY=0x..01 now both report 0x4f4b0f0b0af2385d3ff86ac2ba5d9e2529cd9bc5415aefce34b9f848e9e66f, and 0x..02 reports a different one.

Description

Related Issues or PRs

N/A.

Checklist

  • Ran fast static checks to avoid CI fails, see Code Standards & Verifying Changes: just static
  • PR title has the form <type>(<area>): <title>, 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-->

`--eoa-start` defaulted to `randint(0, 2**256)`, so every run handed
`pre.fund_eoa()` a different set of addresses and nothing in the configuration
could pin them. `fill-stateful` loads this plugin too, which makes a filled
prestate unreproducible: two builds from one snapshot agree until the first
test EOA appears and diverge on every block after it. Measured on snapshot
24,402,727, two 7,736-payload builds shared blocks 24,402,728 and 24,402,729 --
neither uses a test EOA -- then split at 24,402,730 on transactions identical
in length, nonce, gas price, gas limit and value, differing only in recipient.
A prestate can therefore only be shipped as a datadir or a replay bundle, and
fixtures record `snapshotBlockHash`, so a suite filled against one build is
rejected against an equivalent rebuild of it.

Derive the default instead. Two properties the randomness did provide have to
survive being pinned, so both go into the derivation:

- xdist workers. Each worker process evaluates the option default for itself,
  so a random one gave every worker a range of its own. Nothing else offsets
  them and `session_worker_key` takes the first key from this iterator, so a
  bare constant would have every worker funding and spending one account.
- concurrent runs against a shared network. Two runs sharing a range would race
  each other's nonces. The seed key already has to belong to a single command,
  so it separates runs whenever one is set, by flag or by RPC_SEED_KEY.

`keccak256(b"eoa-start:<worker>[:<seed key>]") >> 8` gives 248 bits: never the
invalid key zero, and far enough below the secp256k1 group order that counting
upwards from it stays valid for any run length. An explicit `--eoa-start` still
wins, which is what reproduces a run made before this change.

Two fill-stateful sessions with RPC_SEED_KEY=0x..01 now both report
0x4f4b0f0b0af2385d3ff86ac2ba5d9e2529cd9bc5415aefce34b9f848e9e66f, and
0x..02 reports a different one.
@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.90%. Comparing base (814b313) to head (697cd87).
⚠️ Report is 2 commits behind head on forks/amsterdam.

Additional details and impacted files
@@               Coverage Diff                @@
##           forks/amsterdam    #3504   +/-   ##
================================================
  Coverage            93.90%   93.90%           
================================================
  Files                  624      624           
  Lines                36905    36905           
  Branches              3326     3326           
================================================
  Hits                 34655    34655           
  Misses                1569     1569           
  Partials               681      681           
Flag Coverage Δ
unittests 93.90% <ø> (ø)

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.

@jochem-brouwer jochem-brouwer added the backport benchmarks/amsterdam On merge, bot cherry-picks the squash commit to `benchmarks/amsterdam` and opens a PR. Squash only. label Sep 2, 2026
@marioevz

marioevz commented Sep 2, 2026

Copy link
Copy Markdown
Member

Hi! I've been meaning to do something like this for a long time, but with a different purpose: In execute, we would like to be able to first run the setup of a set of tests, and then in a different process and at a later point, run the actual tests or a subset of them.

I think the only conflict with this approach and the other goal we have is that if the test ends up being assigned to a different worker, the setup will no longer be valid.

I want to suggest using keccak256(b"eoa-start:<test_id>[:<seed key>]") >> 8, where test_id replaces worker_id in order for the test to have the same EOAs regardless of which runner it ends up running in. The tricky part for execute at least is <seed key> because we would most likely always want to set this in order to be able to run the same test multiple times at a later date, and it could be automatically derived or passed with a flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport benchmarks/amsterdam On merge, bot cherry-picks the squash commit to `benchmarks/amsterdam` and opens a PR. Squash only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants