301 lines
15 KiB
Bash
301 lines
15 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# run-node.sh - one command to build the Aere Network (chain 2800) fork and follow the chain
|
|
# from block 0: verify -> patch -> build -> sync -> validate the post-quantum anchor.
|
|
#
|
|
# This script is deterministic and idempotent. It checks its preconditions loudly, verifies the
|
|
# genesis and the registries by sha256 and refuses to run on a mismatch, fetches upstream Besu at
|
|
# the exact named commit, applies the Aere patches in order, builds the node, and starts it syncing
|
|
# chain 2800 from genesis with the registry and anchor configuration a following node needs.
|
|
#
|
|
# It holds no key and signs nothing. It configures a node that VALIDATES the chain, including the
|
|
# post-quantum certificate anchor, but does not PRODUCE seals; producing seals needs a validator
|
|
# Falcon key, which is not in this repository and is not required to follow the chain.
|
|
#
|
|
# Re-running it does not rebuild what is already built or re-apply patches already applied.
|
|
#
|
|
# Usage:
|
|
# ./run-node.sh [options] (the two published bootnodes are used unless --bootnode is given)
|
|
#
|
|
# Options:
|
|
# --bootnode <enode> Override the published bootnodes with your own peer for discovery.
|
|
# --data-path <dir> Node data directory. Default: ./aere-data next to this script.
|
|
# --build-dir <dir> Where upstream Besu is cloned and built. Default: ./.besu-build next to this script.
|
|
# --p2p-port <n> P2P (TCP+UDP) port. Default: 30303.
|
|
# --rpc-port <n> JSON-RPC HTTP port. Default: 8545.
|
|
# --sync-min-peers <n> Minimum peers before full sync starts. Default: Besu's default (5). Lower it
|
|
# (e.g. 1) on a young network where few public peers are reachable.
|
|
# --rebuild Force a rebuild even if a built node is already present.
|
|
# --no-start Do everything up to and including the build, then stop. Does not start the node.
|
|
# -h | --help Print this help and exit.
|
|
#
|
|
# Bootnode: chain 2800 does not publish a public bootnode in this repository yet (publishing a node's
|
|
# p2p address is an infrastructure decision, and validator addresses are deliberately not printed).
|
|
# Until a dedicated public bootnode exists, request a current enode by mail (office@aere.network,
|
|
# subject "bootnode") and pass it with --bootnode. Everything except peer discovery works without one.
|
|
#
|
|
set -euo pipefail
|
|
|
|
# --- constants ---------------------------------------------------------------------------------
|
|
BESU_UPSTREAM_URL="https://github.com/hyperledger/besu.git"
|
|
BASE_COMMIT="d2032017bb3b8cb215a97303980a1e4a643f7180" # Besu develop line after 26.4.0; see anchor/BASE.txt
|
|
GENESIS_SHA256="361709dccec4e9fc85be5aec33b30b58a55dc53424087d84c1ad11bd1d944c24"
|
|
NETWORK_ID=2800
|
|
# Patches applied in this order. 0002 (testnet precompiles) is deliberately NOT applied on mainnet.
|
|
PATCHES=(
|
|
"0001-aere-pqc-precompiles-mainnet.patch"
|
|
"0003-aere-pq-anchor.patch"
|
|
"0004-aere-basefee-floor.patch"
|
|
"0005-aere-eip2935-futureeips.patch"
|
|
)
|
|
|
|
# --- locate self -------------------------------------------------------------------------------
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# --- defaults ----------------------------------------------------------------------------------
|
|
BOOTNODE=""
|
|
DATA_PATH="$SCRIPT_DIR/aere-data"
|
|
BUILD_DIR="$SCRIPT_DIR/.besu-build"
|
|
P2P_PORT=30303
|
|
RPC_PORT=8545
|
|
REBUILD=0
|
|
NO_START=0
|
|
SYNC_MIN_PEERS="" # empty = Besu default (5); lower it on a young network with few reachable peers
|
|
|
|
# --- helpers -----------------------------------------------------------------------------------
|
|
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
|
|
ok() { printf ' \033[1;32mOK\033[0m %s\n' "$*"; }
|
|
die() { printf '\n\033[1;31mFATAL: %s\033[0m\n' "$*" >&2; exit 1; }
|
|
note() { printf ' %s\n' "$*"; }
|
|
|
|
usage() { awk 'NR>=2 && /^#/{sub(/^# ?/,"");print;next} NR>=2{exit}' "${BASH_SOURCE[0]}"; exit 0; }
|
|
|
|
sha256_of() { # portable: prefer sha256sum, fall back to shasum
|
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
|
|
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
|
|
else die "no sha256sum or shasum on PATH; cannot verify file integrity"; fi
|
|
}
|
|
|
|
# --- parse args --------------------------------------------------------------------------------
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--bootnode) BOOTNODE="${2:-}"; shift 2 ;;
|
|
--data-path) DATA_PATH="${2:-}"; shift 2 ;;
|
|
--build-dir) BUILD_DIR="${2:-}"; shift 2 ;;
|
|
--p2p-port) P2P_PORT="${2:-}"; shift 2 ;;
|
|
--rpc-port) RPC_PORT="${2:-}"; shift 2 ;;
|
|
--sync-min-peers) SYNC_MIN_PEERS="${2:-}"; shift 2 ;;
|
|
--rebuild) REBUILD=1; shift ;;
|
|
--no-start) NO_START=1; shift ;;
|
|
-h|--help) usage ;;
|
|
*) die "unknown argument: $1 (try --help)" ;;
|
|
esac
|
|
done
|
|
|
|
say "Aere Network chain 2800 node setup"
|
|
note "bundle: $SCRIPT_DIR"
|
|
note "build dir: $BUILD_DIR"
|
|
note "data path: $DATA_PATH"
|
|
|
|
# --- 1. preconditions --------------------------------------------------------------------------
|
|
say "1/6 Checking preconditions"
|
|
|
|
command -v git >/dev/null 2>&1 || die "git not found on PATH"
|
|
ok "git: $(git --version | awk '{print $3}')"
|
|
|
|
command -v java >/dev/null 2>&1 || die "java not found on PATH; JDK 21 is required"
|
|
JAVA_VER_LINE="$(java -version 2>&1 | head -1)"
|
|
JAVA_MAJOR="$(printf '%s' "$JAVA_VER_LINE" | sed -E 's/.*version "([0-9]+).*/\1/')"
|
|
[ "$JAVA_MAJOR" = "21" ] || die "JDK 21 is required, found: $JAVA_VER_LINE"
|
|
ok "java: $JAVA_VER_LINE"
|
|
|
|
# Disk: a full sync from genesis to the live tip stores tens of GB (headers are ~95% of it),
|
|
# plus a few GB for the build. Warn (do not hard-fail) below ~40 GB free on the build filesystem.
|
|
FREE_KB="$(df -Pk "$SCRIPT_DIR" | awk 'NR==2{print $4}')"
|
|
FREE_GB=$(( FREE_KB / 1024 / 1024 ))
|
|
if [ "$FREE_GB" -lt 40 ]; then
|
|
note "WARNING: only ${FREE_GB} GB free here. A full from-genesis sync needs tens of GB; the run may fill the disk."
|
|
else
|
|
ok "disk: ${FREE_GB} GB free"
|
|
fi
|
|
|
|
# --- 2. verify genesis -------------------------------------------------------------------------
|
|
say "2/6 Verifying genesis.json"
|
|
[ -f "$SCRIPT_DIR/genesis.json" ] || die "genesis.json missing from bundle"
|
|
GOT="$(sha256_of "$SCRIPT_DIR/genesis.json")"
|
|
[ "$GOT" = "$GENESIS_SHA256" ] || die "genesis.json sha256 mismatch
|
|
expected $GENESIS_SHA256
|
|
got $GOT
|
|
This is not the chain-2800 genesis (a CRLF checkout will also fail here). Refusing to run."
|
|
ok "genesis.json sha256 $GOT"
|
|
|
|
# --- 3. verify registries ----------------------------------------------------------------------
|
|
say "3/6 Verifying Falcon validator registries"
|
|
REG_DIR="$SCRIPT_DIR/registries"
|
|
[ -d "$REG_DIR" ] || die "registries/ directory missing. A node cannot validate the anchor past block 13,014,000 without it."
|
|
for f in manifest-13014000.json manifest-13600000.json registru-2800-v2-13600000.properties; do
|
|
[ -f "$REG_DIR/$f" ] || die "registries/$f missing"
|
|
done
|
|
if [ -f "$REG_DIR/SHA256SUMS" ]; then
|
|
( cd "$REG_DIR" && \
|
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum -c SHA256SUMS >/dev/null
|
|
else shasum -a 256 -c SHA256SUMS >/dev/null; fi ) \
|
|
|| die "registries failed their SHA256SUMS check. Refusing to run with tampered registries."
|
|
ok "registries verified against SHA256SUMS (7 keys @ 13,014,000; 9 keys @ 13,600,000)"
|
|
else
|
|
note "WARNING: registries/SHA256SUMS not found; skipping registry integrity check"
|
|
fi
|
|
|
|
# --- 4. fetch upstream Besu at the named base + apply patches -----------------------------------
|
|
say "4/6 Fetching upstream Besu at $BASE_COMMIT and applying Aere patches"
|
|
# sanity: the short base in anchor/BASE.txt must be a prefix of BASE_COMMIT
|
|
if [ -f "$SCRIPT_DIR/anchor/BASE.txt" ]; then
|
|
SHORT="$(grep -oE 'd[0-9a-f]{6,}' "$SCRIPT_DIR/anchor/BASE.txt" | head -1 || true)"
|
|
case "$BASE_COMMIT" in "$SHORT"*) : ;; *) [ -z "$SHORT" ] || die "BASE_COMMIT does not match anchor/BASE.txt ($SHORT)";; esac
|
|
fi
|
|
|
|
if [ ! -d "$BUILD_DIR/.git" ]; then
|
|
note "cloning upstream Besu (blob:none partial clone; this fetches ~the working tree, not full history)"
|
|
git clone --filter=blob:none "$BESU_UPSTREAM_URL" "$BUILD_DIR"
|
|
git -C "$BUILD_DIR" checkout "$BASE_COMMIT"
|
|
else
|
|
ok "upstream Besu clone already present"
|
|
fi
|
|
|
|
HEAD_NOW="$(git -C "$BUILD_DIR" rev-parse HEAD)"
|
|
[ "$HEAD_NOW" = "$BASE_COMMIT" ] || die "Besu build dir is not at the named base commit
|
|
expected $BASE_COMMIT
|
|
got $HEAD_NOW
|
|
Delete $BUILD_DIR and re-run to get a pristine base."
|
|
ok "at base commit $BASE_COMMIT"
|
|
|
|
apply_patch() {
|
|
local patch="$SCRIPT_DIR/patches/$1"
|
|
[ -f "$patch" ] || die "patch not found: $patch"
|
|
# already applied? (reverse-apply check succeeds only if the change is present)
|
|
if git -C "$BUILD_DIR" apply --reverse --check "$patch" >/dev/null 2>&1; then
|
|
ok "already applied: $1"
|
|
return 0
|
|
fi
|
|
# will it apply cleanly? fail loudly if the base moved under it.
|
|
git -C "$BUILD_DIR" apply --check "$patch" \
|
|
|| die "patch does not apply cleanly on this base: $1
|
|
This means the Besu tree is not the pristine named base. Delete $BUILD_DIR and re-run."
|
|
git -C "$BUILD_DIR" apply "$patch"
|
|
ok "applied: $1"
|
|
}
|
|
for p in "${PATCHES[@]}"; do apply_patch "$p"; done
|
|
|
|
# --- 5. build ----------------------------------------------------------------------------------
|
|
say "5/6 Building the node (gradlew installDist)"
|
|
BESU_BIN="$BUILD_DIR/build/install/besu/bin/besu"
|
|
if [ -x "$BESU_BIN" ] && [ "$REBUILD" -eq 0 ]; then
|
|
ok "built node already present: $BESU_BIN (use --rebuild to force)"
|
|
else
|
|
note "this compiles Besu from source and can take many minutes on the first run"
|
|
( cd "$BUILD_DIR" && ./gradlew --no-daemon installDist -x spotlessJavaCheck -x test )
|
|
[ -x "$BESU_BIN" ] || die "build finished but $BESU_BIN is missing"
|
|
ok "built: $BESU_BIN"
|
|
fi
|
|
|
|
if [ "$NO_START" -eq 1 ]; then
|
|
say "Stopping before start (--no-start). The node is built at:"
|
|
note "$BESU_BIN"
|
|
exit 0
|
|
fi
|
|
|
|
# --- 6. start ----------------------------------------------------------------------------------
|
|
say "6/6 Starting the node"
|
|
if [ -z "$BOOTNODE" ]; then
|
|
# The two published bootnodes (RUN-A-NODE.md, bootnodes section): both public read hosts,
|
|
# neither in the validator set, one Besu and one Nethermind. --bootnode overrides them.
|
|
BOOTNODE="enode://7e8ff740b79bf28a6d7e46ea8d2317cea4a223e7af60be36e91e13dc7051f4cb29077631452e699a5e18e640a94fd6e370e9e51dd91ecba1f2ba9666e07ff01b@37.27.216.110:30303,enode://a4b90d4f5fc7758814d41acfe00f6a2516683f3d3052410091b894d014dbb2184d6158da76f72e84ca268c2ccde3590d5686f978868f3b6ad6af87e398179fb8@157.180.67.185:30303"
|
|
say " no --bootnode given; using the two published bootnodes from RUN-A-NODE.md"
|
|
fi
|
|
|
|
mkdir -p "$DATA_PATH"
|
|
|
|
# BESU_OPTS: the follower subset of chain 2800's post-quantum configuration.
|
|
#
|
|
# WHY THESE AND NOT THE VALIDATOR SET. A validator additionally sets, and a follower deliberately
|
|
# OMITS: aere.falcon.key (the signing key, not in this repo), aere.falcon.attachBlock /
|
|
# attachInterval / minAttachFutureMargin / validatorCount (these drive seal PRODUCTION only), and
|
|
# aere.falcon.forkBlock (a per-block Falcon rule that the shipped code retires at the anchor block,
|
|
# so it is inert; leaving it unset keeps the non-blocking baseline). What is below is exactly what
|
|
# the IMPORT (validation) path reads, verified against anchor/ source:
|
|
# - registries: aere.falcon.manifest is the head registry; it loads only WITH
|
|
# aere.falcon.anchor.address (the manifest source is pending until that on-chain anchor is
|
|
# observed), so anchor.address / anchor.block are import-relevant here, not attach knobs.
|
|
# aere.falcon.registry.history carries every earlier registry the chain was signed against.
|
|
# - anchor validation: aere.pq.anchorBlock / anchorInterval / anchorMinSeals / anchor.maxSeals / chainId.
|
|
# - the 1 Gwei base-fee floor active from block 10,141,734: aere.basefee.floor.forkBlock / value.
|
|
# A node syncing from block 0 starts into the GENESIS validator set, which is small (N=3). The
|
|
# startup threshold guard (PqAnchorThresholdGuard) refuses to arm a seal threshold at or above that
|
|
# set's QBFT quorum, because a proposer at that set size could never gather it; the schedule reaches
|
|
# K=3 (block 13,034,000) and K=6 (block 14,961,456), both unreachable at N=3, so a from-genesis node
|
|
# refuses to start (AERE-PQC-THRESHOLD-01) without the line below. The emergency ceiling lowers the
|
|
# EFFECTIVE threshold to what the genesis set supports (quorum(3) - 1 = 1) WITHOUT editing the
|
|
# published schedule, exactly as that guard's own message instructs (--Xaere-pq-anchor-min-seals-max).
|
|
# This does not weaken the post-quantum binding a follower checks: every anchor must still carry a
|
|
# valid validator Falcon seal that verifies against the registry, and its digest must still bind the
|
|
# certificate under the block hash; only the seal-COUNT floor (a proposer-liveness property relative
|
|
# to the current set, not a history-verification property) is relaxed to one. Real anchor blocks
|
|
# carry three to nine seals, so all import cleanly. A validator, which starts into the full set and
|
|
# proposes, does not set this.
|
|
ANCHOR_MIN_SEALS_CEILING="${AERE_ANCHOR_MIN_SEALS_CEILING:-1}"
|
|
REG="$REG_DIR"
|
|
# >>> OPTIUNI DERIVATE DIN LANTUL VIU (aduce-optiunile-urmaritorului.sh) - nu se scriu de mana
|
|
export BESU_OPTS="\
|
|
-Daere.basefee.floor.forkBlock=10141734 \
|
|
-Daere.basefee.floor.value=1000000000 \
|
|
-Daere.falcon.anchor.address=0xC01Bb2843EFAF92D8389F14aC1dD26dbeAf9144C \
|
|
-Daere.falcon.anchor.block=13889290 \
|
|
-Daere.falcon.attachBlock=13889296 \
|
|
-Daere.falcon.attachInterval=32 \
|
|
-Daere.falcon.forkBlock=14050000 \
|
|
-Daere.falcon.manifest=$REG/manifest-18082816.json \
|
|
-Daere.falcon.minAttachFutureMargin=5 \
|
|
-Daere.falcon.registry.history=$REG/manifest-13014000.json,$REG/registru-2800-v2-13600000.properties \
|
|
-Daere.falcon.validatorCount=10 \
|
|
-Daere.pq.anchorBlock=13014000 \
|
|
-Daere.pq.anchorInterval=32 \
|
|
-Daere.pq.anchorIntervalSchedule=17225968:128 \
|
|
-Daere.pq.anchor.maxSeals=9 \
|
|
-Daere.pq.anchorMinSeals=13014000:0,13034000:3,14961456:6,17102384:0,17102416:6 \
|
|
-Daere.pq.anchorV2Block=17047600 \
|
|
-Daere.pq.chainId=2800 \
|
|
-Daere.pq.hybridRegistry=$REG/hibrid-10.properties \
|
|
-Daere.pq.schemeSchedule=17047568:falcon-512+slh-dsa-sha2-128s \
|
|
-Daere.pq.anchor.minSealsCeiling=$ANCHOR_MIN_SEALS_CEILING"
|
|
# <<< OPTIUNI DERIVATE
|
|
|
|
note "BESU_OPTS (follower subset):"
|
|
printf '%s\n' "$BESU_OPTS" | tr ' ' '\n' | sed 's/^/ /'
|
|
note ""
|
|
note "bootnode: $BOOTNODE"
|
|
note "data path: $DATA_PATH"
|
|
note "rpc: http://127.0.0.1:$RPC_PORT (eth_blockNumber to watch height)"
|
|
note ""
|
|
note "Starting FULL sync from block 0. This verifies history rather than trusting a peer for state."
|
|
note "Crossing block 13,014,000 (first anchor) and 13,034,000 (first enforced 3-seal threshold)"
|
|
note "without anchor rejections is the proof that the registries and patches are correct."
|
|
|
|
SYNC_MIN_PEERS_ARG=()
|
|
if [ -n "$SYNC_MIN_PEERS" ]; then
|
|
SYNC_MIN_PEERS_ARG=(--sync-min-peers="$SYNC_MIN_PEERS")
|
|
note "sync-min-peers: $SYNC_MIN_PEERS (Besu default is 5; lower this only on a young network with few reachable peers)"
|
|
fi
|
|
|
|
exec "$BESU_BIN" \
|
|
--genesis-file="$SCRIPT_DIR/genesis.json" \
|
|
--data-path="$DATA_PATH" \
|
|
--network-id="$NETWORK_ID" \
|
|
--sync-mode=FULL \
|
|
--data-storage-format=BONSAI \
|
|
--bootnodes="$BOOTNODE" \
|
|
--p2p-port="$P2P_PORT" \
|
|
"${SYNC_MIN_PEERS_ARG[@]}" \
|
|
--rpc-http-enabled --rpc-http-port="$RPC_PORT" \
|
|
--rpc-http-api=ETH,NET,WEB3,QBFT \
|
|
--min-gas-price=0
|