Commit Graph
67 Commits
Author SHA1 Message Date
grenade 25a5ab6428 Merge pull request 'grafana: leaderboard author label falls back to the last one seen in the range' (#19) from arena/author-range-fallback 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 36s
deploy / deploy-metrics (push) Successful in 10s
2026-09-04 07:12:07 +00:00
Rob ThijssenandClaude Fable 5.1 c10d48da3a grafana: leaderboard author label falls back to the last one seen in the range
The exporter emits quantus_author_node_info only for authors in its rolling
block window, which is rebuilt from live blocks after a restart, while the
leaderboard covers the dashboard range. Authors outside the window showed a
blank author cell. Query G now takes the current series and, for preimages
without one, the last label set seen in the range (one per preimage).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 10:11:50 +03:00
grenade 39ea1d33e2 Merge pull request 'arena: vote author attribution by peer id over a window, follow renames' (#18) from arena/stable-attribution into main
deploy / fetch (push) Successful in 15s
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 37s
deploy / deploy-metrics (push) Successful in 9s
2026-09-04 06:57:35 +00:00
Rob ThijssenandClaude Fable 5.1 5067579d86 arena: vote author attribution by peer id over a window, follow renames
Telemetry node ids are per connection: a node that restarts or renames
comes back under a new id, so votes keyed by node id split across the old
and new identity, and with a lifetime attempt count the new identity had to
outvote the whole history before the name came back. Votes are now keyed by
peer id (stable across restarts and renames; node id only when telemetry
gave no peer id), kept for the last 20 blocks per author, and displayed
with the latest name telemetry reported for that peer, so a rename shows
within a block and a restart within a few. Lifetime attempts/attributed
counters are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 09:57:13 +03:00
grenade 933f9870a1 Merge pull request 'arena: name authors from telemetry first-import; leaderboard shows author, drops preimage and ours' (#17) from arena/author-names into main
deploy / fetch (push) Successful in 17s
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 37s
deploy / deploy-metrics (push) Successful in 10s
2026-09-04 06:42:50 +00:00
Rob ThijssenandClaude Fable 5.1 86a69889af arena: name authors from telemetry first-import; leaderboard shows author, drops preimage and ours
The exporter ingests the feed's ImportedBlock (code 6) reports and keeps,
per block hash, the first reporter and its lead over the second. Each tip
block is joined with that after 8 s; a reward preimage gets the node's
telemetry name once at least 3 of its blocks were attempted and 60% or more
were first reported by the same node. Emitted as
quantus_author_node_info{preimage,display,node_name,peer_id,self} confidence
with attempts/attributed counters per preimage; display is the name, or
the abbreviated preimage (first 8, last 4) when no confident attribution.

Measured today: leads are 40-100 ms for authors on the feed (the network is
better connected than when 200-700 ms was measured), so the lead threshold
is 20 ms and identity consistency carries the attribution. Validated for
ten minutes against bob: ours -> baba-gorchitsa, 0x2ccf39 -> quanpool-planck,
GUI miners -> QuantusMinerGUI at confidence 1.0.

Dashboard: the leaderboard joins quantus_author_node_info and shows the
display label as "author" in place of the reward preimage; the "ours" column
is gone, so the panel is fit to share. ARENA_DEBUG=1 turns on debug logging
(per-block attribution decisions).

Closes #11.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 09:42:29 +03:00
grenade a865c717f1 Merge pull request 'arena: calibrated quick-follow ratio from arrival times; blank cells stay blank' (#16) from arena/leaderboard-calibrated into main
deploy / fetch (push) Successful in 32s
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 42s
deploy / deploy-metrics (push) Successful in 23s
2026-09-04 06:05:54 +00:00
Rob ThijssenandClaude Fable 5.1 4125340be7 arena: arrival-time gap histogram; quick-follow ratio uses it; 1 s poll
Author timestamps are set when the node builds the proposal, right after
the previous block arrives, so gaps between author timestamps measure
proposal timing and flagged our own honest node at 2.8x the network's
quick-follow share. Withheld blocks show in arrival timing instead:
quantus_block_arrival_gap_seconds uses the exporter's observation time
(poll interval now 1 s; telemetry first-seen replaces it under #11). The
leaderboard's quick-follow column and the Author timing panel now use it,
with a 2 s window to sit above the poll resolution.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 09:05:28 +03:00
Rob ThijssenandClaude Fable 5.1 1d1b8d19de dashboard: quick-follow ratio instead of gap p10; transparent base so empty cells stay empty
For a Poisson block process the 10th-percentile gap of an honest author is
about one second by construction, so a fixed threshold flagged everyone.
quick-follow x is the share of an author's blocks arriving within 1 s of
the previous block divided by the network's share: 1 is normal, 2 or more
is withheld-block behaviour; needs 10 samples. Bucket boundaries are
selected as le="1.0" (Prometheus 3 normalises them to float strings).
Threshold bases are transparent so authors without samples show blank
cells, not red or blue.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 09:04:27 +03:00
grenade b17dfaeba1 Merge pull request 'dashboard: visual leaderboard columns — MH/s bar, threshold colours, burst ratio' (#15) from arena/leaderboard-visuals 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 37s
deploy / deploy-metrics (push) Successful in 10s
2026-09-04 06:00:06 +00:00
Rob ThijssenandClaude Fable 5.1 0b117899b7 dashboard: visual leaderboard columns — MH/s bar, threshold colours, burst ratio
est. MH/s as a bar on its own scale; gap p10 and drift median as
threshold-coloured cells (low gap and positive drift are the suspicious
directions); a burst ratio column, consecutive self-blocks over the count
expected from the author's share, coloured amber above 2 and red above 4
and shown only where the expected count exceeds one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 08:59:43 +03:00
grenade 30e79cbfff Merge pull request 'dashboard: scale the leaderboard's blocks bar to its own column; blank NaN timing cells' (#14) from arena/leaderboard-bar-scale 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 36s
deploy / deploy-metrics (push) Successful in 11s
2026-09-04 05:55:55 +00:00
Rob ThijssenandClaude Fable 5.1 833e414c6d dashboard: scale the leaderboard's blocks bar to its own column; blank NaN timing cells
After the multi-query join Grafana ranged the gauge cell across every numeric
field in the frame, so 31 blocks was drawn against a 675 MH/s maximum.
fieldMinMax on the blocks column restores the per-column scale. The gap and
drift quantiles are filtered with expr == expr so authors without samples in
the range show a blank cell instead of NaN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 08:55:36 +03:00
grenade af7992f712 Merge pull request 'dashboard: leaderboard gains est. hashrate, gap p10, drift median and consecutive columns' (#13) from arena/leaderboard-columns into main
deploy / fetch (push) Successful in 17s
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 34s
deploy / deploy-metrics (push) Successful in 13s
2026-09-04 05:51:19 +00:00
Rob ThijssenandClaude Fable 5.1 3e6985cb4e dashboard: leaderboard gains est. hashrate, gap p10, drift median and consecutive columns
Estimated hashrate is the author's share of blocks over the range times the
network hashrate estimate: statistical, roughly ±10% at a few hundred
blocks, and labelled as such. The timing columns join the new per-author
histograms (lair/quantus#12) on the preimage; outer join filtered to the
top-20 rows from the blocks query so authors without timing data still
appear with blanks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 08:50:53 +03:00
grenade 7cd4f9b50c Merge pull request 'arena: per-author block gap, timestamp drift and consecutive-block series' (#12) from arena/gap-and-drift 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 40s
deploy / deploy-metrics (push) Successful in 26s
2026-09-04 05:38:54 +00:00
Rob ThijssenandClaude Fable 5.1 b57526fdcc arena: per-author block gap, timestamp drift and consecutive-block series
Two signals that expose what other miners are doing, both from data the
exporter already fetches plus one chain_getBlock per tip block:

- quantus_block_gap_seconds{preimage}: histogram of the time between a
  block and the previous one, by the author of the later block, from the
  authors' own timestamps (the Timestamp::set inherent, pallet 1 call 0,
  decoded from the first extrinsic). Bursts in the lowest buckets for one
  author right after others' blocks are withheld blocks.
- quantus_block_timestamp_drift_seconds{preimage}: block timestamp minus
  the time the exporter first saw the block. Negative is propagation and
  poll lag; positive is a future-dated block, which the Homestead-style
  retarget rewards slightly (the inherent allows 30 s).
- quantus_consecutive_self_blocks_total{preimage}: blocks authored directly
  after the same author's previous block.

Same top-N cap as the leaderboard, with "other" merged and "all" for the
network. Validated live against bob for three minutes. Dashboard row
"Author timing" with p10 gap, median drift and consecutive-block rate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-04 08:38:31 +03:00
grenade 8ee003d8d3 Merge pull request 'gpu: lock the memory clock at 810 MHz on the mining hosts (+13% on the 4090)' (#10) from gpu/memory-clock-lock 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 35s
deploy / deploy-metrics (push) Successful in 12s
2026-09-03 15:46:59 +00:00
Rob ThijssenandClaude Fable 5.1 1fbe9c5a6b gpu: lock the memory clock at 810 MHz on the mining hosts (+13% on the 4090)
The miner never touches VRAM, so memory at full clock is watts taken from the
SMs inside the same power cap. Measured with the CUDA engine, miner paused:
4090 at 250 W 410 -> 463 MH/s (SM 1950 -> 2190 MHz); 3060 at 130 W 81 -> 83.
405 MHz drops the card into a low-power state (155 MH/s) and is refused.

Applied by nvidia-power-limit.service from the same config file
(NVIDIA_MEMORY_CLOCK_MHZ), configured per host in GPU_MEMORY_CLOCKS; hosts
without an entry are reset to the driver default. beast is not listed:
inference is memory-bound.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-03 18:45:22 +03:00
grenade be86c60252 Merge pull request 'arena: cumulative authorship counter so the leaderboard follows the time range' (#9) from arena/authored-counter 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 36s
deploy / deploy-metrics (push) Successful in 16s
2026-09-03 13:11:06 +00:00
Rob ThijssenandClaude Fable 5.1 dfd10291ad arena: cumulative authorship counter so the leaderboard follows the time range
quantus_blocks_authored is a rolling window by block count (3600 blocks)
served as a gauge, so the leaderboard panel ignored Grafana's time picker.
Add quantus_blocks_authored_total, cumulative since exporter start and not
capped to top N (a counter that comes and goes loses increments; the label
set is bounded by distinct miners seen), and query the panel as
increase() over $__range. The windowed gauge stays for the fixed view.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-03 16:10:48 +03:00
grenade 3a869533a1 Merge pull request 'rules: derive the short hostname so the GPU join matches node-exporter' (#8) from metrics/rules-host-label into main
deploy / fetch (push) Successful in 21s
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 36s
deploy / deploy-metrics (push) Successful in 11s
2026-09-03 11:24:11 +00:00
Rob ThijssenandClaude Fable 5.1 a0ce6d7467 rules: derive the short hostname so the GPU join matches node-exporter
node-exporter's host label is the short name (benjy), the first rule derived
the FQDN from the miner's instance, and quantus:device_hashrate_per_watt
produced no series. Verified against live labels on the metrics host.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-03 14:23:41 +03:00
grenade 41986ace4d Merge pull request 'metrics: performance by build — recording rules, dashboard row, throttle reasons' (#7) from metrics/performance-by-build 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 34s
deploy / deploy-metrics (push) Successful in 11s
2026-09-03 11:16:27 +00:00
Rob ThijssenandClaude Fable 5.1 aa6bd855f4 metrics: performance by build — recording rules, dashboard row, throttle reasons
Monitoring side of quantus/miner#9. The miner now exports build identity,
per-device and per-job series (quantus/miner#14); this makes them readable.

- asset/prometheus/quantus-rules.yml: recording rules joining the miner's
  series with the GPU series on a derived `host` and device index —
  hashrate by device and commit, hashrate per watt, batch time off the GPU,
  stale work, seal latency p95, idle waiting for the node.
- script/infra-setup.sh (metrics role): prometheus.yml gains `rule_files:`
  for /etc/prometheus/rules.d, guarded like scrape_config_files; the dir;
  a sudoers grant for the rules file.
- deploy.yaml deploy-metrics: pushes the rules (reload is already gated on
  content change); validate asserts the quantus-miner rule group loaded.
- asset/grafana/quantus.json: "Performance by build" row, eight panels, and
  a deploy annotation from changes in miner_build_info. No credentials
  involved; Prometheus is the annotation source.
- asset/gpu/nvidia-textfile.sh (gpu role, operator-shipped): five
  nvidia_gpu_event_reason_active{reason=...} gauges from nvidia-smi's clock
  event reasons, so a regression that coincides with thermal or power-brake
  capping can be told from a code regression.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-03 14:13:15 +03:00
grenade 7465e8aca5 Merge pull request 'retire deploy-miner: the miner now deploys from quantus/miner' (#6) from retire/deploy-miner into main
deploy / fetch (push) Successful in 18s
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 38s
deploy / deploy-metrics (push) Successful in 10s
2026-09-03 10:45:21 +00:00
Rob ThijssenandClaude Fable 5.1 03962edf76 retire deploy-miner: the miner now deploys from quantus/miner
lair/quantus#4 step 2 (miner half). quantus/miner#12 deploys the miner from
that repo's main on every push, with its own validate and rollback, and owns
the miner role of infra-setup (deploy/infra-setup.sh there). Two deployers
for one host would flip the binary on every run, so this one stops.

- deploy.yaml: drop the deploy-miner job, the miner download in fetch, the
  MINER_VERSION pin and miner_version input; deploy-metrics needs only
  deploy-node. SCRAPE_MINERS stays and must agree with quantus/miner's
  matrix; `validate metrics` still asserts every target is up.
- infra-setup.sh: drop the miner role. gpu, node, metrics, edge unchanged.
- asset/: remove the miner unit, sysusers, firewalld service and env
  template (moved verbatim to quantus/miner deploy/).
- readme, CLAUDE: point at the new owner.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-03 13:30:36 +03:00
grenade 4a781b3b8c Merge pull request 'infra-setup: grant gitea_ci stop/start on quantus-miner.service' (#5) from bench/miner-stop-start-sudoers into main 2026-09-03 10:15:28 +00:00
Rob ThijssenandClaude Fable 5.1 b6ae4c68ab infra-setup: grant gitea_ci stop/start on quantus-miner.service
The benchmark harness (quantus/miner#2) pauses mining on a host for a
measurement window and resumes it afterwards. restart alone cannot express
a pause. Two lines in the miner role; re-run infra-setup on the miner hosts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-03 12:56:28 +03:00
Rob Thijssen 811f5c5a4c fix: state the leaderboard window instead of saying 'rolling'
deploy / fetch (push) Successful in 17s
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 44s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 39s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-metrics (push) Successful in 8s
The window is a block count capped at 3600, refills from empty after an
exporter restart, and is not the dashboard time range. Adds blocks-held
and wall-clock-elapsed stats beside the table, amber until half full,
and drops the constant 'component' column.
2026-09-03 10:04:28 +03:00
Rob Thijssen 05a071352d fix: observed-vs-expected share panel rendered empty
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 34s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-metrics (push) Successful in 14s
sum() strips labels while quantus_network_hashrate carries job and
instance, so the division matched nothing. Use scalar(), and take the
numerator from the hashes counter rather than the spiky gauge.
2026-09-03 10:00:49 +03:00
Rob Thijssen 62f8887f48 fix: leaderboard was a bar chart fed time series
deploy / fetch (push) Successful in 17s
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 34s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 39s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-metrics (push) Successful in 9s
topk(...) with range=true returns one point per scrape, so every
timestamp became a bar and the miners smeared together. A leaderboard is
a snapshot: instant query, rendered as a sorted table with gauge cells,
our own row marked, and a total in the footer.
2026-09-03 09:58:26 +03:00
Rob Thijssen a6cfa3bbf5 fix: derive hashrate from the counter, not the gauge
deploy / fetch (push) Successful in 17s
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 34s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 39s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-metrics (push) Successful in 11s
miner_hash_rate spikes to impossible values after a restart (1771 MH/s
on a 4090 that peaks near 145) because it is computed per job over a
tiny elapsed time. Hashrate panels now use rate(miner_hashes_total).

Adds node and miner restart annotations so version changes are visible
on every graph, a node version stat, a hash-rate-per-watt panel, and
puts discovered peers on its own axis so connected is legible.
2026-09-03 09:52:00 +03:00
Rob Thijssen 00bdb47d04 chore: pin quantus-node 0.11.1
deploy / fetch (push) Successful in 17s
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 43s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 44s
deploy / deploy-metrics (push) Successful in 13s
2026-09-03 09:44:22 +03:00
Rob Thijssen ebd6fd2b32 chore: pin quantus-miner 4.0.2
deploy / fetch (push) Successful in 18s
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 36s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-metrics (push) Successful in 12s
2026-09-03 09:37:37 +03:00
Rob Thijssen e178bcbdf9 feat: boot-time GPU power limits, clamped to hardware floors
deploy / fetch (push) Successful in 17s
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 34s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 43s
deploy / deploy-metrics (push) Successful in 8s
nvidia-smi -pl survives neither a reboot nor a driver unload, and both
failures are silent -- the card returns to its default limit and nobody
notices until a connector runs warm. Hence a boot-time unit, which also
enables persistence mode so the limit is not lost when the last GPU
client detaches.

Per-host limits configured as host=watts in infra-setup.sh, clamped to
each card's own min/max. Verified: a 300W request on a 5090 whose floor
is 400W is corrected with an explicit log line rather than failing or
being ignored.

Measured on a 4090 mining Poseidon2:
  450W (stock)  185.5 MH/s  70C  0.413 MH/s/W
  300W          183.2 MH/s  61C  0.611
  250W          180.3 MH/s  58C  0.721

44% less power and 12C cooler for 2.8% less hashrate -- this workload is
register-resident integer math and does not need the top of the clock
curve. It also cuts current through the 12VHPWR connector, which is the
one component on these cards with a documented melting failure mode.

Ordered Before=quantus-miner.service so a GPU is never briefly at its
default limit under full mining load.
2026-09-01 06:42:32 +03:00
Rob Thijssen 298dff9343 feat: at-a-glance GPU gauges
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 32s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 43s
deploy / deploy-metrics (push) Successful in 8s
Adds a Now row of gauge panels at the top of the GPU dashboard,
mirroring node-exporter's CPU Busy: threshold markers, amber then red,
one gauge per GPU series rather than one aggregate.

GPU Busy (utilisation), Temperature with amber at 80C and red at 87C
where consumer NVIDIA cards start thermal throttling, and Power as a
fraction of the card's enforced limit rather than raw watts -- a 4090
at 450W and a 5090 at 600W are both fully loaded, and only the ratio
says so on one scale.

Labels are shortened to host + gpu index for the gauges; the timeseries
below keep the full name.
2026-09-01 06:19:28 +03:00
Rob Thijssen 598aea28f8 feat: GPU metrics via the node-exporter textfile collector
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 33s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-metrics (push) Successful in 9s
node-exporter already runs on every host, is already scraped (job
'node', targets from cichlid's inventory, labelled by host), and already
has the textfile collector enabled with a spool at
/var/lib/prometheus/node-exporter. So a textfile drop needs no new
exporter, no port allocation, no firewalld service and no scrape config
-- which is why it is one-time provisioning rather than a deploy job.

DCGM was the alternative and is the wrong tool: it targets datacenter
cards and its GeForce support is partial. nvidia-smi reports everything
wanted.

The script writes to a temp file and renames atomically, because the
collector reads whole files and a half-written one surfaces as
node_textfile_scrape_error. When nvidia-smi fails it emits an explicit
nvidia_gpu_scrape_ok 0 rather than leaving a stale file, so a broken
driver reads as broken instead of the graphs quietly flatlining.

Verified on the dual-GPU host: both cards reporting temperature, power,
utilisation, memory, clocks and fan through node-exporter with
node_textfile_scrape_error 0.

Fleet-general rather than quantus-specific -- it lives here because
there is no monitoring repo yet, and the dashboard says so.
2026-09-01 06:10:10 +03:00
Rob Thijssen d784db8e3a feat: peer occupancy panels for the out-peers experiment
opened MINUS closed per direction, plotted against the caps. Occupancy
is the measurement that matters and cumulative opens actively mislead:
inbound churns constantly (170 opened, 156 closed) while outbound is
deliberately maintained (36 opened, 27 closed), so a high inbound open
count says nothing about whether its cap is full. Reading the ratio of
cumulative opens is how issue #1 came to recommend the wrong knob.

Measured now: outbound 9 held against a cap of 8 -- saturated -- while
inbound sits at 14 of 32 and the node has discovered 300 peers. So
--out-peers binds and --in-peers does not, the reverse of what #1 said.

No exporter change needed; both series are already scraped from the
node. Adds the discovered-peers count and rounds-per-minute alongside,
since those are the other two the experiment compares.
2026-09-01 06:05:01 +03:00
Rob Thijssen 867a71f514 feat: telemetry census — node population vs distinct authors
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 34s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-metrics (push) Successful in 9s
The chain can only see miners that WIN blocks. A miner too small to take
a block in any given window is invisible in the authorship leaderboard
while still being connected and still burning electricity. Telemetry
sees every node, so the two series together answer a question neither
can alone: nodes flat while distinct authors falls means small miners
are being out-competed, not quitting.

Measured on the live feed: 32 nodes against 9-21 distinct authors, 229
aggregate CPU cores, 15 VMs, and five client versions in the wild --
half the network is still on 0.6.x/0.7.x/0.8.x, which is a fork risk at
a runtime upgrade and worth knowing before 2026-09-09.

python3-websockets is a SOFT dependency, packaged not pip: without it
the exporter serves every chain metric and reports
quantus_telemetry_up 0, so a missing package degrades rather than
breaks. infra-setup.sh installs it and treats failure as non-fatal.

The genesis hash is discovered from the node rather than configured --
one less value to get wrong when the chain changes for mainnet.
2026-09-01 05:34:50 +03:00
Rob Thijssen d60173f0ca feat: wss://quantus.internal via the site edge proxy
deploy / fetch (push) Successful in 17s
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 34s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-metrics (push) Successful in 8s
subxt refuses plain ws:// to any host but localhost (InsecureUrl), so
quantus-cli against a remote node needs either an ssh tunnel or TLS.
This adds the vhost and an edge role in infra-setup.sh to mint the cert,
install it, arm step@quantus.timer and reload nginx.

Operator-run rather than CI-deployed, matching lair/mail: the runner's
rights never extend to reading certificate keys or reloading nginx on a
shared edge proxy.

Two things found while writing it. The vhost must listen on
127.0.0.1:14443 with proxy_protocol, not 443, because a stream SNI
router owns 443 on the edge proxies. And the sites-available copy of an
existing vhost is NOT a safe template: 11 of 19 enabled vhosts on
hanzalova are regular files rather than symlinks and have drifted from
sites-available since the SNI migration -- agent0's enabled copy listens
on 14443 while its sites-available copy still says 443. This vhost is
installed as a symlink so it cannot drift the same way.

Verified: nginx -t parses the config on the real host, failing only on
the not-yet-minted certificate.
2026-09-01 05:21:10 +03:00
Rob Thijssen 71bbc2e826 fix: --rpc-external is refused on a validator
deploy / fetch (push) Successful in 17s
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 31s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-metrics (push) Successful in 9s
The node crash-looped 7 times and was down ~100s, not mining:

  Error: --rpc-external option shouldn't be used if the node is running
  as a validator. Use `--unsafe-rpc-external` or `--rpc-methods=unsafe`
  if you understand the risks.

Substrate rejects --rpc-external on a validator outright and exits,
whatever --rpc-methods says. --unsafe-rpc-external binds identically
('Same as --rpc-external' per --help) and differs only in demanding an
explicit acknowledgement. The safety still comes from --rpc-methods
safe, which is unchanged.

Restored on the host immediately; this makes CI converge on the same
thing rather than reverting it.

Also: validate opened with a bare , which exits 3
the moment a unit is 'activating' and under set -e aborted the step
before printing anything. A crash-looping unit sits in
activating/auto-restart forever, so a validator refusing its own flags
surfaced as an opaque 'exit code 3'. It now waits for a terminal state
and, on failure, reports NRestarts and the actual error lines from the
journal.
2026-09-01 04:50:01 +03:00
Rob Thijssen add26303f2 feat: preflight the sudoers before deploying
Run 11 failed with

  sudo: a password is required
  rsync error: error in rsync protocol data stream (code 12)

because quantus-node-rpc.xml was added to the deploy and to
infra-setup.sh's grants, but infra-setup had not been re-run against the
host. The failure landed forty lines into deploy-node, after several
files had already been written, and said nothing about the actual cause.

Each deploy job now compares the paths infra-setup.sh grants for its
role against sudo -n -l on the target, before touching anything. The two
cannot drift because the expected list is extracted from
infra-setup.sh itself rather than duplicated, so an asset added in
future is covered automatically.

Verified against the live host: it correctly reported
/etc/firewalld/services/quantus-node-rpc.xml as missing before
infra-setup was re-run, and reports all 26 paths permitted after.
2026-09-01 04:47:24 +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 ca94e3a6c0 feat: track accrued rewards in the arena exporter
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) (push) Successful in 31s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-metrics (push) Successful in 8s
quantus-node has no balance subcommand, and the faucet_getAccountInfo
RPC that MINING.md documents is absent from this build -- rpc_methods
returns only account_nextIndex and system_accountNextIndex. So the
exporter builds the System.Account storage key itself: twox128(System)
++ twox128(Account) ++ blake2_128_concat(account).

XXH64 is inlined because python3-xxhash is not installed on the node
hosts and this exporter stays dependency-free. It asserts the two
universally-known prefixes at import, so a broken implementation fails
loudly instead of silently querying the wrong key.

The reward address is Poseidon2(inner_hash). Rather than reimplement
Poseidon2 over Goldilocks, the deploy lifts it from the node's own
startup log; empty is tolerated and the exporter simply omits balance
metrics, which is what a genuinely first deploy will see.

Verified live: 158.153404340522 PLK, 12 decimals, zero RPC errors.

Also: str.replace fails silently on a missing anchor, which is how the
first attempt shipped an exporter calling a method that was never
inserted. Edits to this file now assert their anchors.
2026-09-01 04:07:03 +03:00
Rob Thijssen adc04f5bc5 fix: wait for first scrape before judging target health
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) (push) Successful in 29s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-metrics (push) Successful in 11s
Run 8's validate reported

  FAIL quantus-arena    bob:25033       unknown
  FAIL quantus-miner    quadbrat:9900   unknown
  ok   quantus-miner    benjy:9900      up
  ok   quantus-node     bob:9615        up

which is not a failure: a target Prometheus has only just learned about
reports 'unknown' until its first scrape completes, and this step runs
seconds after the config reload with a 15s scrape interval. Both were up
moments later.

The old retry loop broke as soon as ANY quantus target existed, which
was immediately true because the node target predated this run. It now
waits for the unknowns to resolve, bounded at 120s. A genuinely dead
target reports 'down' rather than 'unknown', so this does not paper over
real failures -- the same distinction the readiness wait on the node
makes.

Third race of this shape: deploy finishes before the thing it deployed
is observable.
2026-09-01 03:43:03 +03:00
Rob Thijssen c748da08cb feat: arena exporter — network hashrate, difficulty, authorship leaderboard
deploy / fetch (push) Successful in 14s
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal quadbrat.hanzalova.internal, --public-addr /dns4/nh.thgttg.com/tcp/30333) (push) Successful in 40s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 40s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 42s
deploy / deploy-metrics (push) Failing after 15s
Closes the observability gap in #1 and builds the chain-scraper half of
#2. Every block header carries its miner's reward preimage in a
PreRuntime digest (engine id pow_), so authorship for EVERY miner on the
network is derivable from headers alone -- no indexer, no cooperation,
no event decoding. Difficulty comes from QPoWApi_get_difficulty via
state_call, verified working against the live node.

That makes expected share computable for the first time: our hashrate
over difficulty/interval, plotted against the fraction of blocks we
actually author. The gap between those two lines is orphan and
stale-work loss, which is the only honest test of whether tuning
anything helped.

Pure stdlib Python, deliberately: no pip, no venv, no build step, and it
deploys like every other asset here. Runs as its own service account
against the node's loopback RPC -- it has no business being able to
touch the validator's state or reward config.

Two things learned testing it live. The window is a BLOCK COUNT, not a
duration: a time window is meaningless while the node catches up, since
it ingests history at import speed -- the first version reported a 9ms
block interval and 102 GH/s. And per-preimage series are capped at top-N
plus our own, because an unbounded label set would be a cardinality bomb
if the network grows.

Prior art in Quantus-Network/qsafe.af derived miners from balance events
instead; the digest is lighter and unambiguous, and drops the GraphQL
indexer dependency entirely.
2026-08-31 19:58:02 +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 bbe29caafe feat: mining efficiency panels, and say what they cannot measure
deploy / fetch (push) Successful in 17s
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal, --public-addr /dns4/nh.thgttg.com/tcp/30333) (push) Successful in 31s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Successful in 41s
deploy / deploy-metrics (push) Successful in 9s
Adds a Mining efficiency row: mining rounds started per minute, hashes
per mining round, and active jobs over time.

These are proxies, not the answer. Neither binary exports what the
question actually needs -- quantus-node has no difficulty metric and no
authored-block counter (substrate_proposer_block_constructed counts
proposals constructed, not rounds won), and quantus-miner has no
solutions-found counter. So authored-blocks-vs-expected-share cannot be
plotted, and a panel implying otherwise would be worse than none.

A text panel on the row states that plainly rather than leaving someone
to infer a conclusion the data does not support. lair/quantus#1 tracks
closing the gap -- a chain-query exporter counting blocks credited to
the reward address is the cheapest route -- and the experiments that
depend on it.
2026-08-31 19:05:35 +03:00
Rob Thijssen 2cd478646f fix: validate metrics without shell-escaped python or grafana auth
Two bugs, both in validation only -- the deploy half worked and both
scrape targets are up.

The targets check used python3 -c with \" escapes inside an f-string,
which Python reads as a line continuation:

  SyntaxError: unexpected character after line continuation character

Rewritten as a quoted heredoc reading a file, so nothing is escaped
through the shell. It now also reports per-target health rather than
grepping for ' up '.

The Grafana check called /api/search, which returns 401 -- the API
requires auth and this job holds no Grafana credentials by design.
Replaced with the unauthenticated /api/health, a file presence check for
both dashboard and provider, and a scan of the grafana journal for
dashboard provisioning errors, which is what actually distinguishes a
dashboard that loaded from one that failed to parse.
2026-08-31 18:55:58 +03:00