Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
158 lines
20 KiB
Markdown
158 lines
20 KiB
Markdown
# AERE Network: Trustless Extended Block-Hash Lookback via Native EIP-2935
|
|
|
|
**Chain:** AERE Network mainnet, chain ID 2800 (Hyperledger Besu QBFT), 0.5s blocks.
|
|
**Status date:** 2026-07-11.
|
|
**Source of truth for addresses:** `sdk-js/src/addresses.ts`. Contract source: `contracts/contracts/anchor/`. Consensus change: the AERE Besu fork.
|
|
**Scope:** how AERE extends the trustless block-hash lookback horizon from 256 blocks to 8191 blocks by writing the EIP-2935 history-storage ring buffer inside consensus, and how the `AereHistoryStateRootAnchor` contract turns that reach into canonical, trust-minimized state roots that make the storage-proof coprocessor useful for blocks older than about two minutes.
|
|
|
|
This document describes a consensus-layer change (native EIP-2935 in the Besu fork) plus one application contract (`AereHistoryStateRootAnchor`). It is precise about status: the native EIP-2935 extended-lookback write path is now LIVE on mainnet chain 2800, activated at block 9,189,161 (2026-07-12) as part of the AerePQC hard fork; the `AereHistoryStateRootAnchor` consumer contract that reads it is not yet in the canonical address registry and is pending mainnet deployment. Where a capability is live on chain 2800 versus pending mainnet deployment versus roadmap, that is stated explicitly.
|
|
|
|
---
|
|
|
|
## 0. Honesty preamble (read this first)
|
|
|
|
1. **The extended-lookback write path is now live on mainnet.** The native EIP-2935 write path was implemented in the AERE Besu fork, demonstrated end-to-end on a fresh scratch chain (chain ID 28099), and activated on live chain 2800 at block 9,189,161 (2026-07-12), bundled into the single AerePQC hard-fork alongside the NIST post-quantum precompiles under one coordinated activation. The history-storage system contract now returns its canonical runtime on mainnet, so the 8191-block window is available. The `AereHistoryStateRootAnchor` consumer contract that reads it is not yet in the canonical registry (pending mainnet deployment); the live 256-block `AereStateRootAnchor` continues to use the `BLOCKHASH` window by design.
|
|
|
|
2. **This is an application and consensus-support feature, not a change to how blocks are finalized.** QBFT still produces and finalizes blocks exactly as before. EIP-2935 only adds a consensus-written history buffer that EVM contracts can read. It does not touch validator set, ordering, or finality.
|
|
|
|
3. **The trust model is identical to `BLOCKHASH`, and no larger.** The value the anchor trusts is written by consensus itself, never by a keeper, relayer, or Foundation key. Extending the reach from 256 to 8191 blocks does not add a new trusted party, and does not remove the honest-majority assumption on the seven-validator QBFT set.
|
|
|
|
4. **Decentralization baseline.** AERE runs seven QBFT validators, single operator, single client (Besu), no external audit of the AERE-authored contracts. That is the context for everything below. Extended lookback improves a canonicity mechanism; it does not change the operator or validator count.
|
|
|
|
---
|
|
|
|
## 1. The problem: the 256-block canonicity horizon
|
|
|
|
AERE has a live storage-proof coprocessor, `AereStorageProofVerifier` (`0xF9a1A183bEb3147D88dA5927301683fEbFb9362E`, canonical, immutable, no owner). It takes an SP1 Groth16 proof whose guest RLP-walks AERE's real Merkle-Patricia trie from a `stateRoot` down to a storage `value`, and commits exactly 192 bytes of public values, `abi.encode(chainId, blockNumber, stateRoot, account, slot, value)`. It proves a precise statement: **at the state root R, account A's slot S held value V.**
|
|
|
|
There is one thing that proof cannot do on its own: it cannot show that R is the **canonical** state root of `blockNumber` on chain 2800. Inside the proof, `blockNumber` is just metadata bytes; nothing binds R to the real chain. A caller could hand the verifier any internally-consistent `(R, A, S, V)` from a fabricated trie. So the verifier ships two entry points: `submitProof` (records against a caller-supplied root, canonicity is the caller's problem) and `submitProofWithExpectedRoot` (records only if the proof's root equals a root the caller vouches for). The canonicity check is deliberately pushed to the call site.
|
|
|
|
The clean, trust-minimized way to supply that canonical root is to recover it from consensus. The EVM already exposes one such source: the `BLOCKHASH` opcode, which returns the true hash of a recent block. AERE's first anchor, `AereStateRootAnchor` (`0x78b40a983E89c91Aefd8A62Be709bDF25ABB57cb`, canonical in `sdk-js/src/addresses.ts`, deployment artifact `contracts/deployments/state-root-anchor.json`), uses exactly this: it reads `blockhash(n)`, checks a caller-supplied RLP header hashes to it, decodes the header's state root, and records that root as anchored. The block hash comes strictly from the opcode, never from a caller argument, so nobody can anchor a fabricated root.
|
|
|
|
The limit is reach. `BLOCKHASH` serves only the **last 256 blocks**, about 128 seconds at AERE's 0.5s block time. A consumer wanting to assert "at block N, this contract's owner was X" has roughly two minutes after N to anchor it, or the opcode goes dark and the canonical root is unrecoverable from within the EVM. That is far too tight for any workflow where prover and on-chain consumer are not tightly coupled in time: cross-domain settlement, a light client catching up, an agent proving a historical fact, a dispute over a block already a few minutes old. The deployed 256-block anchor is honest about this in its own source and artifact.
|
|
|
|
EIP-2935 is the mechanism that closes this gap without introducing a trusted oracle.
|
|
|
|
---
|
|
|
|
## 2. EIP-2935: a consensus-written history ring buffer
|
|
|
|
EIP-2935 (part of the Prague ruleset) defines a **history-storage system contract** at the canonical address `0x0000F90827F1C53a10cb7A02335B175320002935`. Its behavior is simple and its trust properties are the whole point:
|
|
|
|
- **It is a ring buffer of the last 8191 block hashes.** At the start of every block, the parent block's hash is written into storage slot `(number - 1) % 8191`. The buffer therefore always holds the hashes of the previous 8191 blocks and no more.
|
|
|
|
- **It is written by consensus, not by a transaction.** On the AERE Besu fork, Besu's `PraguePreExecutionProcessor` performs this write as a **system call from address `0xffff...fffe`** at the start of block processing, before any user transaction runs. There is no externally-owned account, no keeper, no relayer, and no Foundation key involved. The write is part of producing the block. A validator that omitted or forged it would produce a block that other validators reject.
|
|
|
|
- **It is read by a plain `STATICCALL`.** The runtime code answers a read whose input is the 32-byte big-endian block number and whose output is the 32-byte canonical hash of that block. For any block number **outside** the served range `[number - 8191, number - 1]`, it reverts (and returns no 32-byte value), so an out-of-window query is unambiguous.
|
|
|
|
The reach is the improvement. 8191 blocks at 0.5s is roughly **68 minutes** of trustless lookback, against the ~128 seconds `BLOCKHASH` gives. Same trust source (consensus), same "the hash is never a caller argument" property, 32x more blocks and about 32x more wall-clock reach.
|
|
|
|
Note the history on chain 2800. The Pectra ruleset AERE activated in May 2026 nominally listed EIP-2935, but the history-storage contract was not actually populated on mainnet: `eth_getCode` at the system address returned `0x`, so the extended window was unavailable in practice, which is why the live 256-block `AereStateRootAnchor` used `BLOCKHASH`. The AerePQC hard fork (block 9,189,161) added the missing consensus-side write path that makes the ring buffer real: `eth_getCode` at the system address now returns the canonical Pectra runtime (which encodes the 8191-block ring buffer), so the extended window is live on mainnet.
|
|
|
|
---
|
|
|
|
## 3. Native implementation in the fork, bundled into the AerePQC hard-fork
|
|
|
|
The change is in the AERE Besu fork, in the Prague pre-execution path. At each block the `PraguePreExecutionProcessor` writes the parent hash into the EIP-2935 ring buffer via the system-address call described above. Because it is a consensus rule and not a deployed application contract, the buffer is populated deterministically by every validator as a condition of block validity. Nothing needs to be "kept alive" by an operator.
|
|
|
|
On mainnet this activated as part of the **single AerePQC hard-fork** (Besu `futureEips` milestone, `futureEipsTime = 1783820272`), at block 9,189,161, the same fork that carries AERE's native NIST post-quantum precompiles. Coordinating both into one activation was intentional: mainnet got extended trustless lookback and the post-quantum verification path in one scheduled event. The PQC relevance is scheduling, not cryptography: EIP-2935 is a hash ring buffer, not a post-quantum construction, but it rides the same hard-fork so the network takes one coordinated upgrade instead of two.
|
|
|
|
**First proven on scratch (chain ID 28099), now live on mainnet.** On a fresh scratch chain with the fork active, the ring buffer was verified end-to-end before the mainnet activation:
|
|
|
|
- Blocks advanced past the 256-block `BLOCKHASH` horizon.
|
|
- A header **258 or more blocks back** (beyond where `BLOCKHASH` returns anything) was anchored: its canonical hash was recovered **purely from the EIP-2935 system contract** via `STATICCALL`, and the header's state root was recovered from that hash alone.
|
|
- The served window behaves as specified out to 8191 blocks.
|
|
|
|
That scratch run demonstrated the write path, the read path, and the anchor contract working together against a real consensus-written buffer. The write path itself has since activated on mainnet at block 9,189,161: the system contract now returns its canonical runtime on chain 2800, so the ring buffer is live. What remains pending on mainnet is the `AereHistoryStateRootAnchor` consumer contract deployment (see Sections 4 and 7), not the underlying consensus buffer.
|
|
|
|
---
|
|
|
|
## 4. The `AereHistoryStateRootAnchor` mechanism
|
|
|
|
`AereHistoryStateRootAnchor` (source at `contracts/contracts/anchor/AereHistoryStateRootAnchor.sol`; not yet in `addresses.ts`, pending mainnet deployment against the now-live EIP-2935 buffer) is the EIP-2935 sibling of `AereStateRootAnchor`. It is byte-for-byte the same shape as the 256-block anchor, with one difference: where the block hash comes from.
|
|
|
|
**Canonical hash source.** `_historyBlockHash(blockNumber)` `STATICCALL`s the system contract at `HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935` with the 32-byte block number as input. It treats a revert, a non-32-byte return, or a zero return as out-of-window (`OutOfHistoryWindow`), and requires the system contract to have code at all (`HistoryContractMissing`) so the contract fails closed on a chain where the fork is not active. `HISTORY_SERVE_WINDOW = 8191` is the declared reach. The hash is read at call time from consensus storage and is never taken from a caller argument.
|
|
|
|
**Anchoring a historical header.** `anchorHistoricalHeader(blockNumber, rlpHeader)`:
|
|
1. reads the canonical hash from the ring buffer (`_historyBlockHash`);
|
|
2. requires `keccak256(rlpHeader) == canonical`, so the caller cannot substitute a forged header (`HeaderHashMismatch`);
|
|
3. decodes the state root from the header at a fixed offset and records `(blockNumber to stateRoot, blockHash, verifiedAt)` permanently.
|
|
|
|
The decode relies on the fact that a block header's first four fields are fixed-size, so the state root sits at a known offset regardless of any later fields (`extraData`, QBFT commit-seal handling, or the post-Prague tail): `[0] parentHash` (32B, RLP prefix `0xa0`), `[1] ommersHash` (32B, `0xa0`), `[2] beneficiary` (20B, `0x94`), `[3] stateRoot` (32B, `0xa0`). The contract validates each of those prefixes before reading the 32 state-root bytes, and reverts `MalformedHeader` with a numbered code otherwise. This is the same decode logic the live 256-block anchor uses, so QBFT headers that anchor there anchor here identically.
|
|
|
|
**Proving against an anchored root.** `proveAgainstAnchor(publicValues, proof)`:
|
|
1. calls `VERIFIER.verify(publicValues, proof)`, which reverts on an invalid SP1 proof or wrong chainId, and returns `(blockNumber, stateRoot, account, slot, value)` from the proof's committed public values;
|
|
2. loads the anchored record for **that same `blockNumber`** (the block number is committed inside the proof, so the caller cannot mismatch the proof and the anchor), reverting `NotAnchored` if absent;
|
|
3. requires the proof's committed `stateRoot` equals the anchored `stateRoot` (`RootNotCanonical`);
|
|
4. only then calls `VERIFIER.submitProofWithExpectedRoot(publicValues, proof, anchoredRoot)`, which re-verifies and records the slot in the live coprocessor.
|
|
|
|
**Immutability.** `VERIFIER` is fixed at construction. There is no owner, no admin, and no upgrade path. Anyone may anchor any in-window header, and anyone may prove against any anchored root. Once a `(blockNumber to stateRoot)` record exists, it is permanent and independent of the moving 8191-block ring buffer: the buffer only needs to have served the hash at anchoring time.
|
|
|
|
---
|
|
|
|
## 5. Trust model: identical to `BLOCKHASH`, no keeper
|
|
|
|
The security argument is short because it reduces to consensus.
|
|
|
|
- The only value the anchor trusts, the canonical block hash, is produced by the same actor that produces `BLOCKHASH`: QBFT consensus, writing the ring buffer as a system call at the start of each block. There is no additional trusted party. An attacker who could forge an EIP-2935 answer could equally forge `BLOCKHASH`, which means they already control block production.
|
|
- The header cannot be forged: `keccak256(rlpHeader)` must equal the consensus-written hash, and the state root is decoded from that exact header. A wrong header reverts before anything is recorded.
|
|
- The proof cannot be aimed at a wrong root: the block number is inside the proof's public values, the anchor keys on that number, and the state root must match. The verifier reverts the whole call on an invalid proof or chainId mismatch, so no unverified state is ever recorded.
|
|
- No component depends on an off-chain keeper, oracle feed, relayer, or Foundation signature. The only liveness requirement is that **someone** anchors a header while it is still inside the 8191-block window; after that the record is permanent.
|
|
|
|
The honest boundary is the same one that applies to all of AERE: this inherits the honest-majority assumption of a seven-validator, single-operator QBFT set. Extended lookback does not weaken that assumption and does not strengthen it. It moves a mechanism that was capped at 256 blocks up to 8191 blocks under the exact same trust source.
|
|
|
|
---
|
|
|
|
## 6. What this unlocks: trust-minimized storage proofs with a usable time horizon
|
|
|
|
The storage-proof coprocessor is only as useful as the window in which its roots can be made canonical on-chain. With the 256-block anchor, a proof about block N is only trust-minimized if it is anchored within ~128 seconds of N. That rules out most real uses. With native EIP-2935, the same proof is trust-minimized if it is anchored within ~68 minutes of N, and the resulting record is permanent thereafter.
|
|
|
|
Concretely, the two-call pattern becomes practical:
|
|
1. Within 68 minutes of block N, anyone calls `anchorHistoricalHeader(N, rlpHeader)`. The header is checked against the consensus-written hash and its state root is recorded forever.
|
|
2. At any later time, anyone submits a storage proof about slot S of account A at block N via `proveAgainstAnchor`. The proof's root is matched against the permanently anchored canonical root, then recorded in `AereStorageProofVerifier`.
|
|
|
|
This is the canonicity building block the storage-proof coprocessor was designed to lean on, and the same one the validity anchors want. AERE's rollup validity anchors, the bounded-VM `AereRollupValidity` (`0x38772063572DF94E90351e44ccbBEefD5F497fbd`, canonical) and the full-EVM `AereEVMValidity` (`0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26`, canonical), prove statements *about* a state transition; anchoring lets a consumer bind those statements to a canonical AERE block instead of a bare root. Extended lookback widens the window for establishing that binding from two minutes to over an hour.
|
|
|
|
---
|
|
|
|
## 7. Proven scope versus production (explicit)
|
|
|
|
**What is proven, and where:**
|
|
|
|
- Native EIP-2935 write path implemented in the AERE Besu fork (`PraguePreExecutionProcessor`, system-address write, no keeper) and **live on mainnet chain 2800 since block 9,189,161** (the AerePQC hard fork). It was first demonstrated end-to-end on scratch chain **28099** (a header **258+ blocks back**, beyond the 256-block `BLOCKHASH` horizon, anchored with its state root recovered **purely from the system contract**); on mainnet, `eth_getCode` at the system address now returns the canonical Pectra runtime, which serves an **8191-block (~68 min at 0.5s)** window.
|
|
- `AereHistoryStateRootAnchor` compiles, and its anchor-then-prove flow works against a real consensus-written ring buffer (verified on the scratch chain). Its decode and prove logic are identical to the live 256-block `AereStateRootAnchor`.
|
|
- The downstream consumer, `AereStorageProofVerifier`, is **live on mainnet** and has recorded a real proof (`AereTreasury` `0x687933...6119` slot 0 equals the Foundation `0x0243A4...f3C3` at block 8915939, against canonical state root `0x515fce04...4a92f3`, submit tx `0xe541c52b...6c54`, gasUsed 348,345, under the EIP-7825 per-tx cap).
|
|
|
|
**What is not yet true:**
|
|
|
|
- `AereHistoryStateRootAnchor` (the 8191-block consumer contract) is **not yet deployed or registered on mainnet** and is not in `sdk-js/src/addresses.ts`. The underlying EIP-2935 buffer it reads is now live on chain 2800, so the contract can be deployed against it; that deployment plus registration is the remaining step. The live 256-block `AereStateRootAnchor` continues to use `BLOCKHASH` by design and is unaffected.
|
|
- The extended-window anchor path has been exercised against a consensus-written buffer on the scratch chain; it has not yet been exercised on mainnet under production load, pending the consumer contract deployment above.
|
|
- The 8191-block window is still **bounded**. Blocks older than ~68 minutes cannot be freshly anchored from within the EVM by this mechanism; only records anchored while in-window survive (permanently). A trustless anchor for arbitrarily old blocks (for example a consensus-maintained accumulator or a checkpoint tree) is a separate, unbuilt design and is explicitly roadmap.
|
|
- The decentralization baseline is unchanged: seven validators, one operator, one client, no external audit.
|
|
|
|
---
|
|
|
|
## 8. Address and status table
|
|
|
|
**Canonical (verbatim from `sdk-js/src/addresses.ts`):**
|
|
|
|
| Role | Contract | Address | Status |
|
|
|---|---|---|---|
|
|
| Storage-proof coprocessor (consumer) | `AereStorageProofVerifier` | `0xF9a1A183bEb3147D88dA5927301683fEbFb9362E` | Live on chain 2800 |
|
|
| SP1 routing (proof verification) | `SP1VerifierGateway` | `0x9ca479C8c52C0EbB4599319a36a5a017BCC70628` | Live on chain 2800 |
|
|
| Rollup validity anchor (bounded VM) | `AereRollupValidity` | `0x38772063572DF94E90351e44ccbBEefD5F497fbd` | Live on chain 2800 |
|
|
| Recent-lookback anchor (BLOCKHASH, 256) | `AereStateRootAnchor` | `0x78b40a983E89c91Aefd8A62Be709bDF25ABB57cb` | Live on chain 2800, 256-block window |
|
|
| Full-EVM validity anchor (single block) | `AereEVMValidity` | `0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26` | Live on chain 2800 |
|
|
| Full-EVM validity anchor (batch) | `AereEVMValidityBatch` | `0x49D30a5999eA5b7f6eFf12196AB006316683Ff3C` | Live on chain 2800 |
|
|
|
|
**Deployed or proven but not yet in the canonical registry (pending registration):**
|
|
|
|
| Role | Contract / component | Address / location | Status |
|
|
|---|---|---|---|
|
|
| Extended-lookback anchor (EIP-2935, 8191) | `AereHistoryStateRootAnchor` | `contracts/contracts/anchor/AereHistoryStateRootAnchor.sol` | Verified on scratch chain 28099; pending mainnet deployment against the now-live buffer |
|
|
| Native EIP-2935 write path | AERE Besu fork (`PraguePreExecutionProcessor`) | history contract `0x0000F90827F1C53a10cb7A02335B175320002935` | Live on chain 2800 since block 9,189,161 (AerePQC hard fork) |
|
|
|
|
---
|
|
|
|
*Every canonical address above is verbatim from `sdk-js/src/addresses.ts`. Addresses absent from that registry are cited from the named repo artifact or the exact deployment record and marked pending registration. The native EIP-2935 write path is live on mainnet chain 2800 since block 9,189,161 (the AerePQC hard-fork); the `AereHistoryStateRootAnchor` consumer contract that reads it is verified on scratch chain 28099 and is pending mainnet deployment. Block-time-derived figures (128s for 256 blocks, ~68 min for 8191 blocks) assume the current 0.5s block interval. No throughput or TPS figure is claimed here.*
|