feat(attribution): carry a miner's name across chains by reward preimage
deploy / build (push) Successful in 7m6s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 14s

Live telemetry names the miners who win often and leaves everyone else as a
hex string. Measured on mainnet: of 22 miners, 2 were named and 14 had no name
available anywhere — but 6 already had one on Planck, under the same reward
preimage. Those 6 are exactly the small operators the naming is worst for,
because a name needs a block whose first reporter the feed could separate and
they win few blocks.

The preimage is derived from a secret only its owner holds, so the same
preimage on two chains is the same operator. That is an inference the site can
stand behind, unlike guessing from timing — and it is the only lever that works
without waiting on the feed at all.

It is a separate `AttributionSource` and renders as one. `Carried` names carry
no confidence and no votes, because they assert nothing about this chain: only
that this is who the operator was last known to be. Any live attribution here
outranks one, and the row shows an `elsewhere` chip rather than the telemetry
`node` chip so the two can never be read as the same claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
This commit is contained in:
2026-09-09 14:59:29 +03:00
co-authored by Claude Opus 5
parent a177e805f4
commit ffcfb79cab
8 changed files with 188 additions and 9 deletions
+2 -1
View File
@@ -247,7 +247,8 @@ export default function App() {
Node names are a best-effort mapping, inferred from which node reported a block to
telemetry first — never a claim the miner made. A <code>node?</code> tag marks one resting
on a single block or a narrow lead; later blocks correct it. The reward preimage beside it
is the only identity the chain itself asserts.
is the only identity the chain itself asserts. An <code>elsewhere</code> tag means the
name was earned by the same reward preimage on another chain, not observed on this one.
</span>
</footer>
</div>
+1 -1
View File
@@ -7,4 +7,4 @@
* inference from who reported a block first, not a claim the miner made, and
* the UI must not present the two identically.
*/
export type AttributionSource = "preimage" | "telemetry";
export type AttributionSource = "preimage" | "telemetry" | "carried";
+18 -2
View File
@@ -29,6 +29,11 @@ function Row({
onSelect: (miner: string) => void
}) {
const named = row.attribution === 'telemetry'
// A name this operator earned on another chain, under the same reward
// preimage. Shown because a preimage tells a miner nothing about themselves,
// but never dressed as a live observation: nothing here says the node is on
// this chain, only that this is who the operator was last known to be.
const carried = row.attribution === 'carried'
// Thin evidence: a single supporting block, or a lead the rest of the votes
// do not back. Either way the mapping is a guess that a later block may undo.
const tentative = named && (row.attribution_votes < 3 || row.confidence < 0.6)
@@ -47,16 +52,27 @@ function Row({
</button>
<a
href={`#/miner/${row.miner}`}
className={named ? 'miner-name' : 'miner-name anonymous'}
className={named || carried ? 'miner-name' : 'miner-name anonymous'}
title={row.miner}
onClick={(e) => {
e.preventDefault()
onSelect(row.miner)
}}
>
{named ? row.display : shortMiner(row.miner)}
{named || carried ? row.display : shortMiner(row.miner)}
</a>
{mine && <span className="chip chip-you">You</span>}
{carried && (
<span
className="chip chip-named chip-guess"
title={
'Name carried from another chain, matched on the same reward preimage — ' +
'this operator has not yet been resolved by telemetry here.'
}
>
elsewhere
</span>
)}
{named && (
<span
// An attributed name is an inference from who reported the block