Commit Graph
4 Commits
Author SHA1 Message Date
grenadeandClaude Opus 5 f9ea292a70 fix: a hashrate estimate now carries its error bar
deploy / build (push) Successful in 7m25s
deploy / deploy-web (push) Successful in 4s
deploy / deploy-api (push) Successful in 15s
The leaderboard credited a miner with "1.55 GH/s" on the strength of one block,
last seen four hours earlier, to three significant figures. Block finding is
Poisson: k blocks carries a relative standard error of 1/√k, so one block is
±100% and it takes twenty-five to reach ±20%. The estimator was unbiased and the
presentation was not honest, which is the half that matters to a reader.

`miner_hashrate` now returns the count's uncertainty with the figure, and the
leaderboard and miner page render it — dimmed below four blocks, where the error
bar is wider than half the value.

Checked first that the arithmetic itself is sound, because the obvious
suspicion was the formula. It is the chain's own: `qpow-math::is_valid_nonce`
accepts when `hash < U512::MAX / difficulty`, `verify_nonce_internal` passes it
what `get_difficulty()` returns, and the hash is uniform — 200,000 samples of
`hash_squeeze_twice` gave 0.4995 of 2^512 against a uniform 0.5, and 12.48%
below 2^509 against 12.5%. Acceptance is 1/difficulty, so expected trials is
difficulty, exactly. The two network estimators — difficulty over tip interval,
and summed work over window span — also agree within 5% on live data.

What that leaves is recorded in CLAUDE.md and is not an observer bug: our three
miners measure 1.66 GH/s (1.39 instantaneous), while the chain credited the same
preimage 5.3 GH/s over 29 blocks. Sampling does not explain 14 sigma.
`miner_cpu_hash_rate` is 0 on all three — every hash is on GPU — and the miner's
CPU path calls `qpow_math::get_nonce_hash` directly while the CUDA kernel is a
separate implementation with its own counter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
2026-09-09 19:48:51 +03:00
grenadeandClaude Opus 5 a3eb670d11 feat: identify miners by the address they are paid at, not the preimage
deploy / build (push) Waiting to run
deploy / deploy-api (push) Has been cancelled
deploy / deploy-web (push) Has been cancelled
A reward preimage is exact and unrecognisable — no miner knows theirs by sight.
The address is the string they configured and the one their balance shows
against, and the two are related by a derivation the chain performs anyway:
`pallets/mining-rewards` pays `qp_wormhole::derive_wormhole_address`, which is
`qp_poseidon_core::rehash_to_bytes`. Running the same function reproduces the
address exactly, with no RPC and no lookup table.

The derivation takes the preimage and nothing else — no genesis, no chain
parameters — so one preimage is one account on every Quantus chain. The unit
test pins it against a real pair (this observer's own node and the address it
was paid at), so a dependency upgrade that changed the derivation fails the
build rather than quietly relabelling every miner on the site.

Unnamed miners now render as their address. The miner panel shows all three
identifiers, because they answer different questions: the node name is an
inference and may be absent, the address is what the miner recognises, and the
preimage is what the header actually carries and the only one the chain
asserts.

SS58 is written out rather than pulled from `sp-core`, which would bring a
substrate runtime's worth of dependencies to format 36 bytes — the same trade
the readme records for declining subxt. Base58 is hand-rolled because it is
long division and nothing else; blake2 is not, because checksums are not worth
hand-rolling. Both are covered by the address test vector.

`RecentBlock` gains the attribution source: the ticker cannot tell a node name
from an address by looking, since `quanpool-payout-mainnet` and `quantus-radar`
both begin with the letter Quantus addresses do. Two tests that asserted the
old display policy now assert the new one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
2026-09-09 15:13:25 +03:00
grenadeandClaude Opus 5 bfd3470840 feat(attribution): offer a best-effort name, and say how thin the evidence is
deploy / build (push) Waiting to run
deploy / deploy-api (push) Has been cancelled
deploy / deploy-web (push) Has been cancelled
A name now appears as soon as one node leads the vote, rather than waiting for
three. On a chain that resolves a first reporter for about one block in seven,
the old bar left almost every miner permanently anonymous — and since a later
block can overturn a mapping, a guess that corrects itself is more useful than
a blank that never fills in.

What is refused is a dead heat. Choosing between two equally-voted nodes would
be a coin toss wearing a telemetry badge, which is a different thing from a
thin but real lead.

The honesty moves to the presentation instead of being dropped. A percentage
alone cannot separate "100% of 1" from "86% of 7", so `Attribution` and
`LeaderboardRow` now carry the number of votes behind the name. The leaderboard
marks a mapping resting on a single block or a narrow lead with `node?` and a
dashed chip, and the tooltip states both figures and that a later block can
correct it. Dashed rather than a second colour: the accent hue is reserved for
identity and alarm, and this is neither — the same claim held more loosely.

The footer now says node names are inferred from which node reported a block to
telemetry first, never something the miner asserted, and that the reward
preimage beside them is the only identity the chain itself vouches for.

`a_single_lucky_vote_still_names_nobody` asserted the old policy and is
replaced by `one_vote_names_but_says_it_is_only_one`, which pins the new
contract: it names, and it reports `votes == 1` so the UI can mark it. A new
`a_dead_heat_names_nobody` keeps the case that is still refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
2026-09-09 14:44:48 +03:00
grenadeandClaude Opus 5 110fbc3631 feat: blackbeard.observer — live Quantus mining leaderboard
Cargo workspace plus a Vite frontend, following ~/git/architecture/generic.md.

Every block header carries its author's wormhole reward preimage in a `pow_`
PreRuntime digest, so authorship for the whole network is derivable from headers
alone — no indexer, no registration, no way for a miner to be left out. That
decoding, the hashrate maths and the telemetry name attribution live in
blackbeard-core with no I/O at all, so the parts that are easy to get subtly
wrong are exercised by unit tests rather than only against a live chain.

The browser holds one WebSocket: snapshot on subscribe, deltas thereafter. The
head stream is itself a push (chain_subscribeNewHeads), so a block reaches the
page the moment the node imports it. Messages are serialised once per broadcast,
and leaderboards are recomputed only for windows a socket is actually watching.
No RxJS — useSyncExternalStore is React's own contract for this.

Verified against the live Planck testnet: 12/12 headers decoded, telemetry names
attributed (quanpool-planck, baba-gorchitsa, …), warm start restoring 84 blocks
and 5 held names across a restart.

Three findings worth recording, all in CLAUDE.md:

- substrate-telemetry sends its JSON in *binary* frames. A text-only client
  connects, subscribes, reports healthy and receives nothing at all — and a
  Python probe hides it, because json.loads accepts bytes.
- Difficulty is a little-endian U512; decoding it big-endian gives a number
  wrong by ~10^150 that still renders fine.
- Planck's real block interval is ~13-15s against a 6s target with enormous
  variance, so a measured interval needs 20 tip samples before it is publishable.

Deploy assets, the Gitea Actions workflow and script/infra-setup.sh are included;
port 25864 is registered in architecture/port-allocations.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSDYiibCtELsrjQq6KXnoi
2026-09-04 12:33:54 +03:00