Skip to content

M3.13i: a four-node version-seven devnet - #229

Merged
kaikisegfault merged 3 commits into
mainfrom
feat/228-version-seven-devnet
Sep 1, 2026
Merged

M3.13i: a four-node version-seven devnet#229
kaikisegfault merged 3 commits into
mainfrom
feat/228-version-seven-devnet

Conversation

@kaikisegfault

Copy link
Copy Markdown
Owner

Closes #228.

Requirement 13's central claim, asked of version seven: four processes that
were never told each other's answer must hold the same state root at the same
height, through a restart
, having each executed the same blocks independently.

What runs

Alice registers through node 0, Bob registers through node 1, the network is
stopped and started, and Alice pays Bob through node 2. Three transactions
enter through three different replicas
— a node that agreed only with the peer
it heard from would pass a single-submitter run. After every stop all four
databases are opened directly and required to report the same head, which asks
the claim of the store rather than of the engine.

The version reaches the genesis and the bridges from one place

nodeconfig.Devnet.Ensure has taken a ProtocolVersion since M3.13g; the
supervisor passed it ProtocolV1 with a comment saying both halves had to move
together. They move here. devnet.Run takes the version, hands it to Ensure,
and gives every bridge -protocol-version.

That is deliberate rather than convenient: the genesis application state Ensure
writes is what the application requires at InitChain, so a home written for one
ledger version and bridges started for the other is refused there rather than at
the first block — and a version configured twice is a version that can disagree
with itself. protocol-cometbft-devnet start -protocol-version N is parsed
through nodeconfig.ParseProtocolVersion, so a mistyped version is an error
rather than a chain nobody joins, and it defaults to 1 so every existing caller
is unchanged.

The fixture became a live session, and the reason is a fact

An empty version-seven block still moves the state root, because the root
commits to the height. A frozen list of blocks is enough for a single node driven
one transaction at a time; it is not enough for a network that may close a block
the fixture did not ask for. So version_seven_chain.Session holds the ledger
live and the caller advances it to whatever height the network reports before
executing the next transaction against it. build_chain is now three lines over
it and produces the same three blocks it always did.

ADR 0062 is amended in place with that decision rather than getting a second ADR
restating the first with one addition — the same reason ADR 0057 was amended.

The devnet harness is shared rather than copied

tests/integration/cometbft_devnet.py holds what is a property of the network
rather than of a ledger: the port block, the supervisor, health, submitting exact
bytes through one node, auditing every replica's durable head, and stopping. The
protocol version is one field that reaches both the supervisor and the
durable-head audit.

What is deliberately not shared is the model. Each version drives its own and
compares it against what the network reports, which is the whole point of running
four of them.

Verification

Local, against real Ed25519 through the system libsodium:

  • build_chain still passes all four fixture checks after the refactor;
  • a session interleaved with empty blocks produces five distinct roots at five
    contiguous heights
    , confirming the fact the refactor exists for;
  • a transaction that lands at height 5 because the network closed two blocks
    nobody asked for is executed at height 5 by the model;
  • a health report that disagrees with the model is refused.

Also python3 -B tests/tools/test_registration_test.py, tools/verify_metadata.py,
sh -n tools/verify.sh, and git diff --check main HEAD — all clean. No ctest
entry changes: the new run is an integration script in tools/verify.sh, like the
other three.

The Go changes and the run itself are hosted-only. Results recorded below when
the matrix reaches a terminal state.

Out of scope

The uptime schedule, which is what makes requirement 13's word economic
true. Four nodes agreeing on blocks that pay nobody satisfies "four-node" and not
"economic". That is the next slice, and the accepted uptime-measurement-v1
already designs the pipeline and explicitly scopes its founder-reserved part —
the content of a challenge — out of itself.

tools/devnet.sh stays on version one, and the README now says so: the wrapper
decodes a bundled version-one genesis and selects version one's application
binary, so a version-seven wrapper needs a bundled version-seven genesis of its
own.

`nodeconfig.Devnet.Ensure` has taken a `ProtocolVersion` since M3.13g, and
the supervisor passed it `ProtocolV1` with a comment saying a
version-seven devnet needed both halves to move together. This moves them.

`devnet.Run` takes the version, hands it to `Devnet.Ensure`, and gives
every bridge `-protocol-version`. **Both reach it from one place on
purpose.** The genesis application state `Ensure` writes is what the
application requires at `InitChain`, so a home written for one ledger
version and bridges started for the other is refused there rather than at
the first block — and a version configured twice is a version that can
disagree with itself.

`protocol-cometbft-devnet start -protocol-version N` is the surface, and
it is parsed through `nodeconfig.ParseProtocolVersion`, so an operator who
mistypes a version gets an error rather than a chain nobody joins. It
defaults to one, so every existing caller is unchanged.

`tools/devnet.sh` stays on version one and the README says so rather than
leaving it to be discovered: the wrapper decodes a bundled version-one
genesis and selects version one's application binary, and a version-seven
wrapper needs a bundled version-seven genesis of its own.
Requirement 13's central claim, asked of version seven: four processes
that were never told each other's answer must hold the same state root at
the same height, through a restart, having each executed the same blocks
independently.

**Three transactions enter through three different replicas.** A node that
agreed only with the peer it heard from would pass a single-submitter run.
Alice registers through node 0, Bob registers through node 1, the network
is stopped and started, and Alice pays Bob through node 2. After every
stop all four databases are opened directly and required to report the
same head — which asks the claim of the store rather than of the engine.

**The fixture became a live session, and the reason is a fact worth
keeping.** An empty version-seven block still moves the state root,
because the root commits to the height. A frozen list of blocks is enough
for a single node driven one transaction at a time and is not enough for a
network that may close a block the fixture did not ask for, so
`version_seven_chain.Session` holds the ledger live and the caller
advances it to whatever height the network reports before executing the
next transaction against it. `build_chain` is now three lines over it and
produces the same three blocks it always did.

**The devnet harness is shared rather than copied.**
`tests/integration/cometbft_devnet.py` holds what is a property of the
network rather than of a ledger — the port block, the supervisor, health,
submitting exact bytes through one node, auditing every replica's durable
head, and stopping — with the protocol version as one field that reaches
the supervisor and the durable-head audit. What is deliberately not shared
is the model: each version drives its own and compares it against what the
network reports, which is the whole point of running four of them.

Checked locally against real Ed25519 before pushing: `build_chain` still
passes all four fixture checks, a session interleaved with empty blocks
produces five distinct roots at five contiguous heights, a transaction
that lands at height 5 because the network closed two blocks nobody asked
for is executed at height 5 by the model, and a health report that
disagrees with the model is refused.
The fixture ADR recorded a frozen list of blocks, which is what a single
node driven one transaction at a time needs. Four replicas needed more,
and the reason is a fact worth stating rather than a preference: **an
empty version-seven block still moves the state root**, because the root
commits to the height. A network may close a block the fixture did not ask
for, so a frozen list cannot express what the chain did.

The amendment records `Session` — the ledger held live, advanced to
whatever height the network reports before the next transaction is
executed against it — and marks the four-validator devnet as delivered
rather than owed.

ADR 0057 was amended in place for the same reason: a contract belongs in
one document, and a second ADR restating this one's decision with one
addition would be two places for it to be wrong.
@kaikisegfault

Copy link
Copy Markdown
Owner Author

Hosted verification

Run 33506987240 on head ae5e3e1passed, all four presets.

Four independent version-seven replicas agreed on state roots through a full
restart on every one of them
:

CometBFT four-validator version-seven integration: passed
  (4 independent replicas, 2 registrations and 1 confirmed transfer
   through 3 different nodes, full restart, 4 durable C++ audits per stop)

All four integrations pass together, which is what proves the shared-harness
extraction left version one's two alone:

CometBFT single-node integration: passed
CometBFT version-seven integration: passed
CometBFT four-validator integration: passed
CometBFT four-validator version-seven integration: passed
100% tests passed out of 161   (clang-sanitizers; 153 in the debug presets)

No ctest entry changes — the new run is an integration script in
tools/verify.sh, like the other three.

The cost was measured rather than assumed. The four integrations take 36
seconds combined, about ten each, and the whole matrix came in at 8m35s–9m00s per
job against a twenty-minute timeout — faster than the previous run's 10m53s
despite the addition. A slice that adds an integration run should check that
margin rather than discover it.

That the agreement holds under both sanitizers is the part that matters: each
replica's roots came out of an address- and UB-instrumented kernel and store,
reached through four real mempools, four proposers, and a full stop and start.

@kaikisegfault
kaikisegfault merged commit e543681 into main Sep 1, 2026
6 checks passed
@kaikisegfault
kaikisegfault deleted the feat/228-version-seven-devnet branch September 1, 2026 12:30
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.

M3.13i: a four-node version-seven devnet

1 participant