Skip to content

Stage 1: tighten ForkProtocol surface (trivial items)#3

Merged
tcoratger merged 3 commits intotcoratger:multi-fork-architecturefrom
leolara:multi-fork-stage1-tidy
Apr 27, 2026
Merged

Stage 1: tighten ForkProtocol surface (trivial items)#3
tcoratger merged 3 commits intotcoratger:multi-fork-architecturefrom
leolara:multi-fork-stage1-tidy

Conversation

@leolara
Copy link
Copy Markdown

@leolara leolara commented Apr 27, 2026

Stage 1 cleanups for the multi-fork architecture, against PR leanEthereum#638's multi-fork-architecture branch.

Tracking issue: leanEthereum#686 (Stage 1).

Scope

Six trivial, behavior-preserving items from the Stage 1 list. Each landed as its own commit so they're independently revertible:

  1. refactor(forks): stage 1 trivial cleanups for ForkProtocol surface — bundles four small additions:

    • Add GOSSIP_DIGEST: ClassVar[str] to ForkProtocol; route consumers through fork.GOSSIP_DIGEST. Drops the hardcoded GOSSIP_FORK_DIGEST constant and the now-unused Final import.
    • Use DEFAULT_RUNNER directly in __main__.py instead of constructing a redundant SpecRunner(FORK_SEQUENCE).
    • Add previous: ClassVar[type[ForkProtocol] | None] linking each fork to its predecessor (root → None, devnet5 → Devnet4Spec).
    • Tidy devnet5/spec.py: drop the module-level identity aliases, bind container classes directly.
  2. refactor(forks): rename SpecRunner to ForkRegistry — pure rename. The class is a registry, not a dispatcher.

    • File: runner.pyregistry.py
    • Class: SpecRunnerForkRegistry
    • Singleton: DEFAULT_RUNNERDEFAULT_REGISTRY
  3. feat(forks): make upgrade_state abstract on ForkProtocol — replaces the silent identity default with @abstractmethod. Every concrete fork must declare its migration explicitly. Devnet4 (root) and Devnet5 (currently identical) both implement as identity with documented intent. New test asserts ForkProtocol() raises TypeError.

Out of scope (Stage 1 follow-ups)

These remain in Stage 1 but are larger / interdependent and will be separate PRs:

  • Replace Any in ForkProtocol method signatures
  • Add SpecBlockType Protocol; declare structural attributes on SpecStateType / SpecStoreType
  • Drop ClassVar from state_class / block_class / store_class (per Vision B)
  • Delete the generate_pre_state fork fallback (requires fixture plumbing)

Test plan

  • uvx tox -e all-checks clean (ruff, format, ty, codespell, mdformat)
  • uv run pytest --no-cov — 3263 passed
  • One new test (test_cannot_instantiate_directly) locks in the abstract-class contract

leolara and others added 3 commits April 27, 2026 13:43
Bundles four small, behavior-preserving improvements to the multi-fork
skeleton from PR leanEthereum#638. Tests + all-checks green.

* Expose `GOSSIP_DIGEST: ClassVar[str]` on `ForkProtocol`. The gossipsub
  fork digest is fork metadata, not a top-level constant. Devnet4Spec
  binds it to "devnet0" (preserving the existing network agreement);
  Devnet5Spec inherits. Removes the hardcoded `GOSSIP_FORK_DIGEST`
  constant and the now-unused `Final` import from `__main__.py`, and
  routes every consumer through `fork.GOSSIP_DIGEST`.

* Use `DEFAULT_RUNNER` directly in `__main__.py` instead of building a
  second `SpecRunner(FORK_SEQUENCE)`. The forks package already exports
  the singleton; the redundant construction is dropped along with the
  unused `FORK_SEQUENCE` and `SpecRunner` imports.

* Add `previous: ClassVar[type[ForkProtocol] | None]` linking each fork
  to its predecessor. `Devnet4Spec.previous = None`;
  `Devnet5Spec.previous = Devnet4Spec`. Sets up the foundation for a
  future registry that derives ordering from topology and for chained
  state migrations via `upgrade_state`.

* Tidy `devnet5/spec.py`: drop the module-level `_Devnet5State` and
  `_Devnet5Store` identity aliases; bind the container classes
  directly on `Devnet5Spec`. When devnet5 grows its own
  `forks/devnet5/containers/` package, the swap is a file-level diff
  in the imports rather than an alias rebinding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The class is a registry — it stores a sequence of forks and looks them
up by name — not a dispatcher routing spec calls per fork. The old name
implied the latter and set wrong expectations for follow-ups.

* Renames the file `runner.py` -> `registry.py`.
* Renames the class `SpecRunner` -> `ForkRegistry`.
* Renames the singleton `DEFAULT_RUNNER` -> `DEFAULT_REGISTRY`.
* Updates the docstring on `FORK_SEQUENCE` and the error message in
  the empty-list guard.
* Updates the test class name and assertions.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the silent identity default with an abstract method, forcing
every concrete fork to declare its migration explicitly. This prevents
the silent-no-op footgun: a future fork that adds a State field but
forgets to override upgrade_state would otherwise migrate by simply
not migrating.

* `ForkProtocol.upgrade_state` becomes `@abstractmethod` with a
  docstring-only body.
* `Devnet4Spec.upgrade_state` is identity, documented as "root fork,
  no predecessor, no migration".
* `Devnet5Spec.upgrade_state` is identity, documented as "currently
  mirrors devnet4; replace when devnet5's State diverges".
* Adds a test that asserts `ForkProtocol()` raises TypeError, locking
  in the abstract-class contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tcoratger tcoratger merged commit 6d23c9b into tcoratger:multi-fork-architecture Apr 27, 2026
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