Merge pull request 'deploy: node from a chain spec file with genesis assertion, bootnodes as reserved peers, raised peer limits' (#24) from launch/chainspec-deploy into main
deploy / fetch (push) Successful in 16s
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal quadbrat.hanzalova.internal, --public-addr /dns4/nh.thgttg.com/tcp/30333, --unsafe-rpc-external --rpc-methods safe --rpc-c… (push) Successful in 56s
deploy / deploy-metrics (push) Successful in 49s
deploy / fetch (push) Successful in 16s
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal quadbrat.hanzalova.internal, --public-addr /dns4/nh.thgttg.com/tcp/30333, --unsafe-rpc-external --rpc-methods safe --rpc-c… (push) Successful in 56s
deploy / deploy-metrics (push) Successful in 49s
This commit was merged in pull request #24.
This commit is contained in:
@@ -38,6 +38,15 @@ on:
|
||||
node_version:
|
||||
description: quantus-node version (overrides the pin)
|
||||
required: false
|
||||
chainspec_url:
|
||||
description: "chain spec JSON to run from (overrides CHAINSPEC_URL); empty = the built-in CHAIN name"
|
||||
required: false
|
||||
expected_genesis:
|
||||
description: "0x genesis hash the started node must report (overrides EXPECTED_GENESIS); required with a chain spec"
|
||||
required: false
|
||||
bootnodes:
|
||||
description: "space-separated multiaddrs to dial and hold as reserved peers (overrides BOOTNODES)"
|
||||
required: false
|
||||
|
||||
concurrency: # never half-apply two deploys at once
|
||||
group: deploy
|
||||
@@ -52,6 +61,21 @@ env:
|
||||
# Pinned; bump deliberately to upgrade. An unattended upgrade of a validator
|
||||
# is how you find out at 3am that a release changed a consensus rule.
|
||||
NODE_VERSION: "0.11.1"
|
||||
# Mainnet launch (2026-09-10 09:09 UTC): the chain spec is published as a
|
||||
# file before a binary embeds it, so the node can run from a JSON at
|
||||
# /etc/quantus-node/chainspec.json instead of a built-in name. When set, the
|
||||
# spec's `id` (not CHAIN above) names <base-path>/chains/<id>/, which is
|
||||
# where quantus/miner's deploy reads the miner credentials from — pass it
|
||||
# there as the `chain` input. EXPECTED_GENESIS is asserted against the
|
||||
# started node's block-0 hash, which is the whole defence against running
|
||||
# a look-alike spec. BOOTNODES are dialled and held as reserved peers:
|
||||
# launch is a propagation race, and a direct link to the foundation's nodes
|
||||
# is the one latency knob we have (quantus/chain#1). Dispatch inputs
|
||||
# override each of these for the cutover; commit the values afterwards so
|
||||
# push-triggered deploys keep them.
|
||||
CHAINSPEC_URL: ""
|
||||
EXPECTED_GENESIS: ""
|
||||
BOOTNODES: ""
|
||||
# The miner is deployed by quantus/miner's own workflow, following its main
|
||||
# branch (quantus/miner#8, lair/quantus#4). This repo no longer ships it;
|
||||
# SCRAPE_MINERS below must still agree with that repo's deploy matrix.
|
||||
@@ -87,6 +111,10 @@ jobs:
|
||||
runs-on: infra
|
||||
outputs:
|
||||
node_version: ${{ steps.v.outputs.node }}
|
||||
chainspec_url: ${{ steps.v.outputs.chainspec_url }}
|
||||
expected_genesis: ${{ steps.v.outputs.expected_genesis }}
|
||||
bootnodes: ${{ steps.v.outputs.bootnodes }}
|
||||
chain_id: ${{ steps.cs.outputs.chain_id }}
|
||||
steps:
|
||||
- id: v
|
||||
run: |
|
||||
@@ -94,6 +122,46 @@ jobs:
|
||||
n="${{ github.event.inputs.node_version || env.NODE_VERSION }}"
|
||||
echo "node=${n}" >> "$GITHUB_OUTPUT"
|
||||
echo "quantus-node ${n}"
|
||||
cs="${{ github.event.inputs.chainspec_url || env.CHAINSPEC_URL }}"
|
||||
eg="${{ github.event.inputs.expected_genesis || env.EXPECTED_GENESIS }}"
|
||||
bn="${{ github.event.inputs.bootnodes || env.BOOTNODES }}"
|
||||
if [ -n "$cs" ] && [ -z "$eg" ]; then
|
||||
echo "a chain spec needs expected_genesis: without it a look-alike spec would pass validation" >&2
|
||||
exit 1
|
||||
fi
|
||||
case "$eg" in ""|0x[0-9a-f]*) ;; *) echo "expected_genesis must be a 0x hex hash" >&2; exit 1 ;; esac
|
||||
for b in $bn; do
|
||||
case "$b" in /*/p2p/*) ;; *) echo "bootnode '$b' is not a multiaddr ending in /p2p/<peer id>" >&2; exit 1 ;; esac
|
||||
done
|
||||
echo "chainspec_url=${cs}" >> "$GITHUB_OUTPUT"
|
||||
echo "expected_genesis=${eg}" >> "$GITHUB_OUTPUT"
|
||||
echo "bootnodes=${bn}" >> "$GITHUB_OUTPUT"
|
||||
echo "chain spec: ${cs:-built-in ${{ env.CHAIN }}}; expected genesis: ${eg:-not asserted}; bootnodes: ${bn:-none}"
|
||||
|
||||
- name: download chain spec
|
||||
id: cs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cs="${{ steps.v.outputs.chainspec_url }}"
|
||||
if [ -z "$cs" ]; then
|
||||
echo "chain_id=${{ env.CHAIN }}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
mkdir -p _bin
|
||||
curl -fSL --retry 5 --retry-delay 5 --retry-all-errors --connect-timeout 15 \
|
||||
-o _bin/chainspec.json "$cs"
|
||||
# The spec must parse, and its id is the directory the node keeps
|
||||
# this chain's state and miner credentials under.
|
||||
id=$(python3 -c "
|
||||
import json, sys
|
||||
d = json.load(open('_bin/chainspec.json'))
|
||||
for k in ('id', 'name', 'chainType'):
|
||||
assert k in d, f'chain spec has no {k}'
|
||||
print(f\"name={d['name']} id={d['id']} type={d['chainType']} bootNodes={len(d.get('bootNodes') or [])} telemetry={d.get('telemetryEndpoints')}\", file=sys.stderr)
|
||||
print(d['id'])")
|
||||
echo "chain_id=${id}" >> "$GITHUB_OUTPUT"
|
||||
sha256sum _bin/chainspec.json
|
||||
{ echo "chain spec: $cs"; echo "id: $id"; echo "sha256: $(sha256sum _bin/chainspec.json | cut -d' ' -f1)"; } >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: download quantus-node
|
||||
run: |
|
||||
@@ -221,7 +289,8 @@ jobs:
|
||||
env:
|
||||
QUANTUS_INNER_HASH: ${{ matrix.inner_hash }}
|
||||
QUANTUS_PUBLIC_ADDR: ${{ matrix.public_addr }}
|
||||
QUANTUS_CHAIN: ${{ env.CHAIN }}
|
||||
QUANTUS_CHAIN: ${{ needs.fetch.outputs.chainspec_url != '' && '/etc/quantus-node/chainspec.json' || env.CHAIN }}
|
||||
QUANTUS_BOOTNODES: ${{ needs.fetch.outputs.bootnodes }}
|
||||
QUANTUS_RPC_EXPOSE: ${{ matrix.rpc_expose }}
|
||||
QUANTUS_TELEMETRY_URL: ${{ env.TELEMETRY_URL }}
|
||||
run: |
|
||||
@@ -248,6 +317,10 @@ jobs:
|
||||
out = out.replace("{{QUANTUS_PUBLIC_ADDR}}", os.environ.get("QUANTUS_PUBLIC_ADDR", ""))
|
||||
out = out.replace("{{QUANTUS_CHAIN}}", os.environ["QUANTUS_CHAIN"])
|
||||
out = out.replace("{{QUANTUS_RPC_EXPOSE}}", os.environ.get("QUANTUS_RPC_EXPOSE", ""))
|
||||
# Each bootnode is dialled AND held as a reserved peer (not
|
||||
# --reserved-only: the rest of the peer set still fills normally).
|
||||
flags = " ".join(f"--bootnodes {b} --reserved-nodes {b}" for b in os.environ.get("QUANTUS_BOOTNODES", "").split())
|
||||
out = out.replace("{{QUANTUS_BOOTNODES}}", flags)
|
||||
pathlib.Path("node.env").write_text(out)
|
||||
a = pathlib.Path("asset/config/arena.env.tmpl").read_text()
|
||||
a = a.replace("{{QUANTUS_INNER_HASH}}", os.environ["QUANTUS_INNER_HASH"])
|
||||
@@ -301,6 +374,13 @@ jobs:
|
||||
gitea_ci@"$HOST":/etc/systemd/system/quantus-node.service
|
||||
push --chown=root:quantus-node --chmod=F0640 node.env \
|
||||
gitea_ci@"$HOST":/etc/quantus-node/node.env
|
||||
# 3a. Chain spec file, when running from one. Left in place otherwise:
|
||||
# node.env decides what --chain points at, and an unreferenced
|
||||
# file is harmless while deleting it would need one more grant.
|
||||
if [ -s _bin/chainspec.json ]; then
|
||||
push --chown=root:quantus-node --chmod=F0640 _bin/chainspec.json \
|
||||
gitea_ci@"$HOST":/etc/quantus-node/chainspec.json
|
||||
fi
|
||||
pushfw --mkpath --chmod=F0644 asset/firewalld/quantus-node.xml \
|
||||
gitea_ci@"$HOST":/etc/firewalld/services/quantus-node.xml
|
||||
pushfw --mkpath --chmod=F0644 asset/firewalld/quantus-node-miner.xml \
|
||||
@@ -517,6 +597,32 @@ jobs:
|
||||
fail=1
|
||||
fi
|
||||
|
||||
echo "--- chain ---"
|
||||
# Which chain the node actually started, from its own RPC on loopback.
|
||||
# The genesis hash is the identity of the chain; asserting it is what
|
||||
# stops a look-alike spec (or a stale built-in name) from mining the
|
||||
# wrong network unnoticed.
|
||||
rpc() { run "curl -sS -m 10 -H content-type:application/json -d '{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"$1\",\"params\":$2}' http://127.0.0.1:9944" \
|
||||
| python3 -c 'import sys, json; print(json.load(sys.stdin).get("result", ""))'; }
|
||||
chain_name=$(rpc system_chain '[]' || true)
|
||||
genesis=$(rpc chain_getBlockHash '[0]' || true)
|
||||
echo " chain: ${chain_name:-?} genesis: ${genesis:-?} id: ${{ needs.fetch.outputs.chain_id }}"
|
||||
if [ -z "$genesis" ]; then
|
||||
echo " FAIL node RPC did not answer chain_getBlockHash" >&2; fail=1
|
||||
elif [ -n "${{ needs.fetch.outputs.expected_genesis }}" ] && [ "$genesis" != "${{ needs.fetch.outputs.expected_genesis }}" ]; then
|
||||
echo " FAIL genesis ${genesis} is not the expected ${{ needs.fetch.outputs.expected_genesis }} — WRONG CHAIN" >&2; fail=1
|
||||
elif [ -n "${{ needs.fetch.outputs.expected_genesis }}" ]; then
|
||||
echo " genesis matches expected"
|
||||
fi
|
||||
# The miner credential directory follows the spec id; quantus/miner's
|
||||
# deploy must be pointed at exactly this name.
|
||||
if run "sudo cat /var/lib/quantus-node/chains/${{ needs.fetch.outputs.chain_id }}/miner-auth-token" | grep -q .; then
|
||||
echo " miner credentials present under chains/${{ needs.fetch.outputs.chain_id }}/ (use chain=${{ needs.fetch.outputs.chain_id }} on the miner deploy)"
|
||||
else
|
||||
echo " FAIL no miner credentials under chains/${{ needs.fetch.outputs.chain_id }}/ — the chain id the miner deploy needs is not this" >&2; fail=1
|
||||
fi
|
||||
{ echo "chain: ${chain_name:-?}"; echo "genesis: ${genesis:-?}"; echo "chain id for the miner deploy: ${{ needs.fetch.outputs.chain_id }}"; } >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "--- listeners ---"
|
||||
# P2P must be listening; the miner link must be listening or the node
|
||||
# is authoring nothing (--miner-listen-port disables local mining).
|
||||
|
||||
@@ -21,6 +21,11 @@ QUANTUS_PUBLIC_ADDR={{QUANTUS_PUBLIC_ADDR}}
|
||||
# (<base-path>/chains/<chain>/) follows from the same value.
|
||||
QUANTUS_CHAIN={{QUANTUS_CHAIN}}
|
||||
|
||||
# Bootnodes, rendered as complete flags: each address is dialled (--bootnodes)
|
||||
# and held as a reserved peer (--reserved-nodes) so the connection to the
|
||||
# foundation's nodes is never evicted. Empty = the spec's own bootNodes only.
|
||||
QUANTUS_BOOTNODES={{QUANTUS_BOOTNODES}}
|
||||
|
||||
# JSON-RPC exposure. Empty = loopback only. A complete flag string opens it:
|
||||
# --rpc-external --rpc-methods safe --rpc-cors all --rpc-rate-limit 300 \
|
||||
# --rpc-rate-limit-whitelisted-ips 127.0.0.1/32
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Quantus Planck node, validator role, external-miner mode.
|
||||
# Quantus node, validator role, external-miner mode.
|
||||
#
|
||||
# Hardened per ~/git/architecture/generic.md §8. Two knobs are deliberately
|
||||
# relaxed; both are load-bearing, do not "tidy" them back:
|
||||
@@ -39,7 +39,7 @@
|
||||
# gets packets in but never tells anyone to send them.
|
||||
|
||||
[Unit]
|
||||
Description=Quantus node (Planck, validator, external-miner mode)
|
||||
Description=Quantus node (validator, external-miner mode)
|
||||
Documentation=https://github.com/Quantus-Network/chain
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
@@ -62,7 +62,10 @@ ExecStart=/usr/local/bin/quantus-node \
|
||||
--miner-listen-port 9833 \
|
||||
--port 30333 \
|
||||
$QUANTUS_PUBLIC_ADDR \
|
||||
$QUANTUS_BOOTNODES \
|
||||
$QUANTUS_RPC_EXPOSE \
|
||||
--out-peers 32 \
|
||||
--in-peers 64 \
|
||||
--prometheus-port 9615 \
|
||||
--prometheus-external \
|
||||
--max-blocks-per-request 64 \
|
||||
|
||||
@@ -162,6 +162,7 @@ gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /usr/local/bin/quantus-node
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/systemd/system/quantus-node.service
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/sysusers.d/quantus-node.conf
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/quantus-node/node.env
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/quantus-node/chainspec.json
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/firewalld/services/quantus-node.xml
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/firewalld/services/quantus-node-miner.xml
|
||||
gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/firewalld/services/quantus-node-metrics.xml
|
||||
|
||||
Reference in New Issue
Block a user