Skip to content

M3.13h: a version-seven chain through a real CometBFT process - #226

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

M3.13h: a version-seven chain through a real CometBFT process#226
kaikisegfault merged 3 commits into
mainfrom
feat/225-version-seven-chain

Conversation

@kaikisegfault

Copy link
Copy Markdown
Owner

Closes #225.

The first time a version-seven transaction is signed for real, broadcast to a
CometBFT node, admitted by a mempool, proposed, finalized, and committed, with the
engine required to report the state root the independent Python model says that
block produces.

The finding this slice is built on

The handoff recorded the next step as "emit the recorded blocks' raw inputs into a
vector file". Checking that against the fixtures found it would produce a fixture
no chain can accept
.

Every recorded version-seven transaction is signed with a stand-in.
simulation/economy_transition_v7/trace.py says so outright — no signature is
computed anywhere; a stand-in is an eight-octet counter padded to 64 octets,
recorded in an oracle that verifies by exact-match lookup — and Signatures in
tests/kernel/economy_v7_execution_fixture.hpp issues byte-identical tokens so the
C++ trace reproduces the model's exact bytes. That is the right decision for a
contract fixture: it makes every message-binding claim testable without the model
implementing cryptography.

But protocol-application-v7 opens its store through open_sqlite_ledger_v7,
whose verifier defaults to protocol::v7::ed25519_verifier(). It would refuse
every recorded input as invalid_signature.

So this is a fixture slice. tests/integration/version_seven_chain.py is the first
version-seven fixture that signs for real — version one's
tests/differential/cases.py shape, using the pinned libsodium. The model needed
no change
: execute_block already takes the signature oracle as an argument and
only ever calls verify(public_key, message, signature), so a libsodium-backed
object is a drop-in for the recorded table.

One transaction per block is a requirement

A state root commits to the whole block, so reproducing a recorded block that holds
four transactions requires all four to land in one block in the recorded order, and
broadcasting through a mempool does not give you that. CreateEmptyBlocks is
already false, and version one's integration gets one transaction per block
precisely because it submits one at a time.

Three blocks: two registrations, because a transfer to an unregistered recipient is
refused, then a confirmed transfer, because it is the first block that moves value
and charges the fee — a node that agreed to two airdrops and then disagreed about a
fee would pass a two-block fixture.

What the run checks, and why each is a different claim

  • /status — the app hash embedded in the latest header, which at height H is
    the state after H - 1;
  • /abci_info — the application's own durable head, which is the root this block
    produced;
  • block_results — the recorded block identifier, published as the
    protocol_block event M3.13g's bridge emits, which proves an identifier ABCI has
    no field for survived the whole path rather than being decoded and dropped;
  • the durable C++ head read straight off the Unix socket after shutdown.

The chain identity and height-zero root are cross-checked between the model and
--genesis-identity, so every comparison is one implementation against another and
never a value against itself. The third block is committed by a process that did
not execute the first two
, so its root comes from a state read back out of SQLite
— the half of requirement 13's "through restart" a single run cannot show.

Verification

Local, and unusually complete for a hosted-only area. The whole fixture and its
contract test were run against real Ed25519 on this machine: the system libsodium
is 1.0.18 against the repository's pinned 1.0.22, and Ed25519 is Ed25519, so a
scratch relaxation of the pin (never committed) exercised the real code path.

  • version-seven-chain-fixture's four checks — shape, distinctness, determinism,
    and real signatures — all pass.
  • Six mutation probes. Making Signer.sign issue stand-ins is refused by the
    model itself at admission with code 3, invalid_signature — the finding
    demonstrated rather than asserted. A stand-in spliced into a raw input, two
    blocks sharing a root, and a version-six receipt version are each caught by the
    check that names them. One probe passed uncaught and was re-aimed: a key that
    drifts only after the fifth derivation never reached the executed path, because a
    rebuild derives exactly five keys; drifting from the first is caught, and the
    re-aimed probe reports how many times it ran.
  • python3 -B tests/tools/test_registration_test.py — passes after the CMake edit.
  • python3 tools/verify_metadata.py and the tests/tools unittest suite — pass.
  • git diff --check main HEAD — clean.

One ctest entry is added, version-seven-chain-fixture, so the suite goes from 152
to 153 entries in the debug presets and from 160 to 161 under clang-sanitizers.

The integration run itself is hosted: it needs the built protocol-application-v7,
three Go binaries, the pinned CometBFT, and the pinned libsodium. Results recorded
below when the run reaches a terminal state.

Owed, and recorded in the ADR rather than implied

  • The four-validator devnet is still version one. Its genesis and the
    -protocol-version its supervisor passes each bridge must move in one slice.
  • The uptime schedule is still nullptr. This chain executes correctly and
    pays nobody; three blocks open no cycle window, so the fixture is honest about
    what it shows.
  • The fixture exercises three of fourteen kinds on purpose. The recorded
    vectors already execute all fourteen; this is not a second coverage claim and
    should not grow into one without a reason.

Every recorded version-seven transaction is signed with a stand-in. The
traces say so outright — a signature is an eight-octet counter padded to
64 octets, recorded in an oracle that verifies by exact-match lookup — and
that is the right decision for a contract fixture: the model implements no
cryptography and every message-binding claim stays testable.

**It also means nothing in this repository could be broadcast to a node.**
`protocol-application-v7` opens its store through `open_sqlite_ledger_v7`,
whose verifier defaults to `protocol::v7::ed25519_verifier()`, so it would
refuse every recorded input as `invalid_signature`. This is the first
version-seven fixture that signs for real.

**The model needed no change to accept it.** `execute_block` takes the
signature oracle as an argument and only ever calls `verify(public_key,
message, signature)`, so `Signer` — real Ed25519 through the pinned
libsodium, keyed by seeds derived from labels — is a drop-in for the
recorded table. It is version one's `tests/differential/cases.py` shape:
build the transactions, run the same octets through the independent Python
model, and learn what each block produces.

**One transaction per block is a requirement rather than a
simplification.** A state root commits to the whole block, so a fixture
whose block holds four transactions can only be reproduced by getting all
four into one block in one order, which broadcasting through a mempool
does not give you.

The chain is three contiguous blocks: two registrations, because a
transfer to an unregistered recipient is refused, and then a confirmed
transfer, because it is the first block that moves value and charges the
fee, so a node that agreed to the first two and not the third is caught.

`version-seven-chain-fixture` is the registered check, and it states what
a node is being asked to reproduce: 110 canonical genesis octets, three
contiguous heights, one input and one 56-octet version-seven receipt each
with a SUCCESS result byte, four distinct state roots and three distinct
block identifiers, byte-identical reproduction on a second build, and —
the point of the whole thing — a trailing signature that is not a
stand-in.

**Six mutation probes, and the first is the argument for the slice.**
Making `Signer.sign` issue stand-ins is refused by the model itself at
admission with code 3, `invalid_signature`, which is the finding this
fixture exists to answer, demonstrated rather than asserted. A stand-in
shape spliced into a raw input, two blocks sharing a root, and a
version-six receipt version are each caught by the check that names them.
**One probe passed uncaught and was re-aimed**: a key that drifts only
after the fifth derivation never reached the rebuild, because a rebuild
derives exactly five keys; drifting from the first is caught, and the
re-aimed probe reports how many times it ran so a later reader can see the
mutation executed.
Nothing in this repository had ever run a version-seven chain. The kernel
executed blocks, the store made them durable, the application reconciled
the two, the transport carried them, the node process served them, and the
adapter spoke ABCI — each against recorded vectors, none against a
consensus engine.

This is the first time a version-seven transaction is signed for real,
broadcast to a CometBFT node, admitted by a mempool, proposed, finalized,
and committed, with the engine required to report the state root the
independent Python model says that block produces.

**The claim is agreement between two implementations rather than
self-consistency.** The fixture derives the chain identity, the
height-zero root, and each block's root from the Python model; the binary
derives the same two figures from the same genesis file through
`--genesis-identity` and they must match; and the running node derives
each block's root by executing the octets. Every comparison is one against
the other.

**Three things are checked at every height and they are different
claims.** `/status` reports the app hash embedded in the latest header,
which at height `H` is the state after `H - 1`; `/abci_info` reports the
application's own durable head, which is the root this block produced; and
`block_results` must publish the recorded block identifier as the
`protocol_block` event the version-seven bridge emits — which is what
proves the identifier ABCI has no field for survived the whole path rather
than being decoded and dropped.

**The third block is committed by a process that did not execute the first
two.** Its root therefore depends on a state read back out of SQLite
rather than one held in memory, which is the half of requirement 13's
"through restart" a single run cannot show. The home is initialized twice
before the first run, because a repeated initialization must exact-validate
the version-seven genesis rather than silently rewrite it.

`start_stack`, `stop_stack`, and `application_info` take a protocol
version rather than being copied, and `inspect_identity` and
`initialize_home` move out of version one's test into the shared harness
so both versions drive the same three processes through one code path.
`application_info` previously required the application to report version
one; it now requires the version asked for, which catches a stack wired to
the wrong binary.
ADR 0062 records why the slice that was going to emit octets built a
fixture instead.

**Every recorded version-seven transaction is signed with a stand-in**, and
the ADR keeps the reason that is right — exact-match lookup makes every
message-binding claim in the contract testable without the model
implementing cryptography it would then have to be trusted about — beside
the reason it is useless for a run: `protocol-application-v7` opens its
store with `ed25519_verifier()` and would refuse every recorded input as
`invalid_signature`.

The rest is recorded with the reasoning a later session would otherwise
have to rebuild: why two fixtures is not duplication, because they answer
different questions; why one transaction per block is a requirement rather
than a simplification; why three blocks and why those three; why every
comparison in the run is one implementation against another rather than a
value against itself; and why the restart is the third block rather than a
separate case.

Three things are owed rather than implied: the four-validator devnet is
still version one, the uptime schedule is still `nullptr` so this chain
executes correctly and pays nobody, and the fixture exercises three of
fourteen kinds on purpose and should not grow into a second coverage claim
without a reason.

Three alternatives are recorded with why they lose, including the one this
slice started as — emitting the recorded raw inputs — and giving the
recorded traces real signatures, which would move every recorded root in
both accepted version-seven vector files to buy a property only the
integration run needs.
@kaikisegfault

Copy link
Copy Markdown
Owner Author

Hosted verification

Run 33504060503 on head 7b7ba55passed, all four presets.

A version-seven chain ran under a real CometBFT process on every one of them:

CometBFT version-seven integration: passed
  (2 registrations, 1 confirmed transfer, restart at height 2, durable height 3)
Job ctest Result
Classify and verify change scope success (full)
Linux x86_64 / gcc-debug 100% of 153 success
Linux x86_64 / gcc-sanitizers 100% of 153 success
Linux x86_64 / clang-debug 100% of 153 success
Linux x86_64 / clang-sanitizers 100% of 161 success

The new entry is registered and runs everywhere:

108/153 Test #101: version-seven-chain-fixture ...............   Passed    0.17 sec

152 → 153 in the debug presets and 160 → 161 under clang-sanitizers, exactly as
predicted.

The two existing integrations still pass on every preset, which is what proves
the harness parameterization left version one's path alone — start_stack,
stop_stack, and application_info gained a protocol version, and
inspect_identity and initialize_home moved out of the single-node test into
the shared harness:

CometBFT single-node integration: passed (2 signed transfers, restart at height 1, durable height 2)
CometBFT four-validator integration: passed (4 independent replicas, 2 signed transfers, full restart, 4 durable C++ audits per stop)

The version-seven run costs about ten seconds per preset, the same as the other
two, against a twenty-minute job timeout.

That the chain committed under both sanitizers matters more than the pass
itself.
The blocks were executed by an address- and UB-instrumented
protocol-application-v7 reached through a real mempool, a real proposer, and a
real restart handshake, so the roots the engine reported came out of an
instrumented kernel and store rather than a fixture harness.

@kaikisegfault
kaikisegfault merged commit 9c733e4 into main Sep 1, 2026
6 checks passed
@kaikisegfault
kaikisegfault deleted the feat/225-version-seven-chain branch September 1, 2026 11:57
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.13h: a version-seven chain through a real CometBFT process

1 participant