v0.2: validators list + fail-fast startup guards

Status

Merge-ready — pipeline success, no conflicts, no blocking discussions. 5-source review run (pr-review-toolkit code-reviewer / silent-failure-hunter / pr-test-analyzer + codex + gemini); all P1/P2 findings resolved in commits a5ca0da..751e6f6, P3 nits and a known _run race documented as follow-up. See review summary comment.

Summary

Replaces the singular --node-url flag with a repeatable --validator URL list across every quip-miner subcommand, backed by a role-indexed ValidatorPool so the PoW controller, mempool controller, and bootstrap share one URL-rotation pointer. An operator who plugs the canonical nodes.quip.network validator set behind Caddy gets coordinated HA failover for free.

Adds machine-parseable fail-fast guards so misconfigured deployments never get past startup:

Code Trigger
wallet-not-configured keystore=<path> Keystore file missing
wallet-load-failed keystore=<path> error=<Exc> Keystore unreadable / wrong schema
validators-unreachable urls=<csv> reasons=<csv> Every URL in the rotation refused
wallet-underfunded ss58=<a> balance=<n> threshold=<n> Balance below threshold, no faucet wired
wallet-faucet-failed ss58=<a> balance=<n> threshold=<n> error=<Exc> Faucet configured but top-up failed

Single-line kebab-case codes so journalctl -u quip-miner | grep wallet- works for log triage.

Also bundled (small, related):

  • --config path.toml flag and a minimal [miner] TOML schema, with precedence CLI > TOML > defaults. Only validators, signer_key, faucet_url are wired through today; other keys are parsed-but-ignored.
  • v0.2-preview Docker image tag on the v0.2 branch so downstream integration testers can docker pull the in-progress build without touching :latest.
  • Rewrite of the four example TOMLs to v0.2 schema (rename quip-node.example.tomlquip-miner.example.toml).

What changed

Area Detail
shared/validator_pool.py (new) Role-indexed connection holder. Owns one URL-rotation pointer; callers ask for slots by role (rpc, subscribe.pow, subscribe.mempool). Failover is forward-only and idempotent under races via advance_rotation(from_url=...).
shared/substrate_client.py SubstrateClient(urls=[...]), NoValidatorReachable, circular reconnect(), _run wraps failover so a connection-loss exception triggers one reconnect attempt; optional pool= kwarg lets the client delegate rotation to the pool. Get-head methods converted to lambdas (defensive uniformity).
shared/substrate_miner_controller.py + mempool_miner_controller.py Both now take a ValidatorPool instead of constructing their own subscription clients. _subscribe_heads is a while-not-shutdown retry; _main_loop head/result handlers wrap _run calls in try/except so a failover-mid-call doesn't kill the controller.
shared/miner_config.py (new) load_miner_config, merge_config, validate_merged. CLI > TOML > defaults. Conditional tomllib (3.11+) / tomli (3.10) import.
shared/miner_bootstrap.py BootstrapConfig.node_urlvalidators: Tuple[str, ...]. Bootstrap builds its own one-shot ValidatorPool.
quip_cli.py --node-url hard-removed (no deprecation alias). --validator repeatable + --config on all 6 commands. Three guard helpers (_load_keystore_or_fail, _connect_or_fail, _ensure_funded_or_fail) applied across cpu/gpu/qpu + bootstrap + register-solver + deregister-solver. _run_concurrent_miner builds one shared pool for the concurrent path.
.gitlab-ci.yml .shared_image_rules anchor maps branch → MUTABLE_TAG (main→latest, v0.2→v0.2-preview); all four image-build jobs reference it.
*.example.toml Rewritten to v0.2 [miner] schema; quip-node.example.toml renamed to quip-miner.example.toml.

Hand-off note for nodes.quip.network

The two configs in that repo (data/config.cpu.toml, data/config.cuda.toml) already use validators = [...] plural, but they're on the v0.1 [global] schema. Migration map for consuming v0.2 quip-miner:

v0.1 field v0.2 destination
[global] validators = [...] [miner] validators = [...] (relocate)
[global] genesis_config drop — validator owns genesis
[global] node_name drop — no v0.2 equivalent
[global] secret replaced by [miner] signer_key = "/path/to/keystore.json" (generate via quip-miner keygen)
[global] auto_mine dropquip-miner cpu/gpu/qpu always mines
[global] rest_host, rest_port, rest_insecure_port, webroot drop from TOML; map only rest_port to CLI --rest-port N
[global] telemetry_* drop — telemetry is on whenever --rest-port > 0
[global] log_level, node_log, http_log replaced by CLI --log-level on the quip-miner group
[cpu], [gpu], [cuda.N], [qpu], [dwave] move under [miner.cpu] / [miner.gpu] / [miner.qpu] — schema reserved, not yet read; CLI flags still drive behavior

entrypoint.sh in that repo should call quip-miner cpu --config /data/config.toml (or gpu/qpu per profile) instead of quip-network-node --config ..., plus pass secret-generation off to quip-miner keygen.

Known follow-ups (not blocking this MR)

  • _run failover race (review P2 #12 (closed)): a second concurrent caller can hit iface = None mid-reconnect → spurious AttributeError masks the real failover. The ValidatorPool.advance_rotation coordination reduces but doesn't fully close the in-client race; a proper fix needs lock-semantics changes across _run, _raw_run, and reconnect().
  • Faucet test coverage (review P2 #13 (closed)): success path and wallet-faucet-failed path are not covered by unit tests; a faucet integration harness is a separate workstream.
  • Test edge cases (review P3 #19): concurrent _run race, TimeoutError outside ConnectionError hierarchy, shutdown-during- reconnect, empty-TOML round-trip, single-URL rotation.

Test plan

  • CI green on this branch (pipeline success)
  • Targeted suites pass: test_validator_pool (16), test_substrate_client_failover (18), test_miner_config (13) — 47/47 green
  • Push triggers :v0.2-preview image build on merge to v0.2
  • End-to-end smoke against a running dev chain:
    • quip-miner cpu --validator ws://127.0.0.1:9944 --signer-key /tmp/does-not-exist.json → exit 1 with wallet-not-configured keystore=/tmp/does-not-exist.json
    • quip-miner cpu --validator ws://127.0.0.1:1 --validator ws://127.0.0.1:2 --signer-key <real> → exit 1 with validators-unreachable urls=ws://127.0.0.1:1,ws://127.0.0.1:2 reasons=...
    • quip-miner cpu --validator ws://127.0.0.1:9944 --signer-key <fresh-zero-balance-keystore> → exit 1 with wallet-underfunded ss58=5G... balance=0 threshold=2000000000000
    • Same as above + --faucet-url http://... → faucet log line, then normal mining startup
    • Two-validator run, kill primary mid-mining → log "head subscription dropped …; failing over", continued operation on standby
  • quip-miner cpu --config ./quip.network.cpu.example.toml happy-path with TOML-only configuration
Edited by Richard T. Carback III

Merge request reports

Loading