Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

beacon state

Back to all declarations

ChainHash

untyped string

ChainHash, PeriodSeconds and RoundsPerDay are re-exported from p/splash/bls254, which holds the ONE copy of drand evmnet's identity. They are re-exported rather than restated so that a realm needing the cadence does not have to import a crypto package to get it, and so that there is still exactly one place the numbers are written down.

Value

"04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3"

PeriodSeconds

untyped bigint

ChainHash, PeriodSeconds and RoundsPerDay are re-exported from p/splash/bls254, which holds the ONE copy of drand evmnet's identity. They are re-exported rather than restated so that a realm needing the cadence does not have to import a crypto package to get it, and so that there is still exactly one place the numbers are written down.

Value

(3 <untyped> bigint)

RoundsPerDay

untyped bigint

ChainHash, PeriodSeconds and RoundsPerDay are re-exported from p/splash/bls254, which holds the ONE copy of drand evmnet's identity. They are re-exported rather than restated so that a realm needing the cadence does not have to import a crypto package to get it, and so that there is still exactly one place the numbers are written down.

Value

(28800 <untyped> bigint)

blocksPerDay

untyped bigint

blocksPerDay is the block-count day unit, ~3.3s blocks, copied from r/splash/game which documents the choice. A different package cannot reference another's unexported constant, so r/splash/game, r/splash/arcade/duel, r/splash/arcade/raffle and now this realm each restate it. IT IS HERE ONLY FOR EstimatedLatestRound, and only because this realm is the one place that holds both clocks at once: rounds, and the height at which each round arrived.

Value

(26182 <untyped> bigint)

groupKey

[]uint8 (len=128)

groupKey is drand evmnet's 128-byte G2 group public key (EIP-197 wire order), decoded once at init from the single canonical constant in p/splash/bls254. There is no rotation function and no admin path to change it: a different drand chain is a different realm.

Open
OID
0388a9…ede1:4
groupKey details (len=128)

+128 more Inspect slice

mustDecodeHex

func(s string) []uint8
Open
OID
0388a9…ede1:5
mustDecodeHex details

Inspect func

record

type

record is one published round: its 64-byte G1 signature and the chain height at which it was relayed. THE HEIGHT IS NOT BOOKKEEPING, IT IS THE ONLY REAL-TIME EVIDENCE A REALM ON THIS CHAIN CAN GET, and adding it is the reason this migration is safe at all. A drand round existing is a statement about wall-clock time -- round r cannot be signed before genesis + 3(r-1) seconds -- and this chain has no block-timestamp getter, so a published round is the only unforgeable "time has passed this far" a realm can observe. Recording the height it arrived at is what lets a consumer relate drand's clock to its own, and without it EstimatedLatestRound cannot exist and the raffle's round pin has no defence against a stale beacon at all (DRAND-MIGRATION.md section 5b).

Value

beacon.record

rounds

*v0.Tree

rounds maps a zero-padded round key to its record.

Open
OID
0388a9…ede1:8
rounds details

Inspect pointer

latestRound

int64

latestRound/latestHeight/haveLatest track the highest published round, and the height at which THAT round was published, without relying on tree iteration order. latestHeight is the height of the highest ROUND, not the height of the most recent Publish. Those differ when somebody relays an old round after a newer one. AND IT IS NOT WHAT EstimatedLatestRound EXTRAPOLATES FROM — anchorHeight is. This comment used to say it was, and that was true of the very first version of this realm only (see anchorScaled's account of why extrapolating from the highest round's own publication height is exploitable). latestHeight is now reported for the record, through LatestPublishedAtHeight and Render, and read by nothing that decides anything. The distance that bounds the estimate's error is ChainHeight() - anchorHeight; read anchorHeight for why measuring from anything else is unsound.

Value

21291587

latestHeight

int64

latestRound/latestHeight/haveLatest track the highest published round, and the height at which THAT round was published, without relying on tree iteration order. latestHeight is the height of the highest ROUND, not the height of the most recent Publish. Those differ when somebody relays an old round after a newer one. AND IT IS NOT WHAT EstimatedLatestRound EXTRAPOLATES FROM — anchorHeight is. This comment used to say it was, and that was true of the very first version of this realm only (see anchorScaled's account of why extrapolating from the highest round's own publication height is exploitable). latestHeight is now reported for the record, through LatestPublishedAtHeight and Render, and read by nothing that decides anything. The distance that bounds the estimate's error is ChainHeight() - anchorHeight; read anchorHeight for why measuring from anything else is unsound.

Value

236052

haveLatest

bool

latestRound/latestHeight/haveLatest track the highest published round, and the height at which THAT round was published, without relying on tree iteration order. latestHeight is the height of the highest ROUND, not the height of the most recent Publish. Those differ when somebody relays an old round after a newer one. AND IT IS NOT WHAT EstimatedLatestRound EXTRAPOLATES FROM — anchorHeight is. This comment used to say it was, and that was true of the very first version of this realm only (see anchorScaled's account of why extrapolating from the highest round's own publication height is exploitable). latestHeight is now reported for the record, through LatestPublishedAtHeight and Render, and read by nothing that decides anything. The distance that bounds the estimate's error is ChainHeight() - anchorHeight; read anchorHeight for why measuring from anything else is unsound.

Value

true

anchorScaled

int64

anchorScaled is the clock EstimatedLatestRound extrapolates from, and it is a MAXIMUM over every round ever relayed rather than a property of the highest one. anchorScaled = max over relays i of ( round_i*blocksPerDay - height_i*RoundsPerDay ) which is (round\_i - height\_i \* RoundsPerDay/blocksPerDay) scaled up by blocksPerDay so that no integer division is lost. It is the y-intercept of the round-versus-height line each observation implies, and taking the maximum keeps the highest line anybody has ever evidenced. WHY A MAXIMUM, AND NOT THE HIGHEST ROUND'S OWN (round, height) PAIR. The first version of this realm did the latter — it extrapolated from latestRound and the height at which latestRound was relayed — AND THAT WAS EXPLOITABLE. Relaying a round only slightly above latestRound but still far in the PAST resets the height anchor to now and collapses the estimate: latestRound=100 relayed at height 0, now at height 1,000,000 -> estimate 100 + 1,000,000*28800/26182 = 1,100,092 attacker relays round 200 (real, old, and > 100) at height 1,000,000 -> estimate 200 A consumer pinning estimate + offset would then pin a round whose randomness has been public for years, which is AUDIT-2026-09-28.md finding 10's precondition, bought for one Publish. A MAXIMUM CANNOT BE PUSHED DOWN: relaying an old round can only raise anchorScaled or leave it alone, and raising it moves a consumer's pinned round FURTHER into the future, which is the safe direction. AND IT CANNOT BE PUSHED ARBITRARILY HIGH EITHER, which is the other half of the argument. The best an attacker can do is relay drand's CURRENT head as early as possible, and nobody can produce a signature above the current head — so anchorScaled is bounded above by the true round-versus-height intercept. IT IS NOT SELF-CORRECTING, AND THE CLAIM THAT IT WAS IS THE DEFECT anchorHeight AND maxEstimateStaleBlocks EXIST TO CLOSE. DRAND-MIGRATION.md section 9.8 asserted, and this comment asserted with it, that a maximum is "self-correcting in both directions of block-time drift". THAT IS FALSE IN THE UPWARD DIRECTION, and it is false in the worst possible way — monotonically, permanently, on a realm with no owner and no upgrade path. Work it, because reading it will not show it: - blocks SLOWER than the assumed ~3.3s: each observation's intercept RISES over time, so the maximum tracks the MOST RECENT observation and any relay of drand's head pulls the estimate back onto the truth. Self-correcting, as claimed. - blocks FASTER: intercepts FALL over time, so the maximum keeps an EARLY one. The estimate then advances at RoundsPerDay/blocksPerDay = 1.099992 rounds per block AS A PURE FUNCTION OF CHAIN HEIGHT, for ever, REGARDLESS OF DRAND — while drand advances B/PeriodSeconds rounds per block for real average block time B. The two agree only at B = 86400/blocksPerDay = 3.29998 s. At ANY faster block time the estimate outruns drand's true head and CAN NEVER COME BACK DOWN, because a maximum does not fall and because relaying the true head no longer raises it (cand \< anchorScaled once est > truth). "Pins land later than intended, safe" was true only of the first day; it is monotone, and it ends in r/splash/arcade/duel's bound round never being signed inside VoidStale's window (~10 days at B = 3.0 s) and r/splash/arcade/raffle's pinned round never being signed inside the refund grace (~111 days at B = 3.0 s). See maxEstimateStaleBlocks for the arithmetic and AUDIT-2026-09-28.md finding 12. NOTE THE SHAPE OF THE TRADE, because it is the most instructive thing here: the fix that closed the down-push attack above is what REMOVED the only mechanism that corrected upward drift. The single-pair version self-corrected the instant anybody relayed the head — it re-anchored on every relay, which is exactly why it could be pushed down. Maximum: cannot be pushed down, cannot be corrected up. Single pair: can be corrected up, can be pushed down. The bound below is what makes the first of those safe, by refusing to extrapolate far enough for the uncorrectable direction to matter. AND THERE IS AN ACTIVE VARIANT OF THE UPWARD DRIFT, not merely a passive one. anchorScaled is the historical MAXIMUM intercept, so ANY period in which the chain ran slower than 3.29998 s/block raises it permanently, and one permissionless Publish of drand's current head captures that period into it. The captured intercept is CORRECT at the moment of capture — the relay proves drand really did advance that far — but it is retained for ever, so if the chain then speeds up, the estimate resumes outrunning drand from the higher line and no later relay can lower it. A chain HALT is the extreme case: no height passes while drand advances D/PeriodSeconds rounds, and a height-denominated staleness measure cannot see a halt at all (see maxEstimateStaleBlocks' "WHAT THIS DOES NOT BOUND"). OVERFLOW HEADROOM: round\*blocksPerDay at drand's ~2.1e7 is ~5.5e11, and height\*RoundsPerDay at a height of 1e9 is ~2.9e13. Both are five orders of magnitude inside int64. anchorHeight IS THE HEIGHT OF THE RELAY THAT MOST RECENTLY RAISED anchorScaled, and it is NOT "the height of the most recent Publish". Every error in this realm's extrapolation is proportional to ChainHeight() - anchorHeight and to nothing else, so that is the distance the bound below measures. Two reasons the other choice is wrong, and the second one is an attack: 1. A RELAY THAT DOES NOT RAISE THE ANCHOR DOES NOT REDUCE THE ERROR. Once the estimate has drifted above drand's head, relaying the head leaves anchorScaled exactly where it was — that is the whole defect above — so a "most recent Publish" measure would report a fresh beacon while the estimate went on being wrong. 2. "MOST RECENT PUBLISH" FRESHNESS IS FORGEABLE FOR THE COST OF ONE Publish. Any unrelayed old round — drand has ~2.1e7 of them, all public — would reset it. So an attacker could hold the beacon "fresh" indefinitely while the estimate drifted arbitrarily far, which is finding 10's precondition bought for ~16.97M gas a relay. RAISING THE ANCHOR IS HARDER TO FORGE THAN A "LAST Publish" FLAG, AND IT IS NOT UNFORGEABLE. The stronger claim stood here and is RETIRED RATHER THAN QUIETLY DROPPED, because the bound below was justified by it. THE RETIRED TEXT, VERBATIM: RAISING THE ANCHOR, BY CONTRAST, IS UNFORGEABLE, and this is the property that makes the bound sound rather than merely conservative. cand > anchorScaled reduces to round > est(h): raising the anchor requires a valid signature for a round ABOVE this realm's current estimate. Nobody below the DKG threshold can sign a round drand has not reached, so the anchor can be refreshed ONLY by a relay that PROVES the estimate was behind drand's real head — which is exactly the case in which re-anchoring is correct. (END OF THE RETIRED TEXT.) EVERY SENTENCE OF IT IS TRUE EXCEPT THE WORD "ONLY", AND "ONLY" WAS THE LOAD-BEARING ONE. cand > anchorScaled does reduce to round > est(h), and nobody below the DKG threshold can sign a round drand has not reached. What does not follow is that the relay proves anything about NOW: the round that has to be signed is est+1, and when the estimate LAGS — the only situation this bound exists for — est+1 is a round drand passed long ago, one of ~2.1e7 public past rounds whose signature costs an HTTP GET. AND IT ALWAYS WORKS, not sometimes. From est = floor((anchorScaled + h\*RoundsPerDay)/ blocksPerDay) it follows that cand(est+1) = (est+1)*blocksPerDay - h*RoundsPerDay > anchorScaled ALWAYS so relaying est+1 ALWAYS raises the anchor, ALWAYS sets anchorHeight = ChainHeight() so that StaleBlocks() reads 0, and ALWAYS advances the estimate by EXACTLY ONE ROUND — three seconds of drand time bought with one Publish, in exchange for a whole fresh budget of maxEstimateStaleBlocks. Repeat once before each budget expires and the lag grows without limit while the freshness signal reads zero throughout. This is not a derivation to be trusted: it is asserted against the running realm by TestKnownWeaknessRelayingTheRoundJustAboveTheEstimateBuysTheWholeStalenessBudgetBack. SO STATE THE BOUND'S GUARANTEE AT ITS REAL STRENGTH. A raise proves the estimate WAS behind drand's head at the moment of the relay. It does NOT prove the estimate has CAUGHT UP, and the MAGNITUDE of a raise is unconstrained — est+1 and drand's true head are equally valid relays and the realm cannot tell them apart. THE STALENESS BOUND IS THEREFORE NOT FORGERY-PROOF. It is a lower bound on how much drand-time has passed, enforced against a caller who must at least produce a real signature above the estimate; it is not a bound on how far the estimate has fallen behind. AND NO ON-CHAIN FRESHNESS PREDICATE CAN BE, so this is a floor and not a bug to fix here. A drand signature bounds drand's head from BELOW — round R exists implies the head is at least R — and what a freshness test needs is a bound from ABOVE, which no signature and no chain height can supply. DRAND-MIGRATION.md sections 5b and 5c already state that floor and its consequence for every round-number pin; read it there rather than re-deriving it. What remains defensible about measuring from anchorHeight is reason 1 and the WEAKER half of reason 2: an old round BELOW the estimate resets nothing at all, so the bound is still strictly better than "blocks since the last Publish", which any round whatsoever resets. Strictly better is not sound. And the converse is the honest cost, recorded rather than discovered: when the estimate is AHEAD of drand's head, NOBODY can refresh the anchor, so the refusal below becomes permanent until the chain's own block time falls back behind 3.29998 s. See AUDIT-2026-09-28.md finding 12 for what that means for deployment.

Value

550658033234

anchorHeight

int64

anchorScaled is the clock EstimatedLatestRound extrapolates from, and it is a MAXIMUM over every round ever relayed rather than a property of the highest one. anchorScaled = max over relays i of ( round_i*blocksPerDay - height_i*RoundsPerDay ) which is (round\_i - height\_i \* RoundsPerDay/blocksPerDay) scaled up by blocksPerDay so that no integer division is lost. It is the y-intercept of the round-versus-height line each observation implies, and taking the maximum keeps the highest line anybody has ever evidenced. WHY A MAXIMUM, AND NOT THE HIGHEST ROUND'S OWN (round, height) PAIR. The first version of this realm did the latter — it extrapolated from latestRound and the height at which latestRound was relayed — AND THAT WAS EXPLOITABLE. Relaying a round only slightly above latestRound but still far in the PAST resets the height anchor to now and collapses the estimate: latestRound=100 relayed at height 0, now at height 1,000,000 -> estimate 100 + 1,000,000*28800/26182 = 1,100,092 attacker relays round 200 (real, old, and > 100) at height 1,000,000 -> estimate 200 A consumer pinning estimate + offset would then pin a round whose randomness has been public for years, which is AUDIT-2026-09-28.md finding 10's precondition, bought for one Publish. A MAXIMUM CANNOT BE PUSHED DOWN: relaying an old round can only raise anchorScaled or leave it alone, and raising it moves a consumer's pinned round FURTHER into the future, which is the safe direction. AND IT CANNOT BE PUSHED ARBITRARILY HIGH EITHER, which is the other half of the argument. The best an attacker can do is relay drand's CURRENT head as early as possible, and nobody can produce a signature above the current head — so anchorScaled is bounded above by the true round-versus-height intercept. IT IS NOT SELF-CORRECTING, AND THE CLAIM THAT IT WAS IS THE DEFECT anchorHeight AND maxEstimateStaleBlocks EXIST TO CLOSE. DRAND-MIGRATION.md section 9.8 asserted, and this comment asserted with it, that a maximum is "self-correcting in both directions of block-time drift". THAT IS FALSE IN THE UPWARD DIRECTION, and it is false in the worst possible way — monotonically, permanently, on a realm with no owner and no upgrade path. Work it, because reading it will not show it: - blocks SLOWER than the assumed ~3.3s: each observation's intercept RISES over time, so the maximum tracks the MOST RECENT observation and any relay of drand's head pulls the estimate back onto the truth. Self-correcting, as claimed. - blocks FASTER: intercepts FALL over time, so the maximum keeps an EARLY one. The estimate then advances at RoundsPerDay/blocksPerDay = 1.099992 rounds per block AS A PURE FUNCTION OF CHAIN HEIGHT, for ever, REGARDLESS OF DRAND — while drand advances B/PeriodSeconds rounds per block for real average block time B. The two agree only at B = 86400/blocksPerDay = 3.29998 s. At ANY faster block time the estimate outruns drand's true head and CAN NEVER COME BACK DOWN, because a maximum does not fall and because relaying the true head no longer raises it (cand \< anchorScaled once est > truth). "Pins land later than intended, safe" was true only of the first day; it is monotone, and it ends in r/splash/arcade/duel's bound round never being signed inside VoidStale's window (~10 days at B = 3.0 s) and r/splash/arcade/raffle's pinned round never being signed inside the refund grace (~111 days at B = 3.0 s). See maxEstimateStaleBlocks for the arithmetic and AUDIT-2026-09-28.md finding 12. NOTE THE SHAPE OF THE TRADE, because it is the most instructive thing here: the fix that closed the down-push attack above is what REMOVED the only mechanism that corrected upward drift. The single-pair version self-corrected the instant anybody relayed the head — it re-anchored on every relay, which is exactly why it could be pushed down. Maximum: cannot be pushed down, cannot be corrected up. Single pair: can be corrected up, can be pushed down. The bound below is what makes the first of those safe, by refusing to extrapolate far enough for the uncorrectable direction to matter. AND THERE IS AN ACTIVE VARIANT OF THE UPWARD DRIFT, not merely a passive one. anchorScaled is the historical MAXIMUM intercept, so ANY period in which the chain ran slower than 3.29998 s/block raises it permanently, and one permissionless Publish of drand's current head captures that period into it. The captured intercept is CORRECT at the moment of capture — the relay proves drand really did advance that far — but it is retained for ever, so if the chain then speeds up, the estimate resumes outrunning drand from the higher line and no later relay can lower it. A chain HALT is the extreme case: no height passes while drand advances D/PeriodSeconds rounds, and a height-denominated staleness measure cannot see a halt at all (see maxEstimateStaleBlocks' "WHAT THIS DOES NOT BOUND"). OVERFLOW HEADROOM: round\*blocksPerDay at drand's ~2.1e7 is ~5.5e11, and height\*RoundsPerDay at a height of 1e9 is ~2.9e13. Both are five orders of magnitude inside int64. anchorHeight IS THE HEIGHT OF THE RELAY THAT MOST RECENTLY RAISED anchorScaled, and it is NOT "the height of the most recent Publish". Every error in this realm's extrapolation is proportional to ChainHeight() - anchorHeight and to nothing else, so that is the distance the bound below measures. Two reasons the other choice is wrong, and the second one is an attack: 1. A RELAY THAT DOES NOT RAISE THE ANCHOR DOES NOT REDUCE THE ERROR. Once the estimate has drifted above drand's head, relaying the head leaves anchorScaled exactly where it was — that is the whole defect above — so a "most recent Publish" measure would report a fresh beacon while the estimate went on being wrong. 2. "MOST RECENT PUBLISH" FRESHNESS IS FORGEABLE FOR THE COST OF ONE Publish. Any unrelayed old round — drand has ~2.1e7 of them, all public — would reset it. So an attacker could hold the beacon "fresh" indefinitely while the estimate drifted arbitrarily far, which is finding 10's precondition bought for ~16.97M gas a relay. RAISING THE ANCHOR IS HARDER TO FORGE THAN A "LAST Publish" FLAG, AND IT IS NOT UNFORGEABLE. The stronger claim stood here and is RETIRED RATHER THAN QUIETLY DROPPED, because the bound below was justified by it. THE RETIRED TEXT, VERBATIM: RAISING THE ANCHOR, BY CONTRAST, IS UNFORGEABLE, and this is the property that makes the bound sound rather than merely conservative. cand > anchorScaled reduces to round > est(h): raising the anchor requires a valid signature for a round ABOVE this realm's current estimate. Nobody below the DKG threshold can sign a round drand has not reached, so the anchor can be refreshed ONLY by a relay that PROVES the estimate was behind drand's real head — which is exactly the case in which re-anchoring is correct. (END OF THE RETIRED TEXT.) EVERY SENTENCE OF IT IS TRUE EXCEPT THE WORD "ONLY", AND "ONLY" WAS THE LOAD-BEARING ONE. cand > anchorScaled does reduce to round > est(h), and nobody below the DKG threshold can sign a round drand has not reached. What does not follow is that the relay proves anything about NOW: the round that has to be signed is est+1, and when the estimate LAGS — the only situation this bound exists for — est+1 is a round drand passed long ago, one of ~2.1e7 public past rounds whose signature costs an HTTP GET. AND IT ALWAYS WORKS, not sometimes. From est = floor((anchorScaled + h\*RoundsPerDay)/ blocksPerDay) it follows that cand(est+1) = (est+1)*blocksPerDay - h*RoundsPerDay > anchorScaled ALWAYS so relaying est+1 ALWAYS raises the anchor, ALWAYS sets anchorHeight = ChainHeight() so that StaleBlocks() reads 0, and ALWAYS advances the estimate by EXACTLY ONE ROUND — three seconds of drand time bought with one Publish, in exchange for a whole fresh budget of maxEstimateStaleBlocks. Repeat once before each budget expires and the lag grows without limit while the freshness signal reads zero throughout. This is not a derivation to be trusted: it is asserted against the running realm by TestKnownWeaknessRelayingTheRoundJustAboveTheEstimateBuysTheWholeStalenessBudgetBack. SO STATE THE BOUND'S GUARANTEE AT ITS REAL STRENGTH. A raise proves the estimate WAS behind drand's head at the moment of the relay. It does NOT prove the estimate has CAUGHT UP, and the MAGNITUDE of a raise is unconstrained — est+1 and drand's true head are equally valid relays and the realm cannot tell them apart. THE STALENESS BOUND IS THEREFORE NOT FORGERY-PROOF. It is a lower bound on how much drand-time has passed, enforced against a caller who must at least produce a real signature above the estimate; it is not a bound on how far the estimate has fallen behind. AND NO ON-CHAIN FRESHNESS PREDICATE CAN BE, so this is a floor and not a bug to fix here. A drand signature bounds drand's head from BELOW — round R exists implies the head is at least R — and what a freshness test needs is a bound from ABOVE, which no signature and no chain height can supply. DRAND-MIGRATION.md sections 5b and 5c already state that floor and its consequence for every round-number pin; read it there rather than re-deriving it. What remains defensible about measuring from anchorHeight is reason 1 and the WEAKER half of reason 2: an old round BELOW the estimate resets nothing at all, so the bound is still strictly better than "blocks since the last Publish", which any round whatsoever resets. Strictly better is not sound. And the converse is the honest cost, recorded rather than discovered: when the estimate is AHEAD of drand's head, NOBODY can refresh the anchor, so the refusal below becomes permanent until the chain's own block time falls back behind 3.29998 s. See AUDIT-2026-09-28.md finding 12 for what that means for deployment.

Value

236052

haveAnchor

bool

anchorScaled is the clock EstimatedLatestRound extrapolates from, and it is a MAXIMUM over every round ever relayed rather than a property of the highest one. anchorScaled = max over relays i of ( round_i*blocksPerDay - height_i*RoundsPerDay ) which is (round\_i - height\_i \* RoundsPerDay/blocksPerDay) scaled up by blocksPerDay so that no integer division is lost. It is the y-intercept of the round-versus-height line each observation implies, and taking the maximum keeps the highest line anybody has ever evidenced. WHY A MAXIMUM, AND NOT THE HIGHEST ROUND'S OWN (round, height) PAIR. The first version of this realm did the latter — it extrapolated from latestRound and the height at which latestRound was relayed — AND THAT WAS EXPLOITABLE. Relaying a round only slightly above latestRound but still far in the PAST resets the height anchor to now and collapses the estimate: latestRound=100 relayed at height 0, now at height 1,000,000 -> estimate 100 + 1,000,000*28800/26182 = 1,100,092 attacker relays round 200 (real, old, and > 100) at height 1,000,000 -> estimate 200 A consumer pinning estimate + offset would then pin a round whose randomness has been public for years, which is AUDIT-2026-09-28.md finding 10's precondition, bought for one Publish. A MAXIMUM CANNOT BE PUSHED DOWN: relaying an old round can only raise anchorScaled or leave it alone, and raising it moves a consumer's pinned round FURTHER into the future, which is the safe direction. AND IT CANNOT BE PUSHED ARBITRARILY HIGH EITHER, which is the other half of the argument. The best an attacker can do is relay drand's CURRENT head as early as possible, and nobody can produce a signature above the current head — so anchorScaled is bounded above by the true round-versus-height intercept. IT IS NOT SELF-CORRECTING, AND THE CLAIM THAT IT WAS IS THE DEFECT anchorHeight AND maxEstimateStaleBlocks EXIST TO CLOSE. DRAND-MIGRATION.md section 9.8 asserted, and this comment asserted with it, that a maximum is "self-correcting in both directions of block-time drift". THAT IS FALSE IN THE UPWARD DIRECTION, and it is false in the worst possible way — monotonically, permanently, on a realm with no owner and no upgrade path. Work it, because reading it will not show it: - blocks SLOWER than the assumed ~3.3s: each observation's intercept RISES over time, so the maximum tracks the MOST RECENT observation and any relay of drand's head pulls the estimate back onto the truth. Self-correcting, as claimed. - blocks FASTER: intercepts FALL over time, so the maximum keeps an EARLY one. The estimate then advances at RoundsPerDay/blocksPerDay = 1.099992 rounds per block AS A PURE FUNCTION OF CHAIN HEIGHT, for ever, REGARDLESS OF DRAND — while drand advances B/PeriodSeconds rounds per block for real average block time B. The two agree only at B = 86400/blocksPerDay = 3.29998 s. At ANY faster block time the estimate outruns drand's true head and CAN NEVER COME BACK DOWN, because a maximum does not fall and because relaying the true head no longer raises it (cand \< anchorScaled once est > truth). "Pins land later than intended, safe" was true only of the first day; it is monotone, and it ends in r/splash/arcade/duel's bound round never being signed inside VoidStale's window (~10 days at B = 3.0 s) and r/splash/arcade/raffle's pinned round never being signed inside the refund grace (~111 days at B = 3.0 s). See maxEstimateStaleBlocks for the arithmetic and AUDIT-2026-09-28.md finding 12. NOTE THE SHAPE OF THE TRADE, because it is the most instructive thing here: the fix that closed the down-push attack above is what REMOVED the only mechanism that corrected upward drift. The single-pair version self-corrected the instant anybody relayed the head — it re-anchored on every relay, which is exactly why it could be pushed down. Maximum: cannot be pushed down, cannot be corrected up. Single pair: can be corrected up, can be pushed down. The bound below is what makes the first of those safe, by refusing to extrapolate far enough for the uncorrectable direction to matter. AND THERE IS AN ACTIVE VARIANT OF THE UPWARD DRIFT, not merely a passive one. anchorScaled is the historical MAXIMUM intercept, so ANY period in which the chain ran slower than 3.29998 s/block raises it permanently, and one permissionless Publish of drand's current head captures that period into it. The captured intercept is CORRECT at the moment of capture — the relay proves drand really did advance that far — but it is retained for ever, so if the chain then speeds up, the estimate resumes outrunning drand from the higher line and no later relay can lower it. A chain HALT is the extreme case: no height passes while drand advances D/PeriodSeconds rounds, and a height-denominated staleness measure cannot see a halt at all (see maxEstimateStaleBlocks' "WHAT THIS DOES NOT BOUND"). OVERFLOW HEADROOM: round\*blocksPerDay at drand's ~2.1e7 is ~5.5e11, and height\*RoundsPerDay at a height of 1e9 is ~2.9e13. Both are five orders of magnitude inside int64. anchorHeight IS THE HEIGHT OF THE RELAY THAT MOST RECENTLY RAISED anchorScaled, and it is NOT "the height of the most recent Publish". Every error in this realm's extrapolation is proportional to ChainHeight() - anchorHeight and to nothing else, so that is the distance the bound below measures. Two reasons the other choice is wrong, and the second one is an attack: 1. A RELAY THAT DOES NOT RAISE THE ANCHOR DOES NOT REDUCE THE ERROR. Once the estimate has drifted above drand's head, relaying the head leaves anchorScaled exactly where it was — that is the whole defect above — so a "most recent Publish" measure would report a fresh beacon while the estimate went on being wrong. 2. "MOST RECENT PUBLISH" FRESHNESS IS FORGEABLE FOR THE COST OF ONE Publish. Any unrelayed old round — drand has ~2.1e7 of them, all public — would reset it. So an attacker could hold the beacon "fresh" indefinitely while the estimate drifted arbitrarily far, which is finding 10's precondition bought for ~16.97M gas a relay. RAISING THE ANCHOR IS HARDER TO FORGE THAN A "LAST Publish" FLAG, AND IT IS NOT UNFORGEABLE. The stronger claim stood here and is RETIRED RATHER THAN QUIETLY DROPPED, because the bound below was justified by it. THE RETIRED TEXT, VERBATIM: RAISING THE ANCHOR, BY CONTRAST, IS UNFORGEABLE, and this is the property that makes the bound sound rather than merely conservative. cand > anchorScaled reduces to round > est(h): raising the anchor requires a valid signature for a round ABOVE this realm's current estimate. Nobody below the DKG threshold can sign a round drand has not reached, so the anchor can be refreshed ONLY by a relay that PROVES the estimate was behind drand's real head — which is exactly the case in which re-anchoring is correct. (END OF THE RETIRED TEXT.) EVERY SENTENCE OF IT IS TRUE EXCEPT THE WORD "ONLY", AND "ONLY" WAS THE LOAD-BEARING ONE. cand > anchorScaled does reduce to round > est(h), and nobody below the DKG threshold can sign a round drand has not reached. What does not follow is that the relay proves anything about NOW: the round that has to be signed is est+1, and when the estimate LAGS — the only situation this bound exists for — est+1 is a round drand passed long ago, one of ~2.1e7 public past rounds whose signature costs an HTTP GET. AND IT ALWAYS WORKS, not sometimes. From est = floor((anchorScaled + h\*RoundsPerDay)/ blocksPerDay) it follows that cand(est+1) = (est+1)*blocksPerDay - h*RoundsPerDay > anchorScaled ALWAYS so relaying est+1 ALWAYS raises the anchor, ALWAYS sets anchorHeight = ChainHeight() so that StaleBlocks() reads 0, and ALWAYS advances the estimate by EXACTLY ONE ROUND — three seconds of drand time bought with one Publish, in exchange for a whole fresh budget of maxEstimateStaleBlocks. Repeat once before each budget expires and the lag grows without limit while the freshness signal reads zero throughout. This is not a derivation to be trusted: it is asserted against the running realm by TestKnownWeaknessRelayingTheRoundJustAboveTheEstimateBuysTheWholeStalenessBudgetBack. SO STATE THE BOUND'S GUARANTEE AT ITS REAL STRENGTH. A raise proves the estimate WAS behind drand's head at the moment of the relay. It does NOT prove the estimate has CAUGHT UP, and the MAGNITUDE of a raise is unconstrained — est+1 and drand's true head are equally valid relays and the realm cannot tell them apart. THE STALENESS BOUND IS THEREFORE NOT FORGERY-PROOF. It is a lower bound on how much drand-time has passed, enforced against a caller who must at least produce a real signature above the estimate; it is not a bound on how far the estimate has fallen behind. AND NO ON-CHAIN FRESHNESS PREDICATE CAN BE, so this is a floor and not a bug to fix here. A drand signature bounds drand's head from BELOW — round R exists implies the head is at least R — and what a freshness test needs is a bound from ABOVE, which no signature and no chain height can supply. DRAND-MIGRATION.md sections 5b and 5c already state that floor and its consequence for every round-number pin; read it there rather than re-deriving it. What remains defensible about measuring from anchorHeight is reason 1 and the WEAKER half of reason 2: an old round BELOW the estimate resets nothing at all, so the bound is still strictly better than "blocks since the last Publish", which any round whatsoever resets. Strictly better is not sound. And the converse is the honest cost, recorded rather than discovered: when the estimate is AHEAD of drand's head, NOBODY can refresh the anchor, so the refusal below becomes permanent until the chain's own block time falls back behind 3.29998 s. See AUDIT-2026-09-28.md finding 12 for what that means for deployment.

Value

true

maxEstimateStaleBlocks

untyped bigint

maxEstimateStaleBlocks is how far past the anchor EstimatedLatestRound is willing to extrapolate: one day of blocks. Past it, it REFUSES — see EstimatedLatestRoundWithin. THE DERIVATION. Let S = ChainHeight() - anchorHeight (blocks since the anchor was last raised) A = 86400/blocksPerDay = 3.29998 s (the block time this realm assumes) B = the chain's real AVERAGE block time over those S blocks The extrapolation reads S\*RoundsPerDay/blocksPerDay = S\*A/PeriodSeconds rounds off the anchor while drand advances S\*B/PeriodSeconds rounds, so est - truth = S*(A - B)/PeriodSeconds rounds = S*(A - B) SECONDS of drand time ONE CONDITION BOUNDS BOTH DIRECTIONS OF ERROR, which is the reason this bound belongs here and not in the consumers. The expression above is positive when the chain is FASTER than assumed (the estimate outruns drand — the unbounded, uncorrectable direction described at anchorScaled) and negative when it is slower (the estimate lags — the staleness error section 5b of DRAND-MIGRATION.md was about). Both are proportional to the SAME S, so |est - truth| \<= S\*|A - B| seconds whichever way the drift goes, and refusing above a bound on S bounds the error above and below at once. THE BAND THIS IS STATED AGAINST: B within 10% of A, i.e. 2.97 s to 3.63 s. That is the tolerance DRAND-MIGRATION.md section 5b already reasons in, and |A - B| \<= 0.33 s at its edges. At S = blocksPerDay the admitted error is therefore 26182 * 0.33 = 8640 seconds = 2.4 hours of drand time WHY ONE DAY, AND WHY THAT NUMBER IS THE RAFFLE'S AND NOT THE DUEL'S. The two margins at risk differ by more than two orders of magnitude, and no single bound serves both: - r/splash/arcade/raffle pins a round pinOffsetRounds = 8 days out against a week of sales, a nominal margin of 86400 s. At the slow edge of the band the week itself lengthens (it is a BLOCK count while the pin is a ROUND count), leaving 25915 s. 2.4 hours of error fits inside that with 4.8 hours to spare — a 3x margin — so one day of blocks is a sound bound for the raffle, and the raffle asserts exactly that relation at compile time beside its own pinOffsetRounds guard. - r/splash/arcade/duel binds a round duelBindOffsetRounds = 100 rounds out: a margin of 300 SECONDS. 2.4 hours of error destroys it 29 times over. Its tolerance has to be ~450 blocks, and it passes that to EstimatedLatestRoundWithin itself. SO THE BEACON DOES BOTH: a compile-time BACKSTOP no consumer can exceed or forget (this constant, set at the scale of the coarsest consumer), plus a per-call tolerance a consumer with a finer margin must state for itself. A fifth consumer that forgets to think about staleness gets the backstop; one that thinks about it can only ever ask for LESS. That asymmetry is deliberate: EstimatedLatestRoundWithin REFUSES a tolerance above this constant, so the backstop cannot be opted out of. WHAT THIS DOES NOT BOUND, stated here so it is not rediscovered as a surprise: - A CHAIN HALT. S counts blocks, so an interval producing no blocks contributes nothing to S while drand advances 28800 rounds a day through it. Equivalently: a halt inflates B, and this bound only holds under the stated band. That is the floor DRAND-MIGRATION.md section 5b records — no realm on this toolchain can obtain an UPPER bound on elapsed real time — and no constant here can move it. - A SUSTAINED BLOCK TIME OUTSIDE THE BAND. At B = 3.0 s the admitted error at this bound is 26182\*0.29998 = 7854 s, still inside the raffle's margin; at B = 2.5 s it is 20938 s and is not. The band is an assumption, written down rather than implied, exactly as blocksPerDay itself is.

Value

(26182 <untyped> bigint)

MaxEstimateStaleBlocks

untyped bigint

MaxEstimateStaleBlocks is maxEstimateStaleBlocks, exported so that a consumer can state its own tolerance as a fraction of the backstop, assert a relation to it at COMPILE TIME (r/splash/arcade/raffle does), or tell a user how fresh the beacon has to be. A const, so no caller can move it.

Value

(26182 <untyped> bigint)

roundKeyWidth

untyped bigint

roundKeyWidth is enough decimal digits for any non-negative int64 (max is 9223372036854775807, 19 digits).

Value

(19 <untyped> bigint)

zeroPad

untyped string

Value

"0000000000000000000"

roundKey

func(round int64) string

roundKey renders round as a fixed-width, zero-padded decimal string so that avl.Tree's lexicographic key order matches numeric round order. Beacon rounds are round counters and are assumed non-negative; a negative round still stores and retrieves correctly by exact key, it just will not sort where a caller might expect relative to non-negative rounds.

Open
OID
0388a9…ede1:16
roundKey details

Inspect func

Publish

func(round int64, sigHex string)

Publish verifies sig for round against drand evmnet's group key and stores it. PERMISSIONLESS: anyone may relay any round, because validity is what matters and not the sender. Panics if the round is already published or the signature does not verify — including if sigHex is not valid hex, which is just another shape of "not a valid signature". THE ROUND NUMBER IS THE MESSAGE and the caller supplies nothing else: the signed digest is keccak256 of the round as a big-endian uint64 (bls254.DrandRoundMessage), so a relayer cannot choose what a round's randomness is, only whether to relay it. The chain is UNCHAINED, so no previous signature is needed and any round verifies on its own. bls254.VerifyDrand, NOT bls254.Verify. The two implement different schemes and each rejects every signature of the other; Verify is the retired SPLASH try-and-increment scheme and is left untouched, still exercised by p/splash/bls254's own suite. COST: 16,970,375 gas measured on this toolchain (zz\_gas\_test.gno), 0.57% of the 3e9 block limit — about 176 to a block. Of it, 13,396,451 is bls254.VerifyDrand and 12,004,207 of THAT is the RFC 9380 hash-to-curve rather than the pairing: 90% of the verification, 71% of the whole Publish. (CORRECTED 2026-09-30: this said "98%", which was the pre-optimisation ratio; DRAND-MIGRATION.md section 9.5.) It fits with a wide margin, and it is also the reason this realm cannot mirror drand. A RELAYER CANNOT PUSH latestRound INTO THE FUTURE, and a great deal now rests on that. Under the SPLASH key one Publish of a far-future round moved latest arbitrarily far in a single transaction, which was the operator-triggered half of AUDIT-2026-09-28.md finding 7. drand cannot sign a round whose time has not come, so latestRound is bounded above by drand's real head, and that attack is dead.

Open
OID
0388a9…ede1:17
Publish details

Inspect func

Randomness

func(round int64) string

Randomness returns sha256(signature) for a published round, hex-encoded. Panics if the round has not been published. This is drand's own derivation, not one of ours: the "randomness" field /public/\<r> serves is exactly sha256 of the signature it serves beside it, which makes every real round an independent oracle for this function. testdata's fixtures were checked against it.

Open
OID
0388a9…ede1:19
Randomness details

Inspect func

Published

func(round int64) bool

Published reports whether a round has been published. WHAT IT MEANS NOW: "somebody has relayed this round to this chain". NOT "this round's randomness exists" (it may exist on drand and simply not be here yet) and NOT "this round's randomness is obtainable" (it always is, from drand). Every caller that treats false as "lost" is wrong; see DRAND-MIGRATION.md section 3.

Open
OID
0388a9…ede1:20
Published details

Inspect func

PublishedAtHeight

func(round int64) int64

PublishedAtHeight returns the chain height at which a round was relayed. Panics if the round has not been published.

Open
OID
0388a9…ede1:21
PublishedAtHeight details

Inspect func

Latest

func() (int64, string)

Latest returns the highest published round and its randomness. Panics if no round has been published yet. "HIGHEST PUBLISHED", WHICH IS NOT "CURRENT". Under an on-demand record nothing relays routinely, so this can be arbitrarily stale — a week old, a year old. A CONSUMER PINNING A FUTURE ROUND MUST NOT USE THIS AS "now": use EstimatedLatestRound, which corrects for the staleness using a clock no relayer controls. Reading this as the current round is exactly how the raffle's round pin would silently re-open AUDIT-2026-09-28.md finding 10.

Open
OID
0388a9…ede1:22
Latest details

Inspect func

LatestPublishedAtHeight

func() int64

LatestPublishedAtHeight returns the chain height at which the highest published round was relayed. Panics if no round has been published yet.

Open
OID
0388a9…ede1:23
LatestPublishedAtHeight details

Inspect func

EstimatedLatestRound

func() int64

EstimatedLatestRound is this realm's best estimate of the round drand is currently on. Panics if no round has ever been published. IT IS AN ESTIMATE AND THE NAME SAYS SO. There is no way to know the true current round on-chain: proving "round r is not signed yet" needs an UPPER bound on elapsed real time, published rounds give only a LOWER bound, and chain height gives none at all because a halted chain advances no height while drand advances 28800 rounds a day. Every round-number pin in this system is therefore an assumption about this chain keeping pace with drand, and this function is where that assumption lives instead of being spread across three realms. THE CONSTRUCTION: take anchorScaled — the highest round-versus-height intercept any relay has ever evidenced — and read this height off that line. It is exactly latestRound at the moment latestRound is relayed. READ anchorScaled BEFORE CHANGING THIS: extrapolating from the highest round's own publication height instead is the obvious simplification, and it is exploitable. WHY IT IS STRICTLY BETTER THAN Latest() FOR A PIN. A consumer pinning Latest() + offset measures its offset from whenever somebody last happened to relay something, so the pin drifts backwards in real time as the beacon goes quiet — and the raffle's offset is exactly one day larger than a week, so a single day of staleness puts the pinned round's randomness on the public API BEFORE the week's ticket sales close, which is finding 10's last-buyer attack verbatim. The correction removes that failure entirely in the honest case. THE ERROR IS BOUNDED BY REFUSAL, AND THAT IS NEW AS OF THE STALENESS BOUND. The conversion assumes A = 86400/blocksPerDay = 3.29998 s blocks, so its error is S\*(A - B) seconds of drand time for S blocks since the anchor was last raised and a real average block time B — 2.4 hours at one day of S and a 10% drift in B, NOT the ~14 minutes this comment claimed until the figure was re-derived (that number divided by the period twice; see maxEstimateStaleBlocks and AUDIT-2026-09-28.md finding 13). Unbounded S makes it unbounded in BOTH directions, so this function now REFUSES above maxEstimateStaleBlocks rather than returning a number nobody can put a bound on. What the refusal costs, and why it is nevertheless right, is at maxEstimateStaleBlocks and in AUDIT-2026-09-28.md finding 12. MONOTONE IN HEIGHT AND NEVER BELOW latestRound: ChainHeight() only rises and anchorHeight/anchorScaled never move except upward, so the correction term is never negative.

Open
OID
0388a9…ede1:24
EstimatedLatestRound details

Inspect func

EstimatedLatestRoundWithin

func(maxStaleBlocks int64) int64

EstimatedLatestRoundWithin is EstimatedLatestRound with the caller's own staleness tolerance, in blocks, for a consumer whose real-time margin is finer than the backstop's. r/splash/arcade/duel uses it: its bound round is 300 SECONDS out, and the backstop admits 2.4 hours of error. IT CAN ONLY EVER ASK FOR LESS. A tolerance above maxEstimateStaleBlocks is refused, so this is not a way around the backstop — a consumer can tighten the bound and cannot loosen it. Read maxEstimateStaleBlocks for why the two-level shape exists at all. Panics if no round has ever been published, if the tolerance is not positive, if it exceeds the backstop, or if the beacon is staler than the tolerance allows.

Open
OID
0388a9…ede1:25
EstimatedLatestRoundWithin details

Inspect func

extrapolatedRound

func() int64

extrapolatedRound is the extrapolation itself, WITH NO STALENESS CHECK. Unexported on purpose: an unbounded extrapolation is exactly what this realm must not hand a consumer. It exists separately so that Render can show the number it would have returned without panicking on a read path, and so that this package's own tests can assert the drift arithmetic at distances the exported functions refuse.

Open
OID
0388a9…ede1:26
extrapolatedRound details

Inspect func

StaleBlocks

func() int64

StaleBlocks is how far the chain has moved since the last relay that RAISED the estimate's anchor: the one distance every error in this realm's extrapolation is proportional to. Panics if no round has ever been published. IT IS NOT "BLOCKS SINCE THE LAST Publish", and the difference is the whole soundness argument — read anchorHeight. A relay that does not raise the anchor does not reduce the error, and "blocks since the last Publish" is resettable by anyone for the cost of relaying any one of drand's ~2.1e7 public past rounds. EXPORTED SO A CONSUMER OR A FRONT END CAN SEE THE REFUSAL COMING rather than discover it as a panic in the middle of a settlement, and so a caller can decide to relay a round first. Clamped at zero: a negative distance is unreachable on chain (height only rises, anchorHeight only moves to a then-current height) and arises only under \`gno test\`, where ChainHeight() resets between test functions while realm state persists — DRAND-MIGRATION.md section 9.7.

Open
OID
0388a9…ede1:27
StaleBlocks details

Inspect func

AnchorHeight

func() int64

AnchorHeight is the chain height of the last relay that raised the estimate's anchor. Panics if no round has ever been published. See anchorHeight and StaleBlocks.

Open
OID
0388a9…ede1:28
AnchorHeight details

Inspect func

EstimateIsUsable

func() bool

EstimateIsUsable reports whether EstimatedLatestRound would return rather than panic. A read, so a caller can check before committing to a transaction that would revert.

Open
OID
0388a9…ede1:29
EstimateIsUsable details

Inspect func

Render

func(path string) string

Render implements the realm's human-readable view.

Open
OID
0388a9…ede1:30
Render details

Inspect func
ChainHash : untyped string ="04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3"
PeriodSeconds : untyped bigint =(3 <untyped> bigint)
RoundsPerDay : untyped bigint =(28800 <untyped> bigint)
blocksPerDay : untyped bigint =(26182 <untyped> bigint)
groupKey : []uint8 (len=128) Inspect
mustDecodeHex : func(s string) []uint8 Inspect
record : type =beacon.record
rounds : *v0.Tree Inspect
latestRound : int64 =21291587
latestHeight : int64 =236052
haveLatest : bool =true
anchorScaled : int64 =550658033234
anchorHeight : int64 =236052
haveAnchor : bool =true
maxEstimateStaleBlocks : untyped bigint =(26182 <untyped> bigint)
MaxEstimateStaleBlocks : untyped bigint =(26182 <untyped> bigint)
roundKeyWidth : untyped bigint =(19 <untyped> bigint)
zeroPad : untyped string ="0000000000000000000"
roundKey : func(round int64) string Inspect
Publish : func(round int64, sigHex string) Inspect
get : func(round int64) *beacon.record Inspect
Randomness : func(round int64) string Inspect
Published : func(round int64) bool Inspect
PublishedAtHeight : func(round int64) int64 Inspect
Latest : func() (int64, string) Inspect
LatestPublishedAtHeight : func() int64 Inspect
EstimatedLatestRound : func() int64 Inspect
EstimatedLatestRoundWithin : func(maxStaleBlocks int64) int64 Inspect
extrapolatedRound : func() int64 Inspect
StaleBlocks : func() int64 Inspect
AnchorHeight : func() int64 Inspect
EstimateIsUsable : func() bool Inspect
Render : func(path string) string Inspect