stats + revenue: name every rail, and say when the canary is filtered out - #664
Merged
Conversation
…two rows
Found while checking whether the Monad rail was healthy. It is - but the
per-chain revenue split on the PUBLIC /api/stats was reporting the same chain
twice, and two rails not at all.
monad 19 beside eip155:143 42 -> one chain, 61 calls
celo 35 beside eip155:42220 16 -> one chain, 51 calls
stellar 71 beside stellar:pubnet 20 -> one chain, 91 calls
eip155:10 14, eip155:1329 9 -> optimism and sei, unnamed entirely
networkFromPaymentResponse maps CAIP2_NAMES[id] || id, so a chain missing from
that map is booked under its raw id. Sei and Optimism, the two most recently
added rails, were never added to it - so every settlement on them has been
displayed to the public as a hex chain id since they shipped. The older splits
are historical counters recorded before their entries existed.
Nothing was lost on-chain and no money is affected. What is affected is that
per-chain revenue reads low for every split rail, and a reader comparing rails
draws the wrong conclusion from a surface that looks authoritative.
Two fixes:
* eip155:10 -> optimism and eip155:1329 -> sei, stopping the ongoing split.
* mergeNetworkCounters folds raw-id counters into their named bucket at READ
time. Deliberately not a history rewrite: the stored counters stay exactly
as recorded and the merge is a presentation rule anyone can check against
CAIP2_NAMES. A chain we genuinely cannot name still reports under its raw
id rather than vanishing, because silently dropping revenue would be a far
worse defect than showing a hex string.
scripts/test-chain-names.js (10 assertions, offline, in CI) fails if any
offerable chain lacks a name, so a thirteenth rail cannot ship unnamed.
Note on the test itself: its first version called railStatus(), which reads
boot state and is empty offline, so the assertion that matters passed while
checking ZERO rails. It now sources the static NETWORKS map and checks 12.
That vacuous-green shape is the same one this whole audit keeps turning up,
and it nearly shipped inside the guard against it.
Mutations killed: removing optimism from the map reproduces the original
defect, dropping unnameable chains loses revenue silently, and overwriting
instead of summing loses calls. Totals are asserted conserved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tered out Reported as "I don't see any canary runs on the revenue page". The canary was running fine - it settled on 11 of 12 rails that day, every one present in /api/revenue/daily as internal transactions. Only Celo was missing, and Celo's facilitator is down. The chart defaults to External scope, which is correct and must stay: the canary buys from us with our own wallet, and counting it as revenue would inflate every number on the page. But that default made two very different situations look identical on screen - "the canary stopped running" and "the canary is excluded by the current filter" - and the default view was read as the first on a day the second was true. So when internal settlements EXIST in the window and the External filter is hiding them, the page now says so: how many chains, how many days, which control reveals them, and WHY they are excluded. The number on the chart does not move. What changes is that a reader can tell absence from concealment, which is the same fix as the seller-facing discovery note and for the same reason - a gap nobody can see is a gap nobody investigates. The note hides itself when the scope already includes internal, because a note that renders in every state is decoration rather than signal. 4 assertions in scripts/test-revenue-chart.js (jsdom, in CI). Mutations killed: never showing the note restores the invisible state, showing it when the canary is already on screen makes it noise, and dropping the WHY leaves it reading as a bug report rather than a deliberate exclusion. Note on the test: these cases share one jsdom window, so the two new checks restore the controls they flip. Leaving one flipped made the NEXT check fail on a note that was working perfectly - a fault in the harness that reads exactly like a fault in the page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two findings, both surfaced by checking things that were reported as looking wrong but turned out to be reported correctly.
1. One chain shown as two rows; two rails shown as hex ids
Found while verifying the Monad rail was healthy. It is — but the per-chain split on the public
/api/statswas reporting the same chain twice:monad19 +eip155:14342celo35 +eip155:4222016stellar71 +stellar:pubnet20eip155:1014,eip155:13299networkFromPaymentResponsemapsCAIP2_NAMES[id] || id, so a chain missing from that map is booked under its raw id. Sei and Optimism, the two most recently added rails, were never added — every settlement on them has displayed publicly as a hex chain id since they shipped.No money affected. What is affected: per-chain revenue reads low for every split rail, on a surface that looks authoritative.
Fixed both ways — the two missing names, plus
mergeNetworkCountersfolding raw-id counters into their named bucket at read time. Deliberately not a history rewrite: stored counters stay as recorded, and a chain we genuinely cannot name still reports under its raw id rather than vanishing, because silently dropping revenue is worse than showing a hex string. Totals asserted conserved.scripts/test-chain-names.jsfails if any offerable chain lacks a name, so a thirteenth rail cannot ship unnamed.Note on the guard itself: its first version called
railStatus(), which reads boot state and is empty offline — so the assertion that matters passed while checking zero rails. It now sources the staticNETWORKSmap and checks 12.2. "I don't see any canary runs on the revenue page"
The canary was running fine — it settled on 11 of 12 rails that day, all present in
/api/revenue/daily. Only Celo was missing, and Celo's facilitator is down.The chart defaults to External scope, which is correct and stays: the canary buys from us with our own wallet, and counting it as revenue would inflate every number on the page. But that made "the canary stopped running" and "the canary is excluded by the current filter" look identical on screen.
The page now says, when internal settlements exist and are hidden: how many chains, how many days, which control reveals them, and why they are excluded. The number on the chart does not move. The note hides itself when the scope already includes internal, because a note that renders in every state is decoration.
Verification
test-chain-names.js, 4 added totest-revenue-chart.js(jsdom), both in CI