M3.13i: a four-node version-seven devnet - #229
Conversation
`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.
Hosted verificationRun 33506987240 on head Four independent version-seven replicas agreed on state roots through a full All four integrations pass together, which is what proves the shared-harness No ctest entry changes — the new run is an integration script in The cost was measured rather than assumed. The four integrations take 36 That the agreement holds under both sanitizers is the part that matters: each |
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.Ensurehas taken aProtocolVersionsince M3.13g; thesupervisor passed it
ProtocolV1with a comment saying both halves had to movetogether. They move here.
devnet.Runtakes the version, hands it toEnsure,and gives every bridge
-protocol-version.That is deliberate rather than convenient: the genesis application state
Ensurewrites is what the application requires at
InitChain, so a home written for oneledger 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 Nis parsedthrough
nodeconfig.ParseProtocolVersion, so a mistyped version is an errorrather 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.Sessionholds the ledgerlive and the caller advances it to whatever height the network reports before
executing the next transaction against it.
build_chainis now three lines overit 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.pyholds what is a property of the networkrather 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_chainstill passes all four fixture checks after the refactor;contiguous heights, confirming the fact the refactor exists for;
nobody asked for is executed at height 5 by the model;
Also
python3 -B tests/tools/test_registration_test.py,tools/verify_metadata.py,sh -n tools/verify.sh, andgit diff --check main HEAD— all clean. No ctestentry changes: the new run is an integration script in
tools/verify.sh, like theother 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-v1already designs the pipeline and explicitly scopes its founder-reserved part —
the content of a challenge — out of itself.
tools/devnet.shstays on version one, and the README now says so: the wrapperdecodes 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.