9 Commits
Author SHA1 Message Date
Rob ThijssenandClaude Fable 5.1 6276a0bf98 deploy: run the node from a chain spec file with genesis assertion, bootnodes as reserved peers, raised peer limits
Mainnet launches 2026-09-10 09:09 UTC with the chain spec published as a
file before any binary embeds it. The node deploy can now run from
/etc/quantus-node/chainspec.json (CHAINSPEC_URL or the chainspec_url
input), asserts the started node's block-0 hash against EXPECTED_GENESIS
(required with a spec: it is the only defence against a look-alike), and
prints the spec id, which names <base-path>/chains/<id>/ and is what the
miner deploy must be pointed at; validate checks the miner credentials
exist under it. BOOTNODES are dialled and held as reserved peers.

The unit raises --out-peers to 32 and --in-peers to 64: bob sits at the
default cap of 40 peers today, and launch is a propagation race
(quantus/chain#1). infra-setup grants the runner the chain spec path
(applied to bob 2026-09-09).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-09 08:05:00 +03:00
Rob Thijssen aa506b963c feat: optionally expose the JSON-RPC to the site LAN
deploy / fetch (push) Successful in 17s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Has been skipped
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal quadbrat.hanzalova.internal, --public-addr /dns4/nh.thgttg.com/tcp/30333, --rpc-external --rpc-methods safe --rpc-cors all… (push) Failing after 31s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Has been skipped
deploy / deploy-metrics (push) Has been skipped
Substrate binds JSON-RPC to loopback and makes opening it deliberately
awkward, which is correct for a validator. rpc_expose in the node matrix
takes the complete flag string; empty keeps it on loopback.

--rpc-methods safe is the load-bearing flag. It permits every state read
and author_submitExtrinsic (signed, so no key exposure) while blocking
author_rotateKeys, system_addReservedPeer and the rest. --rpc-methods
auto happens to downgrade once listening externally, but relying on that
makes the safety depend on a flag nobody wrote down.

The firewalld rich rule is scoped to the node's own /16, derived from
its own resolved address at deploy time -- so no subnet literal enters
this repo and the rule follows the host between sites.

Loopback is whitelisted from rate limiting because the arena exporter
runs beside the node and makes ~4 calls every 2s, which is 120/min --
enough to throttle itself against a naive limit.

validate now asserts the bind matches intent in both directions: nothing
listening while rpc_expose is set is a failure, and loopback-only is
reported as configuration rather than silence.
2026-09-01 04:43:27 +03:00
Rob Thijssen 938941b887 feat: second miner on quadbrat, and make the chain configurable
One node fans the same job out to every connected miner
(node/src/miner_server.rs holds a HashMap of MinerHandle and
broadcast_job's to all of them) and each miner picks its own random
starting nonce, so a second miner needs no coordination and no second
node. quadbrat joins as the test of that: RTX 3060, idle, gitea_ci
already present.

Separately, --chain was hardcoded in the unit while the workflow already
carried CHAIN in env for the credential path -- so switching chains
would have meant editing a static asset. With mainnet expected
2026-09-09 that is the wrong shape: the chain now comes from config, and
the switch is one line plus the version pins.

readme gains a mainnet-readiness section listing what the deploy cannot
decide: a mainnet reward wallet, the unpublished chain spec name, disk
for a second chains/ directory, and the GPU contention question.
2026-08-31 19:12:37 +03:00
Rob Thijssen 6e931842dd feat: scrape node and miner metrics, and a grafana dashboard
Adds a deploy-metrics job shipping a Prometheus scrape drop-in and a
provisioned Grafana dashboard to the fleet metrics host, plus the
exposure work the exporters needed to be reachable at all.

The node's exporter was loopback-only, so --prometheus-external. The
miner's already bound 0.0.0.0 unconditionally (crates/metrics builds the
listener from [0,0,0,0], no bind option) -- I had recorded it as loopback
in the port registry, which was wrong and is corrected. Both exporters
are unauthenticated, so each gets a firewalld service opened by a rich
rule scoped to the scrape host; that rule, not the bind, is the access
boundary.

Dashboard panels are built on metric names read off the running node and
the miner's source, not guessed. It leads with the states that look
healthy but are not: peers at zero, hash rate at zero while the unit is
active, GPU devices at zero, jobs never arriving.

Two shared-infra changes are in infra-setup.sh rather than the workflow,
since other projects depend on both files: prometheus.yml gains
scrape_config_files so projects stop sharing one file, and
grafana.container gains a dashboards mount it did not have.
2026-08-31 18:45:41 +03:00
Rob Thijssen 3740bfb520 feat: advertise a public address, optionally
Opening 30333 on the host does not make a node dialable. Behind NAT
libp2p advertises the address it can see -- a 10.x mesh address -- so an
edge port forward delivers packets that no peer ever knows to send.
--public-addr is what closes that gap.

Plumbed as a per-host matrix field rather than a constant, since a
mesh-only node should not advertise anything. The unit takes it as a
bare $VAR so systemd word-splits it: a complete flag becomes two
arguments, empty becomes zero, and one unit serves both cases.

Validate now reports which state a node is in. An undialable validator
mines happily and reports active while contributing nothing back to the
network, which is exactly the class of silent failure the rest of the
validate step exists to catch.
2026-08-31 18:25:39 +03:00
Rob Thijssen 08d284287a fix: node needs AF_NETLINK, and validate needs honest pipes
deploy / fetch (push) Successful in 17s
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal) (push) Failing after 23s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Has been skipped
Three bugs from run 2.

The real one: RestrictAddressFamilies omitted AF_NETLINK. libp2p calls
getifaddrs(), which opens a NETLINK_ROUTE socket, so the node started,
reported active, logged

  failed to fetch network interfaces error=GetIfAddrsError("getifaddrs", -1)
  litep2p started with no listen addresses, cannot accept inbound connections

and never bound --port. A silently undialable peer. Verified on the live
host: adding AF_NETLINK binds 30333 on both stacks, removing it unbinds.
The validate listener check caught this correctly — it was not a false
positive.

Two in validate itself: ss was invoked with no -t/-u (the protocol was
captured into $2 and only ever echoed), which errors with RTNETLINK
answers: Invalid argument; and 'awk ... exit' / 'grep -q' on a pipe
SIGPIPEs the writer, which pipefail reports as 141 — that was the exit
code that killed the sync check. Both now capture and parse via
here-string. Also fixed the same latent pattern in the getent lookup.
2026-08-31 18:22:15 +03:00
Rob Thijssen 4a9b09f442 feat(deploy): update node service name to baba-gorchitsa
deploy / fetch (push) Successful in 17s
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal) (push) Failing after 21s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Has been skipped
This change updates the `--name` parameter in the quantus-node systemd
service
to use "baba-gorchitsa" instead of the hostname. This ensures a
consistent
node identifier across deployments.
2026-08-31 18:10:39 +03:00
Rob Thijssen e936597806 fix: the inner_hash is public config, not a secret
The node embeds the reward preimage verbatim in the PreRuntime digest of
every block it authors (client/consensus/qpow/src/lib.rs:726), and the
mining-rewards pallet reads that digest back to derive the payout
address on-chain. It is public from the first mined block.

So the Gitea secret, the 0640 handling and the ps-visibility caveat were
all theatre over a value published on a blockchain. It moves to the
workflow env: block with the rest of the infra truth, and
infra-setup.sh loses --mint-reward-key entirely along with its pass and
Gitea-API dependencies.

This also answers why the node takes a preimage and not an address: the
runtime does the derivation, which is what proves the payout target is a
canonical wormhole address rather than an arbitrary account. An address
would give it nothing to verify.
2026-08-31 17:37:31 +03:00
Rob Thijssen e3dfd73f2f feat: deploy a quantus planck node and external gpu miner
Node on bob, external GPU miner on benjy, split because
--miner-listen-port disables the node's built-in CPU mining.

The PoW is Poseidon2 over Goldilocks: register-resident, no memory
hardness, dominated by 64-bit modular multiplication. Measured on the
fleet, GPU beats CPU by ~200x (RTX 5090 368 MH/s @ 600W, RTX 4090
183 MH/s @ 450W, Ryzen 9 9950X3D 4.15 MH/s), so CPU mining is disabled
everywhere including on the GPU hosts.

The workflow deploys or validates. Validate mode asserts the hash
counter is advancing and the GPU is drawing power, because a miner that
enumerated no adapter and a node authoring nothing both look healthy to
systemctl is-active.

infra-setup.sh provisions the scoped gitea_ci accounts, generates the
node's libp2p key (the node refuses to start without one and it must
never be rotated by a deploy), and derives the reward preimage from a
seed phrase on stdin. It deliberately will not mint a fresh wallet: that
phrase is escalation-grade and fails agent-credentials.md §1.
2026-08-31 16:55:05 +03:00