aere-node/RUN-A-NODE.md

265 lines
14 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 since 2026-08-14. From block 14,050,000 the
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. 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 full-quantum activation at 14,050,000 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
is around twelve million blocks and climbing by two per second. 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 seven
QBFT validators, and none of the seven 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.