269 lines
15 KiB
Markdown
269 lines
15 KiB
Markdown
# Run a node on Aere Network mainnet, chain 2800
|
|
|
|
This file plus `genesis.json` plus one bootnode enode (see the bootnodes section below) is
|
|
everything an outsider needs to bring up a full node that syncs chain 2800 from block 0. Until
|
|
2026-08-03 this repository shipped the precompile sources and the NIST vectors but not the chain
|
|
configuration, so nobody outside the Foundation could actually start a node, and the "verify it
|
|
yourself" claim the repository is built on could not be exercised end to end.
|
|
|
|
Nothing here is a key. `genesis.json` is public chain configuration; a node that nobody can dial
|
|
is a node nobody can sync from, and the bootnodes section says plainly how dialing works today.
|
|
|
|
---
|
|
|
|
## The three things you need
|
|
|
|
| What | Where |
|
|
|---|---|
|
|
| `genesis.json` | The genesis file, byte for byte as it sits on the Foundation nodes. sha256 `361709dccec4e9fc85be5aec33b30b58a55dc53424087d84c1ad11bd1d944c24` (of the published file with LF line endings, exactly as `curl` fetches it; a Windows checkout that converts line endings will hash differently) |
|
|
| a bootnode enode | requested by mail for now; see the bootnodes section below |
|
|
| `patches/`, `precompiles/` | The Besu fork, see `README.md`. Needed to sync past block 9,189,161 |
|
|
|
|
---
|
|
|
|
## Which Besu
|
|
|
|
Stock upstream Besu syncs chain 2800 correctly up to block **9,189,161** and then stops being able
|
|
to follow it. That block is the first with a timestamp at or after `activationTime` 1783820272, when
|
|
the five Aere post-quantum precompiles come into existence. A node without them computes a different
|
|
result for any block containing a call to `0x0AE1` through `0x0AE5` and falls off the chain.
|
|
|
|
So there are two honest paths:
|
|
|
|
- **Follow the tip.** Build the fork: apply `patches/0001-aere-pqc-precompiles-mainnet.patch` to
|
|
upstream Besu commit `d2032017bb3b8cb215a97303980a1e4a643f7180` and build it. Full instructions,
|
|
including the pre-image hashes that prove you are on the right base, are in `README.md`.
|
|
- **Verify the early chain only.** Stock Besu 26.4 or later will import blocks 0 through 9,189,160
|
|
from this genesis. That is enough to check the genesis allocation and the sub-second block period
|
|
transition against what we publish. It is not enough to reach the head.
|
|
|
|
There is a second Aere change to the client, and it is not in patch `0001`:
|
|
`patches/0004-aere-basefee-floor.patch`, one file, `LondonFeeMarket.java`. From block **10,141,734**
|
|
chain 2800 clamps the EIP-1559 base fee to a floor of 1 Gwei. The patch is fork gated by two JVM
|
|
system properties and is a byte behaviour no-op when they are unset, so a node that applies it
|
|
without setting them is stock Besu:
|
|
|
|
```
|
|
-Daere.basefee.floor.forkBlock=10141734 -Daere.basefee.floor.value=1000000000
|
|
```
|
|
|
|
Pass those through `BESU_OPTS`. Apply this patch and set these flags if you want a client that
|
|
computes what the network computes. It was measured on 2026-08-02 that QBFT and IBFT2 in Besu do not
|
|
validate the base fee at import at all, upstream or in our fork, so a node without this change is
|
|
expected to import the chain anyway and only to disagree if it ever proposed a block. That is a
|
|
finding about a missing upstream rule and not a licence to skip the patch.
|
|
|
|
Do not apply `patches/0002-...-testnet.patch` for a mainnet node. It adds `0x0AE6` and `0x0AE7`,
|
|
which are not on mainnet, and a node that has them will not disagree with the chain today but is
|
|
running a configuration the chain does not.
|
|
|
|
## The post-quantum header anchor: apply patch 0003 to follow the current chain
|
|
|
|
Since block 13,014,000 every 32nd header of chain 2800 carries a sixth element in its QBFT
|
|
`extraData`: a certificate of validator Falcon-512 seals, universal since block 13,889,296 and
|
|
enforced at a minimum of three seals per anchor block (f+1 of nine) since 2026-08-14. A node
|
|
rejects an anchor block whose certificate holds fewer than three valid seals. (Correction 2026-08-19:
|
|
an earlier version of this paragraph said that from block 14,050,000 the network refuses to finalize
|
|
any block without a post-quantum quorum; the per-block rule armed at that height is retired in the
|
|
shipped code in favour of the anchor rules and changed no enforcement, so that sentence is withdrawn.)
|
|
|
|
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. 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.
|
|
|
|
**Measured 2026-08-16: the from-genesis sync reached the live tip.** A node built only from these
|
|
packages imported chain 2800 from block 0 to height **14,088,211** and then followed the head,
|
|
crossing the 14,050,000 activation height (which, see above, changed no enforcement) with zero anchor rejections. Two more things
|
|
had to be true for that, both now carried by the patches and both described with their measured
|
|
boundaries in `IMPORT-PROOF-STARE-2026-08-15.md`: a historical window (anchor heights
|
|
13,267,824 to 13,268,944) in which anchoring was deliberately disarmed fleet-wide, and a recovery
|
|
window (13,268,976 to 13,890,544) in which proposers legitimately wrote shorter certificates.
|
|
Inside those windows the header is accepted as carried; outside them nothing changes. The
|
|
controls run in both directions (641 tests, 0 failures).
|
|
|
|
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
|
|
-x spotlessJavaCheck -x test` exited 0 on JDK 21, producing
|
|
`besu/v26.8-develop-d203201/linux-x86_64/openjdk-java-21`. That proves the recipe builds.
|
|
The import proof above, measured 2026-08-16, proves the build follows the live chain from genesis
|
|
to the tip. The proof is repeated, not assumed: see `IMPORT-PROOF-STARE-2026-08-15.md` for the
|
|
measured heights of each run.
|
|
|
|
---
|
|
|
|
## Start it
|
|
|
|
```bash
|
|
besu \
|
|
--genesis-file=/path/to/aere-node/genesis.json \
|
|
--data-path=/path/to/data \
|
|
--network-id=2800 \
|
|
--sync-mode=FULL \
|
|
--data-storage-format=BONSAI \
|
|
--bootnodes=<the enode you were given> \
|
|
--p2p-port=30303 \
|
|
--rpc-http-enabled --rpc-http-port=8545 \
|
|
--rpc-http-api=ETH,NET,WEB3,QBFT \
|
|
--min-gas-price=0
|
|
```
|
|
|
|
Bootnodes are deliberately NOT published in this repository yet. The network entry points
|
|
available today are consensus or origin hosts, and this project does not print the addresses
|
|
of its own machines in public documents; a dedicated public bootnode, on a host that serves
|
|
nothing else, is the planned replacement, and this paragraph will then carry its enode. Until
|
|
then, write to office@aere.network with the subject "bootnode" and you will receive a current
|
|
enode by return. Everything else on this page works without one; discovery is the only step
|
|
that needs it.
|
|
|
|
`--sync-mode=FULL` is deliberate. Chain 2800 runs QBFT with a half second block period, so the head
|
|
was about 12.2 million blocks when this was written (2026-08-03), passed 14.6 million on 2026-08-19,
|
|
and climbs by roughly two per second; read the live height with eth_blockNumber. Full sync from block 0 is the mode
|
|
that actually verifies the history we publish, which is the point of this repository. Snap sync
|
|
against a QBFT chain trusts a peer for the state and verifies far less.
|
|
|
|
`--min-gas-price=0` matches the network. Chain 2800 has a real 1 Gwei base fee from block
|
|
10,141,734, but the minimum gas price a node will accept into its own pool is a local policy and the
|
|
Foundation nodes run it at zero.
|
|
|
|
Outbound and inbound TCP and UDP on your p2p port must be open. Discovery is UDP; a TCP only
|
|
firewall gives you a node that dials out and is never dialled back.
|
|
|
|
---
|
|
|
|
## Check that you are on the right chain, before you trust anything else
|
|
|
|
Two facts settle it. Run these against your own node once it is up.
|
|
|
|
**1. Your block 0 must be the network's block 0.**
|
|
|
|
```bash
|
|
curl -s -X POST http://127.0.0.1:8545 -H 'content-type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0",false]}' \
|
|
| grep -o '"hash":"0x[0-9a-f]*"'
|
|
```
|
|
|
|
Expected, and this is the whole test:
|
|
|
|
```
|
|
0xd86d57a899cbfa580669f0e1c7cd4ae5f525247c3c823f8f75df176a0c5d7f1a
|
|
```
|
|
|
|
That hash covers every consensus relevant field in `genesis.json`, including the state root of the
|
|
six genesis accounts. If it matches, your genesis file is the network's genesis file and no further
|
|
comparison of individual fields is needed. If it does not match, your node will never peer, and no
|
|
amount of correct bootnodes will help.
|
|
|
|
Compare against the public endpoints, which are separate machines with separate operators of the
|
|
same file:
|
|
|
|
```bash
|
|
curl -s -X POST https://rpc.aere.network -H 'content-type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0",false]}'
|
|
curl -s -X POST https://rpc2.aere.network -H 'content-type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0",false]}'
|
|
```
|
|
|
|
**2. You must have peers.**
|
|
|
|
```bash
|
|
curl -s -X POST http://127.0.0.1:8545 -H 'content-type: application/json' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"net_peerCount","params":[]}'
|
|
```
|
|
|
|
A nonzero answer means discovery worked. Zero after a few minutes means either your UDP port is
|
|
closed or the bootnode you were given is down; a plain TCP connect to its address distinguishes
|
|
the two.
|
|
|
|
---
|
|
|
|
## What the two bootnodes are, and what they are not
|
|
|
|
They are two full nodes that are **not** in the validator set. Chain 2800 seals blocks with nine
|
|
QBFT validators (seven until 2026-08-09), and none of the nine is listed here, on purpose. Publishing a validator's p2p
|
|
address hands an attacker a target whose downtime costs the network consensus headroom, which is why
|
|
sentry style architectures exist. The two nodes above carry no consensus responsibility: if both are
|
|
taken down, new nodes cannot bootstrap until they are replaced, and block production is unaffected.
|
|
|
|
A written address is not a live address. This file cannot prove the bootnodes are up at the moment
|
|
you read it; only your own `net_peerCount` can.
|
|
|
|
Both were reachable and serving blocks when this file was written. Both are Foundation operated, so
|
|
this is one operator's word for two addresses. That is a real centralisation fact about chain 2800
|
|
today and not a documentation gap.
|
|
|
|
---
|
|
|
|
## Measured, 2026-08-03
|
|
|
|
The recipe on this page was executed rather than written. A Besu node with an **empty data
|
|
directory** was started from exactly this `genesis.json` and exactly these two bootnode URLs, on a
|
|
machine that is not a validator, with `--sync-mode=FULL` and no static nodes, no other peer hints
|
|
and no copied database.
|
|
|
|
- Its own block 0 came out as
|
|
`0xd86d57a899cbfa580669f0e1c7cd4ae5f525247c3c823f8f75df176a0c5d7f1a`, which is the live chain's
|
|
block 0. That hash covers the state root, so the six accounts in `alloc` are the network's down to
|
|
the last wei, and so is every header field in the file.
|
|
- It reached **11 peers** knowing nothing but the two enode URLs above.
|
|
- It imported from block 0 to **block 2,221,580** in about thirty minutes, with **zero** rejected
|
|
blocks, which carries it through the sub-second block period change at roughly 2,137,652 and out
|
|
the other side.
|
|
|
|
The honest limits on that run, stated because they are the difference between this section and an
|
|
advertisement:
|
|
|
|
- It was stopped at ~2.2 million rather than carried to the head, so it never reached the precompile
|
|
fork at 9,189,161 or the base fee floor at 10,141,734. **"Reaches the current head" is not
|
|
measured.**
|
|
- It ran the Foundation's build of the fork, not a build a third party made from `patches/`. That
|
|
those patches build, and that the built precompiles answer the NIST vectors, is measured
|
|
separately in `README.md`; that the two together sync to the head is not.
|
|
- Nothing here proves the bootnodes will be up when you read this.
|
|
|
|
---
|
|
|
|
## A number in this file that the chain does not agree with
|
|
|
|
`config.transitions.qbft[0].block` reads **2138451**. That is the height at which the file says the
|
|
block period drops from one second to half a second. The chain says otherwise. Sampling header
|
|
timestamps on the public endpoints puts the change just after **2,137,652**. Measured spans of
|
|
twenty blocks: from 2,137,600 the twenty blocks take 20 s, from 2,137,656 they take 15 s, from
|
|
2,137,657 they take 12 s, and from 2,137,700 they take a flat 10 s, which is 500 ms a block and
|
|
stays there through 2,138,400. The published 2138451 is roughly eight hundred blocks late and
|
|
describes a boundary that is not in the data.
|
|
|
|
It is published anyway, unaltered, because this file is the file the network runs and its value as
|
|
evidence comes from being exactly that. Two things make the discrepancy harmless to you rather than
|
|
merely tolerated: the field only governs how fast a proposer may seal, so it has no effect on a node
|
|
that follows; and it was measured, on the sync run described above, that a node carrying this exact
|
|
value imports the blocks either side of both numbers without rejecting one.
|
|
|
|
Correcting it means editing a file on live validators, which is a change to running consensus
|
|
infrastructure and is not made as a side effect of publishing a document.
|
|
|
|
## A note on the `comment` fields in `alloc`
|
|
|
|
The six genesis accounts carry `comment` strings. No client reads them; they are not consensus data
|
|
and they are inside the file only because they have always been. Two of them are wrong and are left
|
|
uncorrected here rather than quietly edited, because this file is published byte for byte as the
|
|
running nodes hold it: the dash characters are mangled, and the 1,400,000,000 AERE account is
|
|
labelled "Mining Reserve" although chain 2800 has no mining and never has. It is the staking and
|
|
emissions reserve. Correcting the text means editing a file on live nodes, which is a change to
|
|
running infrastructure and is not made as a side effect of a documentation fix.
|