fix(001): measure head lag against a node we hold, not the provider's clock - #2267
Merged
Conversation
… clock The spec promises "Reference: archive nodes per chain, validated against block hashes" in three places. The harness never did that: both provider paths compute receiveTime minus the timestamp the provider itself sent. grep for archive/getBlockByNumber/blockTimestamp over the harness returns nothing. Measured consequence: on the same transaction hash, Serialized and Mobula disagree about when it happened by 707 ms on Solana and 1,000 ms on Base, so the leaderboard partly ranks where each vendor puts its clock. Adds one WebSocket subscription per monitored pool straight to a node, timestamping every swap on receipt, matched to provider emissions by transaction hash. Published as head_lag_ref_seconds beside the legacy series so the old one keeps its history while the two are compared. Validated before shipping at a 100% hash match rate on Base and Solana. That validation also surfaced the binding constraint: against public endpoints the reference node is SLOWER than the providers, so the series carries the node's own latency as an offset and must be read as a relative comparison until REF_WS_URL_<CHAIN> points at a paid node. All of this is documented at the call site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CpArutAtXuBb1BVNUDXoYA
This was referenced Sep 5, 2026
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.
The defect
benchmarks/aggregator-head-lag.ymlpromises, in three places, that head lag is measured against archive nodes:The harness never did that. Both provider paths compute
receiveTime - <the timestamp the provider itself sent us>:grep -rl "archive|eth_getBlockByNumber|getBlockTime|blockTimestamp"over the harness returns nothing.Measured on trades matched by hash: the same swap carries timestamps 707 ms apart on Solana and 1,000 ms apart on Base depending on which provider you ask. The published leaderboard partly ranks clock placement, not pipeline speed.
The fix
One WebSocket subscription per monitored pool, straight to a node (
eth_subscribelogs on EVM,logsSubscribewith a mentions filter on Solana), timestamping every swap on receipt. Provider emissions are matched by transaction hash against that single clock, so every provider is measured with the same ruler.Published as
head_lag_ref_secondsbesidehead_lag_seconds, not instead of it, so the leaderboard and its history keep working while the two are compared.Validated before shipping, and it changed the design twice
Two things the validation caught that would have shipped broken otherwise:
logsSubscribeon Solana and then delivers nothing (0 events in 60 s while the EVM equivalents streamed), and drpc rejects the method on the free plan (code 35). Default switched toapi.mainnet-beta.solana.com, which answers.That second point is the binding constraint and it is documented at the call site rather than hidden. The node's own latency sits in every sample as a roughly constant offset, so the absolute value is not a head lag; the relative comparison between providers is sound, because they are all measured against the same clock on the same transaction, which is precisely what the legacy series cannot claim. Negative values are therefore kept and counted as
ahead_of_referencerather than silently dropped, unlike the legacy path which discards every negative.Point
REF_WS_URL_<CHAIN>at a paid or colocated node to collapse the offset and make the absolute number meaningful. Robinhood Chain has no configured endpoint and simply leaves the series empty rather than measuring against something arbitrary.Also: the reference dialer deliberately does not use
getProxyDialer. The scraping proxy is currently quota-saturated and returns 429; routing the reference clock through it would add the proxy's own latency to the ruler.Auditability
head_lag_ref_matches_total{outcome}countsmatched,ahead_of_reference,unmatchedandout_of_range, so a degraded reference subscription is visible in the metrics instead of quietly skewing the series.head_lag_ref_clock_entriespublishes the window occupancy.🤖 Generated with Claude Code
https://claude.ai/code/session_01CpArutAtXuBb1BVNUDXoYA