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.tomlflag and a minimal[miner]TOML schema, with precedence CLI > TOML > defaults. Onlyvalidators,signer_key,faucet_urlare wired through today; other keys are parsed-but-ignored.v0.2-previewDocker image tag on thev0.2branch so downstream integration testers candocker pullthe in-progress build without touching:latest.- Rewrite of the four example TOMLs to v0.2 schema (rename
quip-node.example.toml→quip-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_url → validators: 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 |
drop — quip-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)
_runfailover race (review P2 #12 (closed)): a second concurrent caller can hitiface = Nonemid-reconnect → spuriousAttributeErrormasks the real failover. TheValidatorPool.advance_rotationcoordination reduces but doesn't fully close the in-client race; a proper fix needs lock-semantics changes across_run,_raw_run, andreconnect().- Faucet test coverage (review P2 #13 (closed)): success path and
wallet-faucet-failedpath are not covered by unit tests; a faucet integration harness is a separate workstream. - Test edge cases (review P3 #19): concurrent
_runrace,TimeoutErroroutsideConnectionErrorhierarchy, 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-previewimage build on merge tov0.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 withwallet-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 withvalidators-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 withwallet-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.tomlhappy-path with TOML-only configuration