Skip to content

the BNB column was measuring BNB's block time, not the providers - #2792

Merged
Flotapponnier merged 1 commit into
devfrom
feat/bnb-reference-clock
Oct 3, 2026
Merged

Flotapponnier merged 1 commit into
devfrom
feat/bnb-reference-clock

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Found while checking whether OKX could be measured on BNB. It cannot, for a
reason that turned out to be a defect in the column itself rather than in OKX.

What the BNB column publishes today

head_lag_seconds on bnb is receiveTime minus the provider's own
timestamp, because bnb is in neither referenceChains nor raceChains. The
spec justified that:

Reference on BNB Chain and Robinhood Chain: [...] their chain-supplied
timestamps sit within roughly 60 ms of the moment a trade is observable, so
they keep the on-chain timestamp as the zero point.

The gap between that series and the reference-matched one is that quantity.
It measures 569 ms, not 60.

What that does to the column

The dominant term is BNB's block time, which every provider shares, so the
column barely separates them. 24 h, 8,604 samples per provider, eu-west p50:

provider published today against the reference
mobula 0.621 s 0.052 s
serialized 0.657 s 0.102 s
codex 0.676 s 0.215 s
geckoterminal 33.881 s 31.196 s

The three push feeds sit within 9% of each other today and spread over a
factor of four against a common clock. p90 moves the same way, 1.0-1.2 s to
0.12-0.33 s.

The ranking is unchanged. mobula, then serialized, then codex, before and
after. This removes an offset they all carry; it does not reshuffle anyone.

Does it clear the gate

pending_match.go admits a chain only "once its reference is measured to arrive
before every provider". Share of trades where a provider beats our node:

bnb base (already shipping on the reference)
mobula 6.6% 4.2%
serialized 4.7% 3.1%
codex 3.7% 0.0%
geckoterminal 0.3% —

Higher than Base, same order of magnitude, and Base ships at 4.2%. So this
clears the bar the project already applies to itself rather than a new one
invented for the occasion.

Scope

The reference subscription already exists and already populates
head_lag_ref_seconds on bnb. Nothing new is collected. The only change is
which series the headline reads.

What a reviewer should weigh

The published bnb numbers will drop by roughly 5-10x the day this deploys.
That is a methodology correction, not providers getting faster, and anyone
watching the page will see a step change. The spec now says so in the
methodology list where it used to carry the 60 ms claim. If that step should be
announced somewhere else too, say where.

TestReferenceChainsScope asserted bnb must NOT be here. I updated it
rather than deleted it, with the reason in the comment, and added
reference_chains_test.go pinning why bnb belongs now and why robinhood and
solana still do not. If you disagree with the switch, that test is the thing to
argue with.

Robinhood is untouched and still reads the provider timestamp. It has no
measurement yet, so I did not guess.

Follow-on

This unblocks OKX on BNB (#2791 excludes it precisely because the column read
provider timestamps and OKX sends whole seconds). That is a separate, small
change once both land, not part of either PR.

Checks

  • go build ./... and go test ./cmd/script/ clean
  • pnpm validate 235 specs valid
  • pnpm exec tsc --noEmit clean
  • bun test src/ 425 pass, 0 fail

🤖 Generated with Claude Code

head_lag_seconds on bnb published receiveTime minus the provider's OWN
timestamp, because bnb was in neither referenceChains nor raceChains. The
spec justified that by claiming BNB's chain-supplied timestamps sat
"within roughly 60 ms of the moment a trade is observable".

The gap between that series and the reference-matched one IS that
quantity, and it is 569 ms.

So the dominant term in the published column was block time, which every
provider shares, and the column barely separated them. Measured over 24 h,
8,604 samples per provider, eu-west p50:

              published    against the reference
  mobula        0.621 s          0.052 s
  serialized    0.657 s          0.102 s
  codex         0.676 s          0.215 s
  geckoterminal 33.881 s        31.196 s

Within 9% of each other before, spread over a factor of four after, and
the ranking is unchanged. This removes a common offset rather than
reshuffling anyone. p90 moves the same way (1.0-1.2 s to 0.12-0.33 s).

The gate in pending_match.go is that the reference must arrive before
every provider. Our node is beaten 6.6% of the time by mobula on bnb,
4.7% by serialized, 3.7% by codex. Base, already on the reference and
shipping, sits at 4.2% / 3.1% / 0%. Same order of magnitude, so this
clears the bar the project already applies to itself.

The reference subscription this reads already existed and already
populated head_lag_ref_seconds; nothing new is collected. Only which
series the headline reads changes.

Published numbers on the bnb column will drop by roughly 5-10x the day
this deploys. That is a methodology correction, not providers getting
faster, and the spec now says so where it used to carry the 60 ms claim.

TestReferenceChainsScope asserted bnb must NOT be here. Updated
deliberately rather than deleted, with the reason, and a new test pins
why bnb belongs now and why robinhood and solana still do not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Flotapponnier
Flotapponnier merged commit 6ae0b65 into dev Oct 3, 2026
2 checks passed
@Flotapponnier
Flotapponnier deleted the feat/bnb-reference-clock branch October 3, 2026 22:18
Flotapponnier added a commit that referenced this pull request Oct 3, 2026
* OKX's DEX feed is open after all, on a channel their docs do not mention (#2791)

Their documented trades channel, wss://wsdex.okx.com/ws/v6/dex, answers
"Please log in" to every subscribe including the docs' own verbatim example,
and their fee table says why: Free reads "WebSocket not supported", cheapest
WS-capable plan $99/month.

Their web frontend holds a different socket that needs no credential. Two
things hid it, both of which fail silently:

  - the channel is dex-market-trade-history, not any `trades` variant. Twenty
    other names were rejected before this one;
  - the params go in an extraParams JSON STRING. With flat keys the gateway
    still answers {"event":"subscribe"} and then pushes nothing, which is
    indistinguishable from a dead channel. An ack proves nothing here, only
    frames do.

Base and Solana only, and both exclusions are structural. BNB publishes
head_lag_seconds as receiveTime minus the provider's own timestamp, and every
OKX timestamp is a whole second (1,800 sampled across three chains, all
ts%1000 == 0), so measuring it there would publish our arithmetic as OKX's
latency. Robinhood Chain is not in OKX's supported set.

OKX cannot scope a subscription to a pool: six ways of passing the pool
address are accepted and silently ignored, and the paid channel is
token-scoped too, so this is the shape of the product rather than a level of
access. It does not change what is scored. Only emissions matched to the
reference by transaction hash are, and the reference is the bench pool, so the
scored set is identical to the other four providers'. Verified before shipping
rather than assumed: over 20 minutes on the Base pool, 213 of 213 pool swaps
appeared in OKX's stream, p50 636 ms behind our node, OKX never first.

The one cost is bookkeeping. An off-pool emission never matches and would
expire as a miss, which would read as a 96%-miss provider and drown the signal
that counter exists for: whether the reference itself is healthy.
tokenScopedAggregators exempts it, and a test keeps the exemption to providers
that genuinely cannot scope to a pool.

The feed is undocumented frontend infrastructure with no stability guarantee.
That is the bet geckoterminal_monitor.go already makes here, and the failure
mode is the same: the flow watchdog purges the gauge and the column reads
"Feed down" rather than a wrong number. Tests pin the socket and channel
constants so a silent move fails loudly.

okx.svg replaces the opaque JPEG, which drew a white box around the mark on
dark backgrounds.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* the BNB column was measuring BNB's block time, not the providers (#2792)

head_lag_seconds on bnb published receiveTime minus the provider's OWN
timestamp, because bnb was in neither referenceChains nor raceChains. The
spec justified that by claiming BNB's chain-supplied timestamps sat
"within roughly 60 ms of the moment a trade is observable".

The gap between that series and the reference-matched one IS that
quantity, and it is 569 ms.

So the dominant term in the published column was block time, which every
provider shares, and the column barely separated them. Measured over 24 h,
8,604 samples per provider, eu-west p50:

              published    against the reference
  mobula        0.621 s          0.052 s
  serialized    0.657 s          0.102 s
  codex         0.676 s          0.215 s
  geckoterminal 33.881 s        31.196 s

Within 9% of each other before, spread over a factor of four after, and
the ranking is unchanged. This removes a common offset rather than
reshuffling anyone. p90 moves the same way (1.0-1.2 s to 0.12-0.33 s).

The gate in pending_match.go is that the reference must arrive before
every provider. Our node is beaten 6.6% of the time by mobula on bnb,
4.7% by serialized, 3.7% by codex. Base, already on the reference and
shipping, sits at 4.2% / 3.1% / 0%. Same order of magnitude, so this
clears the bar the project already applies to itself.

The reference subscription this reads already existed and already
populated head_lag_ref_seconds; nothing new is collected. Only which
series the headline reads changes.

Published numbers on the bnb column will drop by roughly 5-10x the day
this deploys. That is a methodology correction, not providers getting
faster, and the spec now says so where it used to carry the 60 ms claim.

TestReferenceChainsScope asserted bnb must NOT be here. Updated
deliberately rather than deleted, with the reason, and a new test pins
why bnb belongs now and why robinhood and solana still do not.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant