fix: a miner's balance is a receipt total, and the supply figures were wrong
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>
This commit is contained in:
@@ -30,34 +30,6 @@ import { height as fmtHeight, share as fmtShare, shortAddress, tokens } from '..
|
||||
import { href } from '../lib/routes'
|
||||
import type { Window as WindowName } from '../api/generated/Window'
|
||||
|
||||
/**
|
||||
* Balance as a percentage of everything the chain has paid an address.
|
||||
*
|
||||
* **The one measure here that privacy cannot hide.** A wormhole transfer does
|
||||
* not record its sender, so `sent visibly` is blind to the very mechanism a
|
||||
* wormhole-held balance would use to pay out — 9,894 such transfers reached 654
|
||||
* recipients on mainnet with no sender recorded at all. But a payment still
|
||||
* debits the account, so a balance that has not fallen is a balance nothing has
|
||||
* left, by any route, private or otherwise.
|
||||
*
|
||||
* Above 100% means the address also received from somewhere besides mining.
|
||||
*/
|
||||
function retained(mined: string | null, balance: string | null): string {
|
||||
if (mined === null || balance === null) return '—'
|
||||
let held: bigint
|
||||
let paid: bigint
|
||||
try {
|
||||
held = BigInt(balance)
|
||||
paid = BigInt(mined)
|
||||
} catch {
|
||||
return '—'
|
||||
}
|
||||
// Nothing mined is not zero retention, it is a ratio with no denominator —
|
||||
// and most accounts that distribute widely on this chain mined nothing at all.
|
||||
if (paid === 0n) return '—'
|
||||
return `${Number((held * 1000n) / paid) / 10}%`
|
||||
}
|
||||
|
||||
export function DistributionPanel({
|
||||
chain,
|
||||
window: windowName,
|
||||
@@ -117,12 +89,11 @@ export function DistributionPanel({
|
||||
</th>
|
||||
<th scope="col">Share</th>
|
||||
<th scope="col">Mined</th>
|
||||
<th scope="col">Holds</th>
|
||||
<th
|
||||
scope="col"
|
||||
title="Balance as a percentage of everything the chain has paid this address. The one column privacy cannot hide: a payout debits the account whether or not its sender was recorded."
|
||||
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."
|
||||
>
|
||||
Retained
|
||||
Received
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
@@ -153,8 +124,7 @@ export function DistributionPanel({
|
||||
</td>
|
||||
<td className="numeral">{fmtShare(m.share)}</td>
|
||||
<td className="numeral">{amount(m.mined)}</td>
|
||||
<td className="numeral">{amount(m.balance)}</td>
|
||||
<td className="numeral">{retained(m.mined, m.balance)}</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>
|
||||
@@ -174,17 +144,30 @@ export function DistributionPanel({
|
||||
transactions, and the privacy is a feature of the chain rather than a gap in this table.
|
||||
</p>
|
||||
<p className="panel-note">
|
||||
<em>Retained</em> is the column privacy cannot hide. A payment debits the account whichever
|
||||
route it takes, so a balance that has not fallen below what the chain paid in is a balance
|
||||
nothing has left. Above 100% means the address also received from somewhere other than
|
||||
mining. It still cannot tell you whether an operator pays from a different wallet, settles
|
||||
off-chain, or is simply a solo miner keeping what it earned — which is the ordinary case,
|
||||
and which every mining address on this chain currently resembles.
|
||||
<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>Holds</em> is chain state and is true now, while <em>mined</em>,{' '}
|
||||
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>
|
||||
|
||||
@@ -85,6 +85,15 @@ export function NetworkPanel({
|
||||
locks.freezes === 0 &&
|
||||
locks.reserves === 0
|
||||
|
||||
// The vesting pot, which on this chain holds almost everything. Rendered as a
|
||||
// percentage because two eighteen-digit strings do not compare at a glance,
|
||||
// and 99.5% is the whole point.
|
||||
const pot = summary.vesting_pot
|
||||
const potShare =
|
||||
pot && pot.share_bps !== null
|
||||
? `${(pot.share_bps / 100).toFixed(pot.share_bps > 9900 ? 2 : 1)}%`
|
||||
: null
|
||||
|
||||
const spark = (pick: (d: NetworkSummary['days'][number]) => number, unit: string): SparkPoint[] =>
|
||||
summary.days.map((d) => ({
|
||||
value: pick(d),
|
||||
@@ -120,6 +129,20 @@ export function NetworkPanel({
|
||||
value={amount(summary.mined_since_genesis)}
|
||||
note="issuance less the endowment"
|
||||
/>
|
||||
<Field
|
||||
label="Held in the vesting pot"
|
||||
value={pot ? amount(pot.balance) : '—'}
|
||||
note={
|
||||
potShare === null
|
||||
? 'the vesting pallet\u2019s own account'
|
||||
: `${potShare} of issuance`
|
||||
}
|
||||
/>
|
||||
<Field
|
||||
label="Issuance outside the pot"
|
||||
value={amount(summary.issuance_outside_pot)}
|
||||
note="the ceiling on what has been distributed"
|
||||
/>
|
||||
<Field label="Fees collected" value={amount(summary.collected_fees)} />
|
||||
<Field label="Inactive issuance" value={amount(summary.inactive_issuance)} />
|
||||
<Field
|
||||
@@ -129,13 +152,22 @@ export function NetworkPanel({
|
||||
/>
|
||||
</div>
|
||||
<p className="panel-note">
|
||||
{pot && potShare !== null && (
|
||||
<>
|
||||
<strong>{potShare} of all issuance sits in one account</strong> — {pot.address}, which
|
||||
the vesting pallet owns and nobody holds a key to. This runtime vests from a pot
|
||||
rather than with locks: undistributed funds wait there and are paid out from it.{' '}
|
||||
</>
|
||||
)}
|
||||
{nothingLocked ? (
|
||||
<>
|
||||
<strong>Nothing is locked.</strong> All four of the balances pallet's
|
||||
immobilising maps — locks, holds, freezes and reserves — are empty, counted rather
|
||||
than assumed. Which is why there is no separate “circulating supply” figure here: it
|
||||
would equal total issuance exactly, and printing it would assert a distinction this
|
||||
chain does not currently make.{' '}
|
||||
All four of the balances pallet's immobilising maps — locks, holds, freezes and
|
||||
reserves — are empty, counted rather than assumed.{' '}
|
||||
<strong>That is not the same as nothing being withheld</strong>: this chain withholds
|
||||
with the pot above, which no lock records. There is still no “circulating supply”
|
||||
figure here, because funds leave the pot on a schedule this observer does not read and
|
||||
an address outside it may be unspendable for other reasons — but issuance alone would
|
||||
overstate what is in play by a factor of several hundred.{' '}
|
||||
</>
|
||||
) : (
|
||||
locks && (
|
||||
@@ -147,12 +179,14 @@ export function NetworkPanel({
|
||||
)}
|
||||
{summary.vesting_schedules !== null && summary.vesting_schedules > 0 && (
|
||||
<>
|
||||
There are {count(summary.vesting_schedules)} vesting schedules, shown as a count
|
||||
rather than a sum: this runtime's vesting does not touch the balances
|
||||
pallet's locks, so what it holds and when it releases would be a guess without
|
||||
reading the pallet's own arithmetic.
|
||||
The {count(summary.vesting_schedules)} vesting schedules are a count rather than a
|
||||
sum; the sum that matters is the pot, and it is read directly.{' '}
|
||||
</>
|
||||
)}
|
||||
<strong>Account balances are not added up anywhere on this site.</strong> On this chain
|
||||
they exceed total issuance and the gap grows: a wormhole exit credits its destination
|
||||
without debiting the note's source, so value that moves privately appears twice in
|
||||
any such total. Total issuance is the figure that counts each unit once.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user