Skip to content

feat: add EIP-7864 binary trie mode for state generation#1

Closed
CPerezz wants to merge 3 commits into
mainfrom
feat/binary-trie-support
Closed

feat: add EIP-7864 binary trie mode for state generation#1
CPerezz wants to merge 3 commits into
mainfrom
feat/binary-trie-support

Conversation

@CPerezz
Copy link
Copy Markdown
Owner

@CPerezz CPerezz commented Feb 4, 2026

Summary

  • Add --binary-trie flag to generate state using the EIP-7864 binary trie instead of the default Merkle Patricia Trie (MPT)
  • Extract batchWriter type to eliminate batch-writing duplication between MPT and binary trie paths
  • Integrate binary trie mode through genesis block writing, geth wrapper scripts, and Kurtosis launcher
  • Update README with trie mode documentation and --binary-trie CLI flag

Commit breakdown

  1. feat: add EIP-7864 binary trie mode — core implementation: writeStateBinaryTrie, batchWriter extraction, CLI flag, genesis integration, shell/Kurtosis scripts, README
  2. test: add comprehensive binary trie test coverage — 7 new test functions covering generation, DB content, reproducibility, golden value pinning, genesis integration, and e2e
  3. fix: address PR review findings — error propagation in encodeStorageValue, goroutine leak prevention via sync.Once-guarded batchWriter.close(), deterministic storage key sorting, WriteGenesisBlock non-mutation fix, TrieMode validation, named return for trieDB.Close() error propagation, regression tests

Add --binary-trie flag to generate state using the EIP-7864 binary trie
instead of the default Merkle Patricia Trie. This enables testing geth
devnets with binary trie state (--override.verkle=0).

Key changes:
- Add TrieMode type and --binary-trie CLI flag
- Implement writeStateBinaryTrie() using bintrie.BinaryTrie API
- Extract batchWriter type to deduplicate batch writing infrastructure
- Extend WriteGenesisBlock with binaryTrie parameter
- Update integration scripts for binary trie support
- Add "Trie Modes" section to README

The binary trie uses a single global tree (no per-account storage
subtries), so Account.Root is always EmptyRootHash in snapshot entries.
- TestGenerateBinaryTrie: basic generation + cross-mode root comparison
- TestDatabaseContentBinaryTrie: snapshot prefixes + EmptyRootHash invariant
- TestBinaryTrieReproducibility: deterministic roots with same seed
- TestBinaryTrieStateRootValue: golden value test for upstream API changes
- TestWriteGenesisBlockBinaryTrie: EnableVerkleAtGenesis persisted
- TestEndToEndWithGenesisBinaryTrie: full workflow e2e test
- TestGenesisAccountsIntegrationBinaryTrie: genesis alloc in binary trie mode
- Propagate rlp.EncodeToBytes error from encodeStorageValue instead
  of silently discarding it
- Add sync.Once-guarded close() method to batchWriter preventing
  goroutine leaks on error paths
- Log dropped batch worker errors instead of silently swallowing them
- Sort storage keys in writeStateBinaryTrie for deterministic
  snapshot writes
- Use local chainCfg variable in WriteGenesisBlock to avoid mutating
  caller's *Genesis struct
- Reject unknown TrieMode values in New() and writeState() instead
  of silently defaulting to MPT
- Use named return in writeStateBinaryTrie to propagate trieDB.Close()
  errors
- Fix inaccurate "SHA256-based" comment on TrieModeBinary
- Add regression test for WriteGenesisBlock non-mutation
- Add EnableVerkleAtGenesis=false assertion for MPT genesis path
@CPerezz CPerezz closed this Feb 4, 2026
CPerezz pushed a commit that referenced this pull request Feb 13, 2026
feat: add EIP-7864 binary trie mode for state generation
CPerezz added a commit that referenced this pull request Apr 29, 2026
…refresh stale claims

Pure-doc pass following the pr-review-toolkit comment-analyzer
re-review. No behaviour change.

  client/nethermind/doc.go: replaced 'PR#3 stage 2 scaffold (this commit)'
    Status block (which still claimed run_cgo.go returns a wiring-only
    error) with the current shipping description. Pin section now points
    at internal/neth/ for the SHA-09bd5a2d wire-format reference and at
    nethermind/nethermind:1.37.0 for what smoke + oracle actually run.

  client/nethermind/run_cgo.go: file header dropped the 'Phase A (this
    commit) / Phase B (next commit)' framing in favour of describing
    the three live dispatch paths (empty alloc, genesis-alloc only,
    synthetic + optional genesis-alloc) and linking to issue ethereum#22 for
    the storage-bearing genesis + synthetic combination. runImpl doc
    now lists the actual 6-step pipeline and labels ctx/opts as
    reserved-for-future-wiring.

  client/nethermind/genesis_alloc_cgo.go: stateDBSink doc now points
    at 'internal/neth/trie.Builder' instead of the planning-doc 'B4'
    tag. writeGenesisAllocAccounts's  parameter doc reworded
    to drop the misleading 'legacy callers' framing.

  client/nethermind/dbs_cgo.go: '5+ separate Put calls' → '5 separate
    Put calls' (matches genesis_cgo.go's exact-count phrasing).

  docs/superpowers/specs/2026-04-28-nethermind-implementation-notes.md:
    blockNumbers wire-format gotcha #1 now reads as past tense ('an
    earlier revision … was wrong') with the fix-commit reference; the
    pipeline diagram and smoke-evidence section drop the 'Phase A /
    Phase B' labels in favour of empty-alloc / genesis-alloc /
    synthetic which describe what actually shipped.
CPerezz added a commit that referenced this pull request May 3, 2026
…refresh stale claims

Pure-doc pass following the pr-review-toolkit comment-analyzer
re-review. No behaviour change.

  client/nethermind/doc.go: replaced 'PR#3 stage 2 scaffold (this commit)'
    Status block (which still claimed run_cgo.go returns a wiring-only
    error) with the current shipping description. Pin section now points
    at internal/neth/ for the SHA-09bd5a2d wire-format reference and at
    nethermind/nethermind:1.37.0 for what smoke + oracle actually run.

  client/nethermind/run_cgo.go: file header dropped the 'Phase A (this
    commit) / Phase B (next commit)' framing in favour of describing
    the three live dispatch paths (empty alloc, genesis-alloc only,
    synthetic + optional genesis-alloc) and linking to issue ethereum#22 for
    the storage-bearing genesis + synthetic combination. runImpl doc
    now lists the actual 6-step pipeline and labels ctx/opts as
    reserved-for-future-wiring.

  client/nethermind/genesis_alloc_cgo.go: stateDBSink doc now points
    at 'internal/neth/trie.Builder' instead of the planning-doc 'B4'
    tag. writeGenesisAllocAccounts's  parameter doc reworded
    to drop the misleading 'legacy callers' framing.

  client/nethermind/dbs_cgo.go: '5+ separate Put calls' → '5 separate
    Put calls' (matches genesis_cgo.go's exact-count phrasing).

  docs/superpowers/specs/2026-04-28-nethermind-implementation-notes.md:
    blockNumbers wire-format gotcha #1 now reads as past tense ('an
    earlier revision … was wrong') with the fix-commit reference; the
    pipeline diagram and smoke-evidence section drop the 'Phase A /
    Phase B' labels in favour of empty-alloc / genesis-alloc /
    synthetic which describe what actually shipped.
CPerezz added a commit that referenced this pull request May 13, 2026
…invariant for free

Replaces the parallel TestE2ESuiteSpec approach with the design the user
asked for: each existing per-client TestE2ESuite drives its
Config.PreAlloc from the same shared spec YAML. The existing
cross-client-genesis-root aggregator job thus becomes the spec-driven
invariant automatically (no new job, no new aggregator).

Key change: cfg.InjectAddresses[SpamoorSenderAddr] removed from every
client's e2e test. The spamoor sender is now entity #1 in the spec YAML,
funded with 999_999_999 ETH. All other v1 schema variants land in the
same YAML so every flavor is exercised through writer → boot → spamoor
→ RPC re-query → golden-hash on every client.

Why this is robust across clients:
- sizecal.NewFixed(64) (hardcoded in the shared helper) neutralizes
  per-client calibration divergence (geth=64, besu=64, neth=80, reth=60).
  Same YAML → same PreAlloc → same state root.
- materializePreAlloc shim folds PreAlloc into the legacy
  GenesisAccounts/Code/Storage maps before any per-client writer runs,
  so the four writers all see identical input.
- CheckInjections (Phase 4 RPC re-query) walks cfg.GenesisAccounts
  for balance verification (new) and cfg.GenesisCode for bytecode
  verification (existing). Every spec entity is RPC-asserted at runtime.

Files changed:
- examples/spec-ci-baseline.yaml: rewritten as the rich CI fixture
  (~12 entities: spamoor sender + 5 ERC-20 flavors + 2 raw + 2 7702
  EOAs + 3 plain EOAs). Covers explicit/name-derived/position-derived
  addresses, `holders` parameter, `approximate_size_bytes`, explicit
  `nonce` override, 7702 markers, storage bloat, skeleton-only ERC-20.
- examples/spec-ci-min.yaml: deleted (consolidated into baseline).
- internal/e2e_testing/spec_setup.go: new LoadCISpecPreAlloc helper.
  Uses sizecal.NewFixed(64); shared by all 4 client tests.
- internal/e2e_testing/spec_setup_test.go: TestCISpecMatchesSpamoorSender
  pins the YAML's spamoor entity to oracle.SpamoorSenderAddr (catches
  silent drift between YAML and devkeys.go).
- internal/e2e_testing/check_entities.go: CheckInjections now also
  walks cfg.GenesisAccounts for eth_getBalance assertions on every
  non-zero-balance spec entity (spamoor sender + plain EOAs + 7702 EOAs).
- client/{geth,besu,nethermind,reth}/{e2e_test,oracle_test}.go:
  - Drop `InjectAddresses: []common.Address{oracle.SpamoorSenderAddr},`
  - Add `PreAlloc: e2e.LoadCISpecPreAlloc(t, ".../spec-ci-baseline.yaml", "<client>")`
  - Drop now-unused common.Address import.
  - Keep oracle.AddPragueSystemContracts (system contracts are
    infrastructure, not feature-under-test).
  - Keep --accounts/--contracts synthetic-fill (state warmup unchanged).

Reverts the previous geth-only TestE2ESuiteSpec (was the wrong shape).

docs/SPEC.md + CHANGELOG.md: updated to describe the new design.

go vet + go build + go test -short ./... green across the tree.
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.

1 participant