From 600a71607089ecabab0f43a0d5c3b8f0d50c4a1a Mon Sep 17 00:00:00 2001 From: Aere Network Date: Sat, 15 Aug 2026 16:07:02 +0300 Subject: [PATCH] Patch 0005: the import proof was attempted, and it found what no green test had A from-genesis sync of a patches-only build stopped at block 9,189,161, the futureEipsTime activation, with a stateroot mismatch. Two causes, one patch: upstream bases futureEips on the unreleased amsterdam definition while chain 2800 isolates it on Osaka, and the fleet activates EIP-2935 history storage in the same fork, one system state write per block that a build without this patch never performs. With 0005 applied the same node, same data directory, crossed the activation and keeps importing. The fleet's optional Block-STM wiring is deliberately omitted and the omission is stated in the patch header. --- RUN-A-NODE.md | 22 ++++-- patches/0005-aere-eip2935-futureeips.patch | 82 ++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 patches/0005-aere-eip2935-futureeips.patch diff --git a/RUN-A-NODE.md b/RUN-A-NODE.md index 031c083..d2a73ce 100644 --- a/RUN-A-NODE.md +++ b/RUN-A-NODE.md @@ -67,11 +67,23 @@ network refuses to finalize blocks without that post-quantum quorum. The anchor ships in this repository both as source, under `anchor/`, and as `patches/0003-aere-pq-anchor.patch`. A node built without it has not been shown to import headers past block 13,014,000: the second client needed an explicit change to accept the sixth -element, and no floor-only build has been observed following the chain past that height. What -this repository does NOT yet contain is an import proof for a node built from these patches -alone, of the kind the rest of this file insists on; until that proof is added here, apply -`0001`, `0003` and `0004` together and treat a build without `0003` as unable to follow the -current chain. +element, and no floor-only build has been observed following the chain past that height. Apply +`0001`, `0003`, `0004` and `0005` together and treat a build missing any of them as unable to +follow the current chain. + +## Patch 0005 exists because the import proof was actually attempted + +Measured 2026-08-15, on a real from-genesis sync of a build from patches +`0001+0003+0004` alone: the node followed chain 2800 for 9.18 million blocks and stopped at +block 9,189,161, the `futureEipsTime` activation, with `failed persisting block due to +stateroot mismatch`. Not one cause but two, both in `MainnetProtocolSpecs.java`, both now +carried by `patches/0005-aere-eip2935-futureeips.patch`: upstream bases the futureEips +milestone on the unreleased amsterdam definition (Block Access Lists, a different state-root +committer), while chain 2800 isolates it on Osaka; and the fleet activates EIP-2935 historical +block hashes in that same fork, one system state write per block from 9,189,161 onward, which +a build without the patch never performs. No amount of green unit tests found this; importing +the real chain found it in one afternoon. The sync continues past the activation with `0005` +applied, and this section will carry the measured height as it climbs. Measured 2026-08-15: on a pristine `d2032017` checkout, `git apply` (no flags) accepted `0001`, `0003` and `0004` in that order, and `./gradlew --no-daemon installDist diff --git a/patches/0005-aere-eip2935-futureeips.patch b/patches/0005-aere-eip2935-futureeips.patch new file mode 100644 index 0000000..90c3292 --- /dev/null +++ b/patches/0005-aere-eip2935-futureeips.patch @@ -0,0 +1,82 @@ +AERE patch 0005: futureEips activation isolated on Osaka, plus protocol-native EIP-2935 + +Why this patch exists, measured 2026-08-15 on a from-genesis sync: a node built +from patches 0001+0003+0004 alone followed chain 2800 to block 9,189,160 and +stopped at 9,189,161, the futureEipsTime activation block, with +"failed persisting block due to stateroot mismatch". Two independent causes, +both fixed here, both in MainnetProtocolSpecs.java: + +1. Upstream bases futureEips on amsterdamDefinition, which drags in unreleased + amsterdam-milestone changes (EIP-7928 Block Access Lists with a new state + root committer, EIP-7708 transfer logs, 2D gas accounting). Chain 2800 runs + Osaka; the fleet isolates the AERE milestone on osakaDefinition so the + post-activation block format stays identical to Osaka. +2. The fleet activates EIP-2935 (historical block hashes, ring buffer in the + system contract at 0x0000F90827F1C53a10cb7A02335B175320002935) in the SAME + futureEips fork, so every block from 9,189,161 onward performs one system + state write. A build without it computes a different state root on the very + first activation block, which is exactly what was measured. + +Modified upstream file: MainnetProtocolSpecs.java (plus one build.gradle line). +The upstream copyright header is unchanged, per Apache License 2.0 section 4(c); +the in-diff comments state exactly what changed, per section 4(b). The base is +upstream commit d2032017bb3b8cb215a97303980a1e4a643f7180. + +One deliberate omission: the fleet's own copy of this change also wires an optional +Block-STM parallel block processor behind an environment gate. That wiring references +source files not published here, and with the gate unset it reproduces the default +builder exactly, so this patch carries the consensus-relevant part only: the Osaka +isolation and EIP-2935. A build from this patch follows the chain; it does not enable +parallel execution. + +diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +index 87f1a6b..545aeb2 100644 +--- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java ++++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +@@ -1321,7 +1321,17 @@ public abstract class MainnetProtocolSpecs { + final boolean isParallelTxProcessingEnabled, + final BalConfiguration balConfiguration, + final MetricsSystem metricsSystem) { +- return amsterdamDefinition( ++ // AERE MILESTONE ISOLATION: base the AerePQC (futureEips) activation on the ++ // OSAKA spec, NOT amsterdam. Mainnet runs Osaka/Fusaka, and inheriting ++ // amsterdamDefinition would have silently activated every unreleased ++ // amsterdam-milestone change on futureEipsTime, notably EIP-7928 Block ++ // Access Lists (new block field + BAL state-root committer), EIP-7708 ++ // transfer logs, and EIP-7778/EIP-8037 2D pre-refund gas accounting + ++ // Amsterdam gas calculator. Those are block-format / state-transition ++ // changes that would fork the chain away from Osaka. Basing on osaka makes ++ // the post-activation block format IDENTICAL to Osaka and adds ONLY the two ++ // AERE features below: (a) the 5 PQC precompiles and (b) EIP-2935. ++ return osakaDefinition( + chainId, + enableRevertReason, + genesisConfigOptions, +@@ -1330,7 +1340,15 @@ public abstract class MainnetProtocolSpecs { + isParallelTxProcessingEnabled, + balConfiguration, + metricsSystem) ++ // AERE EIP-2935: historical block hashes (ring buffer in the system ++ // contract at 0x0000f9...2935), wired into the SAME futureEips (AerePQC) ++ // hard fork as the PQC precompiles so a single futureEipsTime activation ++ // delivers both. Forced on unconditionally here: the inherited Prague ++ // definition falls back to FrontierPreExecutionProcessor for QBFT/PoA ++ // chains without system-contract addresses, which would silently skip ++ // EIP-2935 history storage. This override restores it, protocol-native. ++ .preExecutionProcessor(new PraguePreExecutionProcessor()) + .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::futureEips) + .hardforkId(FUTURE_EIPS); + } + +diff --git a/evm/build.gradle b/evm/build.gradle +index b416c99..9b4e0a5 100644 +--- a/evm/build.gradle ++++ b/evm/build.gradle +@@ -37,6 +37,7 @@ dependencies { + annotationProcessor 'com.google.dagger:dagger-compiler' + + implementation project(':crypto:algorithms') ++ implementation 'org.bouncycastle:bcprov-jdk18on' + implementation project(':datatypes') + implementation project(':ethereum:rlp') + implementation project(':util')