Execution Kernel, Stage 1: the MachineInterface seam, contract plus a differential replay harness proven on real blocks

The kernel's first stage is not new infrastructure, it is a written contract
for the state-transition seam (pre_state, block) -> (post_state, receipts)
through StateView only, plus a harness that proves two independent clients
agree on the state root of real chain-2800 blocks, in both directions per the
D-150 rule (what was lost, not only what was added).

Run and measured 2026-08-15, from the two public endpoints (Besu at
rpc.aere.network, Nethermind at client2.aere.network): 24 blocks, 168 fields,
zero divergences. All four negative-control plants (value differs, block only
on one side, a lost field, a corrupted side) make the gate go red exactly
where planted. Installs nothing, three read-only RPC methods, runnable by
anyone. This makes "a second execution machine tomorrow" a mechanical slot,
not a promise.
This commit is contained in:
Aere Network 2026-08-15 22:13:28 +03:00
parent 4ce1928d9e
commit cea943ff57
2 changed files with 791 additions and 0 deletions

View File

@ -0,0 +1,294 @@
# MachineInterface (MI) contract, Execution Kernel treapta 1
Chain 2800 (AERE). Written 2026-08-15, the day the post-quantum consensus milestone went
live at block 14,050,000. This is the seam for interface **I3** in
`aerenew/strategie/EXECUTION-KERNEL-2026-08-15.md`.
This document is a **contract**, not an implementation. Treapta 1 adds no new infrastructure.
Production Besu already ships `BlockProcessor` and `MainnetBlockProcessor` under
`ethereum/core/.../mainnet/`. Treapta 1 is (a) the written contract of what that seam guarantees
and forbids, and (b) a differential replay harness that proves two independent execution paths
derive the **same** post-state from the same input on **real live blocks**. The harness lives
next to this file: `mi-replay-diferential.mjs`.
The rule of the house applies to every sentence here: **a claim asserts no more than the
measurement that backs it.** Anything below that the harness does not measure is marked
NOT MEASURED in the harness output, never asserted as proven.
---
## 1. What MI is, in one line
MI(pre_state, block) -> (post_state, receipts)
MI is the pure function that a block-processing engine computes. Given the world state before
a block (`pre_state`) and the block itself (`block`), it produces the world state after the
block executed (`post_state`) and the execution output (`receipts`). Nothing else is an input.
Nothing else is an output.
The value of treapta 1 is not a new virtual machine. It is that this function is **named,
isolated, and re-derivable from a stranger's machine**, so that "a second execution engine
tomorrow" becomes a mechanical slot instead of a promise.
---
## 2. The interface
```
interface MachineInterface {
// Deterministic, side-effect-free block execution.
// Reads state ONLY through `view`. Writes nothing outside the returned post_state.
Result execute(StateView view, Block block);
}
// The only channel through which MI may read prior state.
interface StateView {
Account account(Address a); // nonce, balance, codeHash, storageRoot
Bytes code(Address a);
Word storage(Address a, Word slot);
Hash blockHash(long number); // only within the EIP-2935 / BLOCKHASH window
Header header(); // the header of the block being executed
// NO clock, NO filesystem, NO network, NO RNG, NO ambient config read here.
}
// The output of execute(). Fully determined by (pre_state, block).
struct Result {
Hash postStateRoot; // world-state trie root AFTER the block
List<Receipt> receipts; // one per transaction, in order
Hash receiptsRoot; // trie root of receipts
long gasUsed;
Bloom logsBloom; // OR of every receipt bloom
}
```
The three roots and `gasUsed`/`logsBloom` are exactly the header fields the live chain already
commits per block, so an MI result is checkable against the canonical header with no privileged
access. That is what the harness does.
---
## 3. What MI guarantees
1. **Determinism.** For a fixed `(pre_state, block)`, `execute` returns one and only one
`Result`, on every machine, in every process, at every wall-clock time. Two runs that
disagree are a contract violation, not a nondeterministic outcome to be retried. See section 5
for the exact definition of "same" used here.
2. **Purity of reads.** Every byte of prior state that influences the result is read through
`StateView`. If a value is not reachable through `StateView`, it may not change the result.
This is the load-bearing rule and it has its own section (section 4).
3. **Isolation of writes.** `execute` mutates nothing observable except by returning
`post_state`. No global, no cache that a later block can read, no file, no counter. A cache
is permitted only if it is a pure function of `StateView` inputs and is invisible to the
result (an index that can be dropped and rebuilt with byte-identical output; see the D-153 /
log-filter-cache lesson in `CLAUDE.md`, "a computed index is deleted without losing history").
4. **Input closure.** The admissible inputs are exactly those in section 6. Anything outside
that set (a timestamp read from the OS clock, an environment variable, a random seed, the
identity of which validator won the round) is forbidden as an input. The base-fee-floor fork
is a function of `block.number` and system properties fixed per node, therefore part of the
ruleset, not an ambient input; see section 6.
5. **Finality independence.** MI is an execution seam, not a consensus rule. It never decides
finality. Finality on 2800 is QBFT + Falcon (interfaces I1/I2). An MI implementation that is
slow, absent, or divergent must never be able to touch whether a block is final. This mirrors
the golden rule of I4: proofs never gate finality; MI never gates it either.
---
## 4. The purity rule of StateView (the one that matters)
**Everything that can change the result is read through `StateView`, and nothing else is.**
Why it is the rule the whole treapta stands on: the failure mode of a second execution path
(Block-STM in treapta 4, or a second client today) is not a loud crash. It is a **silent state
divergence**, a `post_state` that differs by one slot with no error raised. The only way that
divergence stays impossible is if the complete set of inputs is closed and named, so that two
implementations reading the same `StateView` over the same `block` cannot legally reach two
answers.
Concretely, MI is in violation if any of the following influence the result:
- the wall-clock time, an OS timer, or process uptime;
- an environment variable, a config file, or any node-local setting **other than** the fixed
ruleset parameters of section 6;
- a random number, a map iteration order, a thread-scheduling order, or floating point;
- the contents of any cache that is not a pure function of `StateView` inputs;
- state at a height outside the window `StateView` exposes (the `blockHash` / EIP-2935 window);
- which node, which peer, or which validator produced or relayed the block.
**Negative control, required.** The purity rule is not proven by reading code and agreeing it
looks pure. It is proven by **planting an impurity and watching the gate go red.** A conforming
treapta-1 test suite MUST include at least one planted violation of the list above (for example:
make `execute` branch on `System.currentTimeMillis()`), and MUST show the differential gate
turns that plant into a divergence. A gate that has never rejected an impure implementation is
applause, not a gate. The runnable analogue of this, at the header-root level, is the negative
control mode of `mi-replay-diferential.mjs` (section 8).
---
## 5. What determinism means here
"Same result" is defined by **byte-level equality of the committed commitments**, compared in
both directions (section 7). Two MI results for the same input are equal when, and only when:
| field | equality |
|---|---|
| `postStateRoot` | identical 32-byte hash |
| `receiptsRoot` | identical 32-byte hash |
| `transactionsRoot` (input identity) | identical 32-byte hash |
| `logsBloom` | identical 256 bytes |
| `gasUsed` | identical integer value (compared by value, so `0x1f4` == `0x01f4`) |
| block `hash`, `parentHash` | identical 32-byte hash |
Determinism is a claim about these commitments, not about internal representation. Two clients
may lay out their trie nodes differently in memory, name their threads differently, and store
`extraData` differently for the same block (QBFT lets each node assemble the final header
locally, and the block hash does not cover the seals; see `CLAUDE.md`). None of that is an MI
input or output. Only the commitments above are.
**Determinism is measured across implementations, not asserted from one.** A single client
agreeing with itself proves nothing about determinism. The measurement that means something is
two independent engines, built by different teams in different languages, deriving the same
commitments from the same input. Today that is Besu (serial, live producer) versus Nethermind
(independent second client, live at `client2.aere.network`). Tomorrow it is Block-STM through
the same seam (treapta 4), measured by the same harness.
**Honesty boundary, carried from `cross-client-determinism/`.** A matching header root served
over RPC proves, on its own, only that both endpoints **serve the same bytes** for a block that
travels over devp2p. The load-bearing fact underneath is that Nethermind **validates on
processing and rejects on disagreement**: this exact client has rejected live blocks it computed
differently (`WithdrawalsEmpty` at 2,075,341, `HeaderGasUsedMismatch` at 2,149,971). A block
that sits in the follower's canonical chain below its processed head is therefore a block whose
`post_state` the follower's own engine reproduced. The harness states this boundary on every run
and never claims more than it.
---
## 6. Admissible inputs
`execute(StateView view, Block block)` admits exactly:
1. **`block`**: header + ordered transaction list. From the header, MI may use only the fields
that are consensus inputs to execution: `number`, `parentHash`, `timestamp` (as the block's
own declared timestamp, an input carried IN the block, never read from the OS clock),
`gasLimit`, `baseFeePerGas`, `coinbase`/`miner`, `prevRandao`/`mixHash`, `withdrawals`,
and the blob fields where active. `extraData`/seals are consensus material, not execution
input, and MI must not branch execution on them.
2. **`view`**: the pre-state, reachable only through `StateView` as defined in section 4.
3. **The fixed ruleset**, which is a pure function of `block.number` and the per-node system
properties that define the fork schedule, and is identical on every honest node:
- the EVM fork rules by height/timestamp (Cancun/Prague/Osaka milestones);
- `aere.basefee.floor.forkBlock = 10141734`, floor 1 Gwei, delivered via `BESU_OPTS`. This is
the live AERE fork. It is a **rule keyed on block number**, not an ambient input: every node
computes the same base-fee floor for the same height. An engine that lacks it computes a
different fee, therefore a different state root, and is a different MI, not the same one with
a different environment. This is exactly the D-150 lesson: a binary missing the floor fork
froze a real node at a state-root mismatch while every shape check stayed green.
Everything not in this list is inadmissible as an input. In particular: no OS clock, no RNG, no
environment read at execution time, no network, no cross-block mutable cache.
---
## 7. Both directions (the D-150 rule)
The most expensive lesson on this project (D-150, 2026-08-07): **we verified what we added,
never what we lost.** Every gate asked "does the new thing contain what we put in?" and none
asked "did it keep what was already there?" A binary missing three production subsystems passed
hundreds of green tests.
Applied to MI differential replay, "both directions" is not optional and not cosmetic:
- **Forward (Besu -> Nethermind):** for every field Besu commits, Nethermind must commit the
same value. Catches a field Nethermind got **wrong**.
- **Reverse (Nethermind -> Besu):** for every field Nethermind commits, Besu must commit the
same value. Catches a field Nethermind has that Besu **lacks**, and a block Nethermind holds
that Besu does not.
The comparison therefore takes the **union of the field keys on both sides** and the **union of
the block numbers on both sides**, and flags three distinct kinds of divergence:
1. present on one side, **absent** on the other (a lost field, or a block only one side holds);
2. present on both, **different** value;
3. and it does this symmetrically, so "only on them" is caught with the same weight as
"only on us". "Only on them" is the direction that kills.
A one-directional loop (iterate Besu's blocks, ask Nethermind for Besu's fields) is exactly the
D-150 mistake in miniature: it can never see a field or a block that exists only on the other
side. The harness does not do that.
---
## 8. The negative control (why a green run means anything)
A harness that has never gone red is decoration. Before any green run counts, the harness must
be shown to turn a planted divergence into a red verdict, using the **same comparison code** the
real run uses, not a copy of it.
`mi-replay-diferential.mjs` has a built-in negative-control mode driven by the environment
variable `AERE_MI_INJECT`. When it is set, the harness fetches **real** headers from both live
clients, plants exactly one synthetic divergence into the fetched data, runs the **real**
`compareInterval` over it, and then **inverts its success condition**: the run passes only if the
gate caught the exact planted divergence and turned red, and fails if the plant slipped through
green. This is the runnable form of "plant a fault, require red".
The plant can target either direction, so both arms of section 7 are exercised:
| `AERE_MI_INJECT` value | what it plants | which direction it proves |
|---|---|---|
| `stateRoot` (or `1`) | flips one nibble of one block's `stateRoot` on the Nethermind side | forward, value differs |
| `receiptsRoot@5` | corrupts `receiptsRoot` at interval index 5 | forward, value differs |
| `gasUsed` | bumps `gasUsed` by 1 on Nethermind | forward, value differs, numeric |
| `drop` | makes Nethermind "not have" one block | reverse, block only on Besu |
| `missing:stateRoot` | deletes the `stateRoot` field from one Nethermind header | reverse, a **lost** field |
| `stateRoot:besu` | plants the corruption on the Besu side instead | reverse, value differs |
The default `execute`-level purity control (planting an impure read and watching the gate go red)
is the Java-side obligation of section 4; the header-root control above is its runnable analogue
that anyone can execute against the live chain with no build tree.
---
## 9. The gate
Treapta 1 is CLOSED only when all of the following hold, each measured, none asserted:
1. For N real live blocks, every MI field in section 5 is **identical in both directions**
between the Besu path and the second path. Coverage (N and the exact range) is printed, and
any block that could not be compared is counted and attributed, never silently dropped.
2. The per-client pre-state linkage holds: within each client, `parentHash(n) == hash(n-1)`
across the interval, so the `(pre_state, block) -> (post_state)` tuple is pinned as a chain
and not just a set of isolated roots.
3. The negative control (section 8) turns a planted divergence red, hitting the exact field and
block it planted, in each direction it is asked to.
4. The Java-side purity control (section 4) plants an impure read and the differential gate turns
it red.
Items 1 through 3 are runnable today by anyone, against public endpoints, with no dependencies,
via `mi-replay-diferential.mjs`. Item 4 is the obligation on the treapta-1 test suite inside the
fork build.
---
## 10. What this contract does NOT claim
Printed as NOT MEASURED by the harness, never as a pass:
- **That consensus is post-quantum.** It is not. Consensus finality is classical secp256k1 ECDSA
QBFT plus a Falcon certificate. MI is an execution seam and says nothing about consensus.
- **That equal header roots prove independent execution by themselves.** See the honesty boundary
in section 5. The load-bearing fact is Nethermind's reject-on-disagreement behaviour, not the
served bytes.
- **That the second path re-executed history it never reached.** Only blocks both clients hold
and serve in the compared interval are covered; the rest is counted as uncovered.
- **Anything about performance.** MI determinism is not throughput. This contract makes no TPS
claim and the harness runs no benchmark.
- **Who operates the validator or client keys.** Address independence is not operator
independence, and nothing measurable from a public endpoint settles it.
- **State older than roughly the last 512 blocks** on the public endpoints. The harness reads
headers and bodies pinned to explicit block numbers, never a deep state query, precisely to
stay inside what these endpoints can answer honestly.

View File

@ -0,0 +1,497 @@
#!/usr/bin/env node
// mi-replay-diferential.mjs
//
// MachineInterface (MI) differential replay harness, Execution Kernel treapta 1.
// Chain 2800 (AERE). See MACHINE-INTERFACE-CONTRACT.md next to this file.
//
// WHAT IT PROVES
// MI is the pure function MI(pre_state, block) -> (post_state, receipts).
// Two independent execution engines each committed a post-state and receipts for the same
// canonical blocks: Besu (the live serial producer) and Nethermind (the independent second
// client). For a range of REAL live blocks this harness fetches, from BOTH clients, the header
// commitments that fully determine an MI result:
//
// stateRoot post-state world trie root (the headline MI output)
// receiptsRoot trie root of receipts (MI output)
// logsBloom OR of all receipt blooms (MI output)
// gasUsed total gas (MI output, compared by value)
// transactionsRoot the block's tx list (MI input identity)
// hash, parentHash block identity + pre-state linkage
//
// If both engines committed the same values for the same input, two independent MI
// implementations derived the same (post_state, receipts). That is the MI determinism claim.
//
// BOTH DIRECTIONS (the D-150 rule)
// The comparison is SYMMETRIC. It takes the union of block numbers on both sides and the union
// of field keys on both sides, and flags three kinds of divergence:
// - present on one side, ABSENT on the other (a lost field, or a block only one side holds)
// - present on both, DIFFERENT value
// "Only on them" is caught with the same weight as "only on us". D-150: we verify what we lost,
// not only what we added.
//
// NEGATIVE CONTROL (why a green run means anything)
// Set the env var AERE_MI_INJECT to plant exactly one synthetic divergence into the REAL
// fetched data, then the harness runs the SAME comparison code and REQUIRES it to go red on the
// exact planted field/block. In inject mode the success condition is inverted: exit 0 only if
// the plant was caught. See the table below and section 8 of the contract.
//
// READ-ONLY BY CONSTRUCTION
// The only JSON-RPC methods it will send are eth_chainId, eth_blockNumber, eth_getBlockByNumber.
// Any other method is refused before it leaves this process. Nothing is signed, nothing is
// broadcast, no validator is touched, no state is written.
//
// NO DEPENDENCIES
// Node 18+ and its built-in http/https only. No npm, no ethers. Runnable by anyone.
//
// USAGE
// node mi-replay-diferential.mjs
// AERE_COUNT=32 node mi-replay-diferential.mjs
// AERE_START=14000000 AERE_END=14000050 node mi-replay-diferential.mjs
// AERE_BESU_RPC=https://rpc.aere.network AERE_NETH_RPC=https://client2.aere.network \
// node mi-replay-diferential.mjs
//
// Negative control (plant a divergence, require red):
// AERE_MI_INJECT=stateRoot node mi-replay-diferential.mjs # forward, value differs
// AERE_MI_INJECT=drop node mi-replay-diferential.mjs # reverse, block only on Besu
// AERE_MI_INJECT=missing:stateRoot node mi-replay-diferential.mjs # reverse, a LOST field
// AERE_MI_INJECT=stateRoot:besu node mi-replay-diferential.mjs # reverse, corrupt Besu side
//
// ENV VARS
// AERE_BESU_RPC default https://rpc.aere.network (also reads BESU_RPC)
// AERE_NETH_RPC default https://client2.aere.network (also reads NETHERMIND_RPC)
// AERE_START, AERE_END explicit inclusive range (decimal). AERE_END may be "head".
// AERE_COUNT blocks ending near head when START/END unset (default 16)
// AERE_SAFETY blocks to stay behind the min head, avoids racing the tip (default 4)
// AERE_CONCURRENCY parallel header fetches (default 6)
// AERE_MI_INJECT negative-control plant (see table above). Unset = normal differential run.
// AERE_OUT results JSON path (default results/mi-replay-<range>-<ts>.json)
//
// EXIT CODES
// normal run: 0 AGREE identical in both directions, coverage > 0
// 1 MISMATCH a field differed or a block was one-sided
// 2 NOT MEASURED no coverage / unreachable endpoint / chain-id mismatch
// inject run: 0 the gate caught the planted divergence (control PASSED)
// 1 the plant slipped through green (control FAILED) OR fatal
import http from 'node:http';
import https from 'node:https';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = path.dirname(fileURLToPath(import.meta.url));
const BESU_RPC = process.env.AERE_BESU_RPC || process.env.BESU_RPC || 'https://rpc.aere.network';
const NETH_RPC = process.env.AERE_NETH_RPC || process.env.NETHERMIND_RPC || 'https://client2.aere.network';
const CONCURRENCY = Math.max(1, parseInt(process.env.AERE_CONCURRENCY || '6', 10));
const DEFAULT_COUNT = 16;
const SAFETY = Math.max(0, parseInt(process.env.AERE_SAFETY || '4', 10));
const INJECT = process.env.AERE_MI_INJECT || '';
// The commitments that fully determine / identify an MI result. See contract section 5.
// kind 'hash' compared as lowercased hex; kind 'num' compared by integer value.
const MI_FIELDS = [
{ name: 'hash', kind: 'hash', role: 'block identity' },
{ name: 'parentHash', kind: 'hash', role: 'pre-state linkage' },
{ name: 'stateRoot', kind: 'hash', role: 'MI output: post-state' },
{ name: 'transactionsRoot', kind: 'hash', role: 'MI input: tx list' },
{ name: 'receiptsRoot', kind: 'hash', role: 'MI output: receipts' },
{ name: 'gasUsed', kind: 'num', role: 'MI output: gas' },
{ name: 'logsBloom', kind: 'hash', role: 'MI output: logs bloom' },
];
const FIELD_BY_NAME = new Map(MI_FIELDS.map((f) => [f.name, f]));
// ---- read-only JSON-RPC over the Node built-ins, no dependencies -------------------------------
const ALLOWED_METHODS = new Set(['eth_chainId', 'eth_blockNumber', 'eth_getBlockByNumber']);
function rpc(endpoint, method, params, tries = 4) {
if (!ALLOWED_METHODS.has(method)) {
return Promise.reject(new Error(`method ${method} is not in the read-only allowlist`));
}
const u = new URL(endpoint);
const lib = u.protocol === 'http:' ? http : https;
const port = u.port ? parseInt(u.port, 10) : (u.protocol === 'http:' ? 80 : 443);
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params });
const attempt = () => new Promise((resolve, reject) => {
const req = lib.request({
hostname: u.hostname,
port,
path: u.pathname === '/' && u.search ? u.pathname + u.search : (u.pathname || '/'),
method: 'POST',
headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) },
timeout: 15000,
}, (res) => {
let d = '';
res.on('data', (c) => { d += c; });
res.on('end', () => {
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode} from ${u.hostname}`));
let j;
try { j = JSON.parse(d); } catch (_) { return reject(new Error(`non-JSON reply from ${u.hostname}: ${d.slice(0, 100)}`)); }
if (j.error) return reject(new Error(`RPC error ${JSON.stringify(j.error)}`));
resolve(j.result);
});
});
req.on('timeout', () => req.destroy(new Error('timeout')));
req.on('error', reject);
req.write(body);
req.end();
});
return (async () => {
let lastErr;
for (let t = 0; t < tries; t++) {
try { return await attempt(); } catch (e) { lastErr = e; await sleep(250 * (t + 1)); }
}
throw lastErr;
})();
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const hx = (n) => '0x' + BigInt(n).toString(16);
async function chainIdOf(endpoint) { return parseInt(await rpc(endpoint, 'eth_chainId', []), 16); }
async function headOf(endpoint) { return parseInt(await rpc(endpoint, 'eth_blockNumber', []), 16); }
// Fetch a header, no tx bodies. Returns a plain object with the MI fields, or null if the block
// is absent (the endpoint does not hold it). Pinned to an explicit number, never "latest".
async function getHeader(endpoint, number) {
const b = await rpc(endpoint, 'eth_getBlockByNumber', [hx(number), false]);
if (!b) return null;
const out = { number: parseInt(b.number, 16) };
for (const f of MI_FIELDS) if (b[f.name] !== undefined) out[f.name] = b[f.name];
return out;
}
async function mapPool(items, limit, worker) {
const results = new Array(items.length);
let next = 0;
async function run() {
for (;;) {
const i = next++;
if (i >= items.length) return;
results[i] = await worker(items[i], i);
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, run));
return results;
}
// ---- the core comparison: symmetric, both directions --------------------------------------------
function eqField(f, a, b) {
if (a == null || b == null) return a == null && b == null;
if (f.kind === 'num') { try { return BigInt(a) === BigInt(b); } catch (_) { return false; } }
return String(a).toLowerCase() === String(b).toLowerCase();
}
// besuByNum / nethByNum: Map<number, header|null>. numbers: the interval.
// Returns { divergences:[...], directions:{...}, fieldsCompared }.
// Each divergence has { block, field|null, kind, besu, nethermind }.
// kind 'block-only-besu' block held by Besu, absent on Nethermind (reverse direction)
// kind 'block-only-nethermind'block held by Nethermind, absent on Besu (forward would miss it)
// kind 'field-only-besu' field present on Besu, absent on Nethermind (a LOST field)
// kind 'field-only-nethermind'field present on Nethermind, absent on Besu
// kind 'field-differs' present on both, different value
function compareInterval(besuByNum, nethByNum, numbers) {
const divergences = [];
const directions = { forwardDiff: 0, reverseOnlyBesu: 0, reverseOnlyNeth: 0, fieldsCompared: 0 };
for (const n of numbers) {
const a = besuByNum.get(n);
const b = nethByNum.get(n);
if (a == null && b == null) continue; // neither holds it; out of coverage
if (a != null && b == null) {
divergences.push({ block: n, field: null, kind: 'block-only-besu', besu: 'present', nethermind: 'absent' });
directions.reverseOnlyBesu++;
continue;
}
if (a == null && b != null) {
divergences.push({ block: n, field: null, kind: 'block-only-nethermind', besu: 'absent', nethermind: 'present' });
directions.reverseOnlyNeth++;
continue;
}
// both hold the block: compare the UNION of field keys, so a one-sided field is caught.
const keys = new Set([...Object.keys(a), ...Object.keys(b)].filter((k) => k !== 'number' && FIELD_BY_NAME.has(k)));
for (const k of keys) {
const f = FIELD_BY_NAME.get(k);
const av = a[k];
const bv = b[k];
const aHas = av !== undefined && av !== null;
const bHas = bv !== undefined && bv !== null;
if (aHas && !bHas) { divergences.push({ block: n, field: k, kind: 'field-only-besu', besu: av, nethermind: null }); directions.forwardDiff++; continue; }
if (!aHas && bHas) { divergences.push({ block: n, field: k, kind: 'field-only-nethermind', besu: null, nethermind: bv }); directions.forwardDiff++; continue; }
if (!aHas && !bHas) continue;
directions.fieldsCompared++;
if (!eqField(f, av, bv)) divergences.push({ block: n, field: k, kind: 'field-differs', besu: av, nethermind: bv });
}
}
return { divergences, directions };
}
// Per-client pre-state linkage: parentHash(n) == hash(n-1) across the interval. Pins the
// (pre_state, block) -> (post_state) tuple as a chain, not just isolated roots. See contract sec 9.
function preStateLinkage(byNum, numbers) {
const issues = [];
let checked = 0;
for (const n of numbers) {
const cur = byNum.get(n);
const prev = byNum.get(n - 1);
if (!cur || !prev) continue;
checked++;
if (String(cur.parentHash).toLowerCase() !== String(prev.hash).toLowerCase()) {
issues.push(`block ${n} parentHash ${cur.parentHash} != hash(${n - 1}) ${prev.hash}`);
}
}
return { checked, issues };
}
// ---- negative control: plant exactly one synthetic divergence -----------------------------------
// spec grammar: field[@index][:side] | drop[@index][:side] | missing:field[@index][:side]
// bare "1"/"on"/"true" => stateRoot at the middle index on the nethermind side.
function parseInject(spec, numbers) {
let s = spec.trim();
if (s === '1' || s === 'on' || s === 'true') s = 'stateRoot';
let side = 'nethermind';
const sideM = s.match(/:(besu|nethermind|neth)$/i);
if (sideM) { side = sideM[1].toLowerCase() === 'besu' ? 'besu' : 'nethermind'; s = s.slice(0, sideM.index); }
let index = Math.floor(numbers.length / 2);
const idxM = s.match(/@(\d+)$/);
if (idxM) { index = Math.min(numbers.length - 1, parseInt(idxM[1], 10)); s = s.slice(0, idxM.index); }
let op = 'corrupt';
let field = s;
if (s === 'drop') { op = 'drop'; field = null; }
else if (s.startsWith('missing:')) { op = 'missing'; field = s.slice('missing:'.length); }
if (op === 'corrupt' && !FIELD_BY_NAME.has(field)) {
throw new Error(`AERE_MI_INJECT: unknown field "${field}". Use one of: ${MI_FIELDS.map((f) => f.name).join(', ')}, or drop, or missing:<field>`);
}
const block = numbers[index];
return { op, field, side, index, block };
}
function flipHashNibble(v) {
// XOR the last hex nibble with 1, so a valid-shaped but different 32-byte hash is produced.
const last = v[v.length - 1];
const flipped = (parseInt(last, 16) ^ 1).toString(16);
return v.slice(0, -1) + flipped;
}
// Mutate `byNum` in place (a Map). Returns the expected divergence the gate MUST report.
function applyInject(byNumBesu, byNumNeth, plan) {
const target = plan.side === 'besu' ? byNumBesu : byNumNeth;
const h = target.get(plan.block);
if (!h) throw new Error(`inject target block ${plan.block} not present on ${plan.side}`);
if (plan.op === 'drop') {
target.set(plan.block, null);
return { block: plan.block, field: null, expectKind: plan.side === 'besu' ? 'block-only-nethermind' : 'block-only-besu' };
}
if (plan.op === 'missing') {
delete h[plan.field];
// deleting on nethermind => field present on besu only => field-only-besu, and vice versa.
return { block: plan.block, field: plan.field, expectKind: plan.side === 'besu' ? 'field-only-nethermind' : 'field-only-besu' };
}
// corrupt: change the value but keep the shape
const f = FIELD_BY_NAME.get(plan.field);
if (f.kind === 'num') h[plan.field] = hx(BigInt(h[plan.field]) + 1n);
else h[plan.field] = flipHashNibble(String(h[plan.field]));
return { block: plan.block, field: plan.field, expectKind: 'field-differs' };
}
// ---- range resolution ---------------------------------------------------------------------------
function resolveRange(head) {
let start;
let end;
if (process.env.AERE_START || process.env.AERE_END) {
end = process.env.AERE_END && process.env.AERE_END !== 'head' ? parseInt(process.env.AERE_END, 10) : head;
start = process.env.AERE_START ? parseInt(process.env.AERE_START, 10) : end;
} else {
const count = parseInt(process.env.AERE_COUNT || String(DEFAULT_COUNT), 10);
end = head;
start = Math.max(0, head - count + 1);
}
if (start > end) [start, end] = [end, start];
return { start, end };
}
// ---- main ---------------------------------------------------------------------------------------
async function main() {
const report = {
harness: 'execution-kernel/mi-replay-diferential.mjs',
contract: 'MACHINE-INTERFACE-CONTRACT.md',
generatedAt: new Date().toISOString(),
besuRpc: BESU_RPC,
nethermindRpc: NETH_RPC,
mode: INJECT ? 'negative-control' : 'differential',
inject: INJECT || null,
chainId: null,
range: null,
heads: null,
blocksCovered: 0,
blocksUncovered: 0,
directions: null,
preStateLinkage: null,
divergences: [],
honesty: 'A matching header root served over RPC proves both endpoints serve the same bytes; '
+ 'the load-bearing fact is that Nethermind validates on processing and rejects on '
+ 'disagreement. Consensus is classical secp256k1 ECDSA QBFT plus Falcon; MI is execution '
+ 'only and this is not a post-quantum-consensus claim. Not a benchmark.',
verdict: null,
};
console.log('MachineInterface (MI) differential replay harness, Execution Kernel treapta 1');
console.log(` contract: ${report.contract}`);
console.log(` Besu RPC: ${BESU_RPC}`);
console.log(` Nethermind: ${NETH_RPC}`);
console.log(` mode: ${report.mode}${INJECT ? ` (AERE_MI_INJECT=${INJECT})` : ''}`);
// chain-id gate on both endpoints
const [besuChain, nethChain] = await Promise.all([chainIdOf(BESU_RPC), chainIdOf(NETH_RPC)]);
report.chainId = { besu: besuChain, nethermind: nethChain };
if (besuChain !== 2800) console.warn(` WARNING: Besu chainId ${besuChain}, expected 2800`);
if (nethChain !== besuChain) {
report.verdict = `NOT MEASURED: chainId mismatch (Besu ${besuChain} vs Nethermind ${nethChain})`;
return finish(report, 2);
}
// heads: compare only up to the lower head, minus a safety margin off the tip
const [besuHead, nethHead] = await Promise.all([headOf(BESU_RPC), headOf(NETH_RPC)]);
report.heads = { besu: besuHead, nethermind: nethHead };
const safeHead = Math.max(0, Math.min(besuHead, nethHead) - SAFETY);
console.log(` heads: Besu ${besuHead}, Nethermind ${nethHead} (compare at/below ${safeHead})`);
let { start, end } = resolveRange(safeHead);
if (end > safeHead) end = safeHead;
if (start > end) {
report.verdict = 'NOT MEASURED: no blocks in range are held by both clients';
return finish(report, 2);
}
report.range = { start, end };
const numbers = [];
for (let n = start; n <= end; n++) numbers.push(n);
// one extra predecessor for the pre-state linkage check, if available
const linkNumbers = start > 0 ? [start - 1, ...numbers] : numbers.slice();
console.log(` range: [${start} .. ${end}] (${numbers.length} blocks)\n`);
// fetch both sides
const [besuArr, nethArr] = await Promise.all([
mapPool(linkNumbers, CONCURRENCY, (n) => getHeader(BESU_RPC, n).catch(() => null)),
mapPool(linkNumbers, CONCURRENCY, (n) => getHeader(NETH_RPC, n).catch(() => null)),
]);
const besuByNum = new Map();
const nethByNum = new Map();
linkNumbers.forEach((n, i) => { besuByNum.set(n, besuArr[i]); nethByNum.set(n, nethArr[i]); });
// negative control: plant exactly one divergence into the REAL fetched data
let expected = null;
if (INJECT) {
const plan = parseInject(INJECT, numbers);
expected = applyInject(besuByNum, nethByNum, plan);
console.log(' NEGATIVE CONTROL: planted a synthetic divergence into fetched data');
console.log(` plant: op=${plan.op} field=${plan.field ?? '(whole block)'} side=${plan.side} block=${plan.block}`);
console.log(` require: the gate reports kind "${expected.expectKind}" at block ${expected.block}` +
`${expected.field ? ` field ${expected.field}` : ''}\n`);
}
// coverage accounting (before injection semantics): a block held by neither is uncovered
for (const n of numbers) {
const held = (besuByNum.get(n) != null) || (nethByNum.get(n) != null);
if (held) report.blocksCovered++; else report.blocksUncovered++;
}
// the core comparison (SAME code for real run and negative control)
const { divergences, directions } = compareInterval(besuByNum, nethByNum, numbers);
report.divergences = divergences;
report.directions = directions;
// pre-state linkage, per client
report.preStateLinkage = {
besu: preStateLinkage(besuByNum, numbers),
nethermind: preStateLinkage(nethByNum, numbers),
};
// sample line so the roots are visibly real, not asserted
const sampleN = numbers[0];
const sB = besuByNum.get(sampleN);
const sN = nethByNum.get(sampleN);
if (sB && sN) {
console.log('Sample (both clients, block ' + sampleN + '):');
console.log(` besu stateRoot ${sB.stateRoot}`);
console.log(` nethermind stateRoot ${sN.stateRoot}`);
console.log('');
}
console.log('Directions (both, per D-150):');
console.log(` fields compared on blocks held by both: ${directions.fieldsCompared}`);
console.log(` forward (value differs / one-sided field): ${directions.forwardDiff}`);
console.log(` reverse (block only on Besu): ${directions.reverseOnlyBesu}`);
console.log(` reverse (block only on Nethermind): ${directions.reverseOnlyNeth}`);
const link = report.preStateLinkage;
console.log(` pre-state linkage: besu ${link.besu.issues.length ? 'FAIL' : 'ok'} (${link.besu.checked} links), ` +
`nethermind ${link.nethermind.issues.length ? 'FAIL' : 'ok'} (${link.nethermind.checked} links)`);
console.log('');
// verdicts
if (INJECT) {
const caught = divergences.find((d) => d.block === expected.block && d.kind === expected.expectKind &&
(expected.field == null || d.field === expected.field));
if (caught) {
report.verdict = `NEGATIVE CONTROL PASSED: the gate went red on the planted ${expected.expectKind}` +
` at block ${expected.block}${expected.field ? ` field ${expected.field}` : ''}.`;
console.log('RESULT: ' + report.verdict);
console.log(' (a green run therefore means something: this gate can and did reject.)');
return finish(report, 0);
}
report.verdict = `NEGATIVE CONTROL FAILED: the planted ${expected.expectKind} at block ${expected.block}` +
`${expected.field ? ` field ${expected.field}` : ''} was NOT caught. The gate is decoration.`;
console.log('RESULT: ' + report.verdict);
return finish(report, 1);
}
const linkFail = link.besu.issues.length > 0 || link.nethermind.issues.length > 0;
if (report.blocksCovered === 0) {
report.verdict = 'NOT MEASURED: no block in the range was held by either client';
console.log('RESULT: ' + report.verdict);
return finish(report, 2);
}
if (divergences.length === 0 && !linkFail) {
report.verdict = `AGREE: Besu and Nethermind committed identical MI fields ` +
`(stateRoot, receiptsRoot, transactionsRoot, logsBloom, gasUsed, hash, parentHash) in BOTH ` +
`directions for all ${report.blocksCovered} covered blocks, and the pre-state chain links on ` +
`both clients. Two independent execution paths derived the same post-state.`;
console.log('RESULT: AGREE across ' + report.blocksCovered + ' blocks (both directions, 0 divergences).');
finish(report, 0);
} else {
report.verdict = `MISMATCH: ${divergences.length} divergence(s)` +
(linkFail ? ' plus a pre-state linkage failure' : '') + ` across ${report.blocksCovered} blocks.`;
console.log(`RESULT: MISMATCH, ${divergences.length} divergence(s):`);
for (const d of divergences.slice(0, 12)) {
console.log(` block ${d.block} ${d.field ? `field ${d.field} ` : ''}[${d.kind}] besu=${trunc(d.besu)} neth=${trunc(d.nethermind)}`);
}
if (linkFail) {
[...link.besu.issues, ...link.nethermind.issues].slice(0, 6).forEach((s) => console.log(' linkage: ' + s));
}
finish(report, 1);
}
}
function trunc(v) { const s = String(v); return s.length > 22 ? s.slice(0, 22) + '..' : s; }
function finish(report, code) {
try {
const outDir = path.join(HERE, 'results');
fs.mkdirSync(outDir, { recursive: true });
const tag = report.range ? `${report.range.start}-${report.range.end}` : 'norange';
const outPath = process.env.AERE_OUT || path.join(outDir, `mi-replay-${report.mode}-${tag}-${Date.now()}.json`);
fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
console.log(`\nReport written: ${outPath}`);
} catch (e) {
console.warn('could not write report: ' + e.message);
}
process.exit(code);
}
main().catch((e) => {
console.error('FATAL: ' + e.message);
process.exit(2);
});