Two claims this repository shipped, both disproved by measuring mainnet. **A mining address is keyless, so its balance can only rise.** `retained` was documented here as "the one measure privacy cannot hide", reasoning that a payment debits the account whichever route it takes. It does not. `mining-rewards` pays `derive_wormhole_address(preimage)`, which no Dilithium keypair signs for, and `Wormhole::credit_and_record` credits an exit's destination with `Unbalanced::increase_balance` while never debiting the note's source. QUANPOOL's balance rose at all twelve samples between blocks 1,000 and 42,400 and fell at none, nonce 0 throughout — as every miner's must. So `retained` read ~100% for everyone because it had to, and a number identical for all reads as an accusation aimed at whoever mined most. The column is gone, `balance` is `received`, and the table says plainly that a pool paying out in full and one paying nothing produce the same row. **Four empty lock maps are not "nothing is withheld".** Locks, Holds, Freezes and Reserves are all empty — counted, not assumed — and the network page concluded circulating equals total issuance. Wrong by a factor of 436: this runtime vests from a *pot*, not with locks, and `modlqvesting` holds 5,669,940 QTC, 99.54% of every unit of balance on the chain. The page now reports the pot and issuance-minus-pot, and still declines the word "circulating" because the release schedule is not read here. The pot address is derived from `Vesting::PalletId` in metadata rather than a hard-coded `qvesting`: it is a consequence of a runtime constant, and a runtime that changed it would leave the site reporting an empty pot while looking perfectly healthy. Both steps are pinned against the real mainnet metadata fixture. Also recorded: never sum account balances and call it supply. They exceed `TotalIssuance` by 13,427 QTC at block 42,400 and by exactly zero at genesis, bisecting to block 4,800 and a `verify_public_batch` — private value is counted twice by any such total. Single blocks reconcile exactly, so a spot check says everything is fine. Refs #17 Closes #19 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
8.2 KiB
TypeScript
177 lines
8.2 KiB
TypeScript
/**
|
||
* Who won the blocks, and what became of the rewards.
|
||
*
|
||
* **This table reports flows and does not judge them.** Every figure is
|
||
* something the chain recorded — direction comes from each transfer event's own
|
||
* `from` and `to` — but what the figures *mean* is a question the data cannot
|
||
* settle. An operator may hold several addresses, may settle off-chain, or may
|
||
* simply be a solo miner keeping what it earned, which is the ordinary case and
|
||
* unremarkable.
|
||
*
|
||
* So there is no badge, no flag, and no word like "delinquent" anywhere here.
|
||
* What earns the table its place is **comparison**: one miner's recipient count
|
||
* says almost nothing, and beside its neighbours' it says a great deal. The
|
||
* reader draws the conclusion; the site lays out the evidence and names its
|
||
* limits.
|
||
*
|
||
* The name column is the weakest thing on the page and is marked as such. There
|
||
* is no such thing as a "pool" on this chain — the word appears only inside a
|
||
* name an operator reports over telemetry, and a site that flagged entities as
|
||
* delinquent on the strength of a name they chose themselves would be one bad
|
||
* match away from libelling someone.
|
||
*/
|
||
|
||
import { useEffect, useState } from 'react'
|
||
import { Link } from 'react-router-dom'
|
||
|
||
import type { Distribution } from '../api/generated/Distribution'
|
||
import { RequestFailed, fetchDistribution } from '../api/rest'
|
||
import { height as fmtHeight, share as fmtShare, shortAddress, tokens } from '../lib/format'
|
||
import { href } from '../lib/routes'
|
||
import type { Window as WindowName } from '../api/generated/Window'
|
||
|
||
export function DistributionPanel({
|
||
chain,
|
||
window: windowName,
|
||
decimals,
|
||
symbol,
|
||
}: {
|
||
chain: string
|
||
window: WindowName
|
||
decimals: number
|
||
/** The unit, shown once in the header rather than on every cell: six columns
|
||
* of amounts each repeating "QTC" is noise, and they are all the same unit. */
|
||
symbol: string
|
||
}) {
|
||
const [data, setData] = useState<Distribution | null>(null)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
useEffect(() => {
|
||
const controller = new AbortController()
|
||
setData(null)
|
||
setError(null)
|
||
fetchDistribution(chain, windowName, controller.signal)
|
||
.then(setData)
|
||
.catch((e: unknown) => {
|
||
if (controller.signal.aborted) return
|
||
setError(e instanceof RequestFailed ? e.message : 'Could not reach the observer.')
|
||
})
|
||
return () => controller.abort()
|
||
}, [chain, windowName])
|
||
|
||
if (error) return <p className="empty">{error}</p>
|
||
if (!data) return <p className="empty">Following the rewards…</p>
|
||
|
||
const amount = (raw: string | null | undefined) =>
|
||
raw === null || raw === undefined ? '—' : tokens(raw, decimals, 1)
|
||
|
||
return (
|
||
<section className="panel" style={{ marginBottom: 26 }}>
|
||
<div className="panel-head">
|
||
<h2 className="panel-title">Where the rewards went</h2>
|
||
<span className="eyebrow">
|
||
{fmtHeight(data.window_blocks)}-block window · amounts in {symbol} ·{' '}
|
||
{data.indexed_from !== null && data.indexed_to !== null
|
||
? `blocks ${fmtHeight(data.indexed_from)}–${fmtHeight(data.indexed_to)}`
|
||
: 'nothing indexed yet'}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="scroll-x">
|
||
<table className="board">
|
||
<caption className="visually-hidden">
|
||
Each miner's share of blocks, what it was paid, and what it has sent on
|
||
</caption>
|
||
<thead>
|
||
<tr>
|
||
<th scope="col" className="left">
|
||
Miner
|
||
</th>
|
||
<th scope="col">Share</th>
|
||
<th scope="col">Mined</th>
|
||
<th
|
||
scope="col"
|
||
title="Everything ever credited to this address. A mining address is keyless, so nothing can transfer out of it and this number can only rise — it is a receipt total, not a holding."
|
||
>
|
||
Received
|
||
</th>
|
||
<th
|
||
scope="col"
|
||
title="Transfers whose sender the chain recorded. Wormhole sends record none, so a zero here is not evidence that nothing left."
|
||
>
|
||
Sent visibly
|
||
</th>
|
||
<th scope="col">Recipients</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{data.miners.map((m) => {
|
||
const recipients = m.flows?.recipients ?? null
|
||
return (
|
||
<tr key={m.miner}>
|
||
<td className="left">
|
||
<div className="call-cell">
|
||
<Link to={href({ chain, miner: m.miner, window: windowName })}>
|
||
{m.display}
|
||
</Link>
|
||
{/* Only where the name is not already the address: an
|
||
unattributed miner renders as its payout address, and
|
||
repeating it underneath is one string printed twice. */}
|
||
{m.address && m.attribution !== 'preimage' && (
|
||
<span className="runtime-type">{shortAddress(m.address)}</span>
|
||
)}
|
||
</div>
|
||
</td>
|
||
<td className="numeral">{fmtShare(m.share)}</td>
|
||
<td className="numeral">{amount(m.mined)}</td>
|
||
<td className="numeral">{amount(m.received)}</td>
|
||
<td className="numeral">{amount(m.flows?.sent)}</td>
|
||
<td className="numeral">{recipients === null ? '—' : fmtHeight(recipients)}</td>
|
||
</tr>
|
||
)
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p className="panel-note">
|
||
<strong>This chain hides who sends.</strong> A wormhole transfer records its recipient and
|
||
its amount but not its origin — on mainnet, 9,894 such transfers reached 654 recipients with
|
||
no sender recorded at all. So <em>sent visibly</em> and <em>recipients</em> count only the
|
||
transfers that named a sender, and{' '}
|
||
<strong>a zero in those columns is not evidence that nothing left</strong>. It is evidence
|
||
that nothing left by a route the chain writes down. Payouts here are very plausibly wormhole
|
||
transactions, and the privacy is a feature of the chain rather than a gap in this table.
|
||
</p>
|
||
<p className="panel-note">
|
||
<strong>
|
||
There is no column here that can tell you whether a miner is paying out, and there cannot
|
||
be.
|
||
</strong>{' '}
|
||
The chain pays a mining reward to an address derived from the miner's own reward
|
||
preimage — a Poseidon hash that no key signs for. Nothing can transfer out of it. The only
|
||
way to move the value is a wormhole exit, which credits its destination{' '}
|
||
<em>without debiting the source</em>. So <em>received</em> is a running total of everything
|
||
ever credited, it rises and never falls, and it does that identically for an operator who
|
||
pays out in full and one who pays out nothing. Measured over all of mainnet's history,
|
||
the largest miner's balance rose at every sample and fell at none.
|
||
</p>
|
||
<p className="panel-note">
|
||
That is also why this table no longer carries a “retained” percentage. It read close to 100%
|
||
for every miner on the chain, because it had to, and a number that is the same for everyone
|
||
reads as an accusation aimed at whoever happens to be at the top — which here is simply
|
||
whoever mined the most. The place any of this is observable is the{' '}
|
||
<Link to={href({ chain, index: 'wormhole' })}>wormhole page</Link>, which counts what has
|
||
gone in and come out without claiming to know who moved it.
|
||
</p>
|
||
<p className="panel-note">
|
||
The names are the weakest column and come from telemetry, not the chain — an operator
|
||
chooses what its node calls itself, so a name containing “pool” is a claim rather than a
|
||
fact. <em>Received</em> is chain state and is true now, while <em>mined</em>,{' '}
|
||
<em>sent visibly</em> and <em>recipients</em> are counted over the indexed range above; on a
|
||
chain read from genesis those agree, on one still being indexed backwards they do not.
|
||
</p>
|
||
</section>
|
||
)
|
||
}
|