From 0f4b29820c0b96b7e30eb364272b467036e6850f Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:08:11 +0200 Subject: [PATCH] add bench 101 wormhole-vaa-latency (Wormhole VAA finalization by source chain) (#1374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New passive-observation bench: polls wormholescan /api/v1/vaas every 60s, computes updatedAt - timestamp per VAA (source-tx observation to Guardian quorum), emits wormhole_vaa_latency_seconds histogram bucketed by source chain. Zero canary tx cost, entirely observation-based. Sample data during dev: BSC 5-11s, Solana 17-26s, Sui 6s, Moonbeam 42s. - benchmarks/wormhole-vaa-latency.yml: 20 source-chain providers, histogram_quantile queries - harnesses/wormhole-vaa-latency/: Go binary + Dockerfile, LRU dedupe (10k entries), :2112 metrics - src/data/provider-registry.ts: wormhole entry - src/lib/logo-manifest.ts: wormhole:/logos/wormhole.png - public/logos/wormhole.png: 180x180 PNG from wormhole.com apple-touch-icon - src/lib/spec.ts: cache keys v42->v43 / v45->v46 (bench SET grew by 1) Still needs VPS docker compose + prom scrape target — will follow up in ship. Co-authored-by: Florent Tapponnier --- benchmarks/wormhole-vaa-latency.yml | 323 ++++++++++++++++++ harnesses/wormhole-vaa-latency/Dockerfile | 22 ++ .../wormhole-vaa-latency/cmd/script/chains.go | 37 ++ .../wormhole-vaa-latency/cmd/script/main.go | 198 +++++++++++ .../cmd/script/metrics.go | 53 +++ harnesses/wormhole-vaa-latency/go.mod | 18 + harnesses/wormhole-vaa-latency/go.sum | 46 +++ public/logos/wormhole.png | Bin 0 -> 9558 bytes src/data/provider-registry.ts | 6 + src/lib/logo-manifest.ts | 1 + src/lib/spec.ts | 16 +- 11 files changed, 708 insertions(+), 12 deletions(-) create mode 100644 benchmarks/wormhole-vaa-latency.yml create mode 100644 harnesses/wormhole-vaa-latency/Dockerfile create mode 100644 harnesses/wormhole-vaa-latency/cmd/script/chains.go create mode 100644 harnesses/wormhole-vaa-latency/cmd/script/main.go create mode 100644 harnesses/wormhole-vaa-latency/cmd/script/metrics.go create mode 100644 harnesses/wormhole-vaa-latency/go.mod create mode 100644 harnesses/wormhole-vaa-latency/go.sum create mode 100644 public/logos/wormhole.png diff --git a/benchmarks/wormhole-vaa-latency.yml b/benchmarks/wormhole-vaa-latency.yml new file mode 100644 index 00000000..d62800ac --- /dev/null +++ b/benchmarks/wormhole-vaa-latency.yml @@ -0,0 +1,323 @@ +# OpenChainBench. Bench № 101 + +slug: wormhole-vaa-latency +number: "101" +title: Fastest Wormhole VAA finalization by source chain, live latency +seo_title: "Fastest Wormhole VAA source chain 2026" +seo_description: "{{best_name}} leads Wormhole VAA finalization at {{best_p50}} (source-tx to Guardian quorum p50, 24h) across 20 source chains. Observed, not simulated." +subtitle: Wall-clock seconds from a source-chain transaction being observed by the Guardian network to a Wormhole VAA reaching signing quorum, measured across 20 source chains via the public wormholescan indexer. + +category: Bridges +status: live +metric: VAA finalization +unit: s +higher_is_better: false + +seo_intro: | + Wormhole is a cross-chain messaging network. When a transaction on a + source chain emits a Wormhole message, a network of Guardian nodes + observes it, waits for the source chain's own finality threshold, and + each Guardian signs an attestation (VAA , Verified Action Approval). + Once 13 of 19 Guardians have signed, the VAA is quorum-ready and can + be relayed to any destination chain to unlock the corresponding + action (bridged token mint, cross-chain message delivery, etc.). + This benchmark measures end-to-end VAA finalization latency by + source chain: how long it takes from a source-chain transaction + being seen to a full-quorum VAA becoming available on wormholescan. + Every value on this page is an observation of real user traffic + (~thousands of VAAs/day) , the harness never sends canary + transactions and never estimates. Latency distribution is dominated + by the source chain's own finality latency: BNB Chain and Sui with + sub-second finality land VAAs in ~5-10 s; Solana's 32-slot finalized + commitment adds ~15-25 s; Ethereum's Casper FFG two-epoch wait can + push VAAs into the 15-30 s range depending on where in the epoch a + message lands. Choosing a source chain for a Wormhole integration + is a choice about VAA availability latency, and this page ranks the + 20 source chains Wormhole supports that have both non-trivial + VAA volume and a live listing on OpenChainBench. + +abstract: | + For each of 20 source chains supported by Wormhole, we measure the + wall-clock latency between a source-chain block being observed and a + Wormhole VAA emitted by that block reaching Guardian signing quorum. + Data source: the public wormholescan `/api/v1/vaas` feed, polled + every 60 seconds. Each VAA's `updatedAt − timestamp` is one + observation of finalization latency for that source chain, recorded + in a per-chain histogram. The p50/p90/p99 shown here are computed + via Prometheus `histogram_quantile` over the last 24 hours. + +methodology: + - "Data source: `https://api.wormholescan.io/api/v1/vaas?pageSize=100`, the public wormholescan indexer of Wormhole VAAs. Polled every 60 seconds." + - "Latency definition: `updatedAt − timestamp` from the VAA row. `timestamp` is the source-chain observation time (block containing the Wormhole message); `updatedAt` is the moment wormholescan first indexed the VAA as fully-signed by 13-of-19 Guardians." + - "Dedupe: last 10 000 VAA ids kept in memory (rolling FIFO). Wormholescan returns ~100 VAAs/min sustained; 10k cache holds ~100 minutes of history, well above any poll interval." + - "Bucketing: histogram with geometric buckets 2, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300, 600 seconds." + - "Guard: samples with `updatedAt − timestamp < 0` or `> 3600 s` are dropped (clock skew or backfilled VAAs whose `updatedAt` refers to a later re-index event)." + - "No canary transactions: this bench observes organic Wormhole traffic (thousands of VAAs/day). No wallet is funded, no gas is paid." + - "Scope: 20 source chains Wormhole supports that are already tracked on OpenChainBench's chain registry. Chains with negligible VAA volume in a 24-hour window may appear with wide error bars or a low sample count." + - "Destination chain is not measured: VAAs are relayed on demand once quorum is reached, so post-quorum latency is caller-driven (relayer choice, destination gas price, etc.) and not a property of Wormhole itself." + +findings: + - "{{best_name}} currently leads Wormhole VAA finalization at {{best_p50}} (source-tx observation to Guardian quorum p50, 24h) across 20 measured source chains." + - "Sub-second BFT chains lead: Sui and BNB Chain finalize source blocks fast enough that VAA availability lands in single-digit seconds, dominated by Guardian signing round-trip rather than chain finality." + - "Solana's ~32-slot (13 s) finalized commitment shows up cleanly as a ~15-25 s VAA availability floor even though Guardians observe the source tx sub-second." + +faq: + - q: "What is a Wormhole VAA?" + a: "A VAA (Verified Action Approval) is a signed attestation produced by the Wormhole Guardian network that a specific event happened on a source chain (a token bridge deposit, a cross-chain message emission, etc.). Once 13 of 19 Guardians have signed, the VAA is quorum-ready and can be relayed to any destination chain Wormhole supports." + - q: "What does this benchmark actually measure?" + a: "Wall-clock seconds from a source-chain block being observed by the Guardian network to a fully-signed VAA reaching wormholescan's indexer. That is the closest publicly-observable proxy for 'how fast can I use Wormhole to move something out of this chain'. It does NOT measure destination-chain delivery time (which is caller-driven , anyone can relay a signed VAA)." + - q: "How is the data sourced?" + a: "The harness polls wormholescan's public `/api/v1/vaas` endpoint every 60 seconds and records `updatedAt − timestamp` for each VAA it has not seen before. Wormholescan is Wormhole Foundation's canonical indexer; the timestamps come directly from the Guardian gossip layer and the on-chain event observation. No canary transactions are sent, no user funds are used." + - q: "Why does source chain matter so much?" + a: "Because Guardians wait for the source chain's own finality threshold before signing. On sub-second BFT chains (Sui, BNB), the wait is minimal. On Solana, Guardians wait for 32-slot finalized commitment (~13 s). On Ethereum, they wait for Casper FFG finalization, which can add another 12-15 s. The source-chain finality latency is the floor for Wormhole VAA availability." + - q: "Which source chain is fastest for Wormhole today?" + a: "{{best_name}} currently leads at {{best_p50}} (VAA finalization p50 over the last 24 hours). The leaderboard re-sorts continuously against fresh Prometheus samples; the ranking on this page is the ranking right now." + - q: "Why not measure the destination side too?" + a: "Destination-side latency (relay a signed VAA + burn/mint on the target chain) is caller-driven. Anyone can relay a VAA once it is quorum-ready; the time to do so depends on the relayer's gas policy, the destination chain's own block time, and how urgently the caller needs delivery. Measuring it would benchmark the caller's choices, not Wormhole. VAA availability, by contrast, is entirely a property of the Wormhole protocol and its source-chain observation strategy , that is what this bench isolates." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/wormhole-vaa-latency + +prometheus: + window: 24h + freshness_metric: wormhole_vaa_seen_total + +providers: + - slug: solana + name: Solana + tag: Solana → any chain, VAA finality via Guardian quorum on 32-slot Solana confirmation + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Solana: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="solana"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="solana"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="solana"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="solana"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="solana"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="solana"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="solana"}[1h]))) + + - slug: ethereum + name: Ethereum + tag: Ethereum → any chain, VAA finality after Casper FFG epoch inclusion + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Ethereum: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ethereum"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ethereum"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ethereum"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="ethereum"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="ethereum"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="ethereum"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ethereum"}[1h]))) + + - slug: bnb + name: BNB Chain + tag: BNB Chain → any chain, VAA finality via BEP-126 fast-finality + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from BNB Chain: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="bnb"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="bnb"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="bnb"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="bnb"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="bnb"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="bnb"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="bnb"}[1h]))) + + - slug: polygon + name: Polygon + tag: Polygon PoS → any chain, VAA finality after Bor block confirmation + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Polygon: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="polygon"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="polygon"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="polygon"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="polygon"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="polygon"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="polygon"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="polygon"}[1h]))) + + - slug: avalanche + name: Avalanche + tag: Avalanche C-Chain → any chain, VAA finality via Snowman++ sub-second consensus + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Avalanche: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="avalanche"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="avalanche"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="avalanche"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="avalanche"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="avalanche"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="avalanche"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="avalanche"}[1h]))) + + - slug: celo + name: Celo + tag: Celo → any chain, VAA finality after single-block deterministic finalization + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Celo: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="celo"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="celo"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="celo"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="celo"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="celo"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="celo"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="celo"}[1h]))) + + - slug: moonbeam + name: Moonbeam + tag: Moonbeam parachain → any chain, VAA finality after GRANDPA relay-chain justification + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Moonbeam: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="moonbeam"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="moonbeam"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="moonbeam"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="moonbeam"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="moonbeam"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="moonbeam"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="moonbeam"}[1h]))) + + - slug: sui + name: Sui + tag: Sui → any chain, VAA finality via Mysticeti DAG-BFT checkpoint commit + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Sui: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="sui"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="sui"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="sui"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="sui"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="sui"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="sui"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="sui"}[1h]))) + + - slug: arbitrum + name: Arbitrum + tag: Arbitrum One → any chain, VAA finality after Nitro block posted + Ethereum L1 confirmation + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Arbitrum: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="arbitrum"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="arbitrum"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="arbitrum"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="arbitrum"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="arbitrum"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="arbitrum"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="arbitrum"}[1h]))) + + - slug: optimism + name: Optimism + tag: Optimism → any chain, VAA finality after OP Stack sequencer block + settlement confirmation + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Optimism: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="optimism"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="optimism"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="optimism"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="optimism"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="optimism"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="optimism"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="optimism"}[1h]))) + + - slug: base + name: Base + tag: Base → any chain, VAA finality after OP Stack sequencer block + settlement confirmation + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Base: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="base"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="base"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="base"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="base"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="base"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="base"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="base"}[1h]))) + + - slug: scroll + name: Scroll + tag: Scroll zkEVM → any chain, VAA finality after L2 sequencer block + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Scroll: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="scroll"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="scroll"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="scroll"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="scroll"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="scroll"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="scroll"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="scroll"}[1h]))) + + - slug: mantle + name: Mantle + tag: Mantle → any chain, VAA finality after L2 sequencer block + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Mantle: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="mantle"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="mantle"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="mantle"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="mantle"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="mantle"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="mantle"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="mantle"}[1h]))) + + - slug: linea + name: Linea + tag: Linea zkEVM → any chain, VAA finality after L2 sequencer block + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Linea: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="linea"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="linea"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="linea"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="linea"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="linea"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="linea"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="linea"}[1h]))) + + - slug: berachain + name: Berachain + tag: Berachain → any chain, VAA finality via BeaconKit consensus + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Berachain: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="berachain"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="berachain"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="berachain"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="berachain"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="berachain"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="berachain"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="berachain"}[1h]))) + + - slug: unichain + name: Unichain + tag: Unichain → any chain, VAA finality after OP Stack sequencer + Flashblocks + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Unichain: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="unichain"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="unichain"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="unichain"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="unichain"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="unichain"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="unichain"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="unichain"}[1h]))) + + - slug: world-chain + name: World Chain + tag: World Chain → any chain, VAA finality after OP Stack sequencer block + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from World Chain: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="world-chain"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="world-chain"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="world-chain"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="world-chain"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="world-chain"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="world-chain"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="world-chain"}[1h]))) + + - slug: monad + name: Monad + tag: Monad → any chain, VAA finality after MonadBFT ~800 ms consensus + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Monad: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="monad"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="monad"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="monad"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="monad"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="monad"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="monad"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="monad"}[1h]))) + + - slug: ink + name: Ink + tag: Ink → any chain, VAA finality after OP Stack sequencer block + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Ink: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ink"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ink"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ink"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="ink"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="ink"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="ink"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="ink"}[1h]))) + + - slug: injective + name: Injective + tag: Injective → any chain, VAA finality after CometBFT block commit + formula: "50th percentile over 24h of Wormhole VAA finalization latency for VAAs emitted from Injective: wall-clock delta from source-chain observation to Guardian quorum, indexed via wormholescan." + queries: + p50: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="injective"}[24h]))) + p90: histogram_quantile(0.90, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="injective"}[24h]))) + p99: histogram_quantile(0.99, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="injective"}[24h]))) + mean: sum(rate(wormhole_vaa_latency_seconds_sum{source_chain="injective"}[24h])) / sum(rate(wormhole_vaa_latency_seconds_count{source_chain="injective"}[24h])) + sample_size: increase(wormhole_vaa_seen_total{source_chain="injective"}[24h]) + series: histogram_quantile(0.50, sum by(le) (rate(wormhole_vaa_latency_seconds_bucket{source_chain="injective"}[1h]))) diff --git a/harnesses/wormhole-vaa-latency/Dockerfile b/harnesses/wormhole-vaa-latency/Dockerfile new file mode 100644 index 00000000..270b0b69 --- /dev/null +++ b/harnesses/wormhole-vaa-latency/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /app +RUN apk add --no-cache git + +COPY go.mod ./ +RUN go mod download || true + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/monitor ./cmd/script + +FROM debian:bookworm-slim + +WORKDIR /app +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/monitor /app/monitor + +EXPOSE 2112 + +CMD ["/app/monitor"] diff --git a/harnesses/wormhole-vaa-latency/cmd/script/chains.go b/harnesses/wormhole-vaa-latency/cmd/script/chains.go new file mode 100644 index 00000000..2cbd5079 --- /dev/null +++ b/harnesses/wormhole-vaa-latency/cmd/script/chains.go @@ -0,0 +1,37 @@ +package main + +// emitterChainSlug maps Wormhole's numeric emitter-chain id to the site's +// chain slug. Only chains with non-trivial VAA volume are enumerated — +// unrecognised chains fall through to a synthetic "chain" slug in +// main.go so the harness never drops a sample silently. +// +// Source: https://docs.wormhole.com/wormhole/reference/blockchain-ids +// (kept manually short — Wormhole periodically adds new chain ids and +// we only want to surface chains that (a) have public infrastructure +// and (b) are already tracked elsewhere on the site.) +var emitterChainSlug = map[int]string{ + 1: "solana", + 2: "ethereum", + 4: "bnb", + 5: "polygon", + 6: "avalanche", + 10: "fantom", + 14: "celo", + 15: "near", + 16: "moonbeam", + 19: "injective", + 21: "sui", + 22: "aptos", + 23: "arbitrum", + 24: "optimism", + 30: "base", + 32: "sei", + 34: "scroll", + 35: "mantle", + 38: "linea", + 39: "berachain", + 44: "unichain", + 45: "world-chain", + 48: "monad", + 50: "ink", +} diff --git a/harnesses/wormhole-vaa-latency/cmd/script/main.go b/harnesses/wormhole-vaa-latency/cmd/script/main.go new file mode 100644 index 00000000..5327a997 --- /dev/null +++ b/harnesses/wormhole-vaa-latency/cmd/script/main.go @@ -0,0 +1,198 @@ +// wormhole-vaa-latency: single-provider observation-only bench. +// +// Every 60s, polls https://api.wormholescan.io/api/v1/vaas?pageSize=100 for +// the most recent VAAs. For each VAA the monitor has not seen before, +// computes `updatedAt − timestamp` as the finalization latency (source-tx +// observation → Guardian quorum reached + indexer wrote the row), and +// records it in a Prometheus histogram bucketed by source chain. +// +// The harness never sends canary transactions — Wormhole handles enough +// organic volume (~thousands of VAAs/day) that passive observation gives +// statistically robust per-chain distributions inside a 24-hour window. +// +// Dedupe: last N=10000 VAA ids kept in memory (rolling LRU on insert +// order). Wormholescan returns ~100 VAAs/min sustained; a 10k cache +// holds ~100 min of history, comfortably wider than the poll window. +package main + +import ( + "container/list" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "os/signal" + "strconv" + "sync" + "syscall" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +const ( + wormholescanURL = "https://api.wormholescan.io/api/v1/vaas?pageSize=100" + pollInterval = 60 * time.Second + requestTimeout = 15 * time.Second + dedupeCacheMaxLen = 10_000 + metricsListenAddr = ":2112" +) + +type vaaEntry struct { + ID string `json:"id"` + EmitterChain int `json:"emitterChain"` + Timestamp string `json:"timestamp"` + UpdatedAt string `json:"updatedAt"` +} + +type vaaResponse struct { + Data []vaaEntry `json:"data"` +} + +// lruSet keeps the last N inserted keys, evicting FIFO. Not thread-safe — +// caller (single-goroutine poller) synchronises. +type lruSet struct { + max int + order *list.List + set map[string]*list.Element +} + +func newLRUSet(max int) *lruSet { + return &lruSet{max: max, order: list.New(), set: make(map[string]*list.Element, max)} +} + +func (l *lruSet) contains(k string) bool { _, ok := l.set[k]; return ok } + +func (l *lruSet) add(k string) { + if _, ok := l.set[k]; ok { + return + } + el := l.order.PushBack(k) + l.set[k] = el + for l.order.Len() > l.max { + front := l.order.Front() + if front == nil { + return + } + l.order.Remove(front) + delete(l.set, front.Value.(string)) + } +} + +func (l *lruSet) size() int { return l.order.Len() } + +func chainSlug(id int) string { + if s, ok := emitterChainSlug[id]; ok { + return s + } + return "chain" + strconv.Itoa(id) +} + +func poll(ctx context.Context, client *http.Client, seen *lruSet) error { + req, err := http.NewRequestWithContext(ctx, "GET", wormholescanURL, nil) + if err != nil { + return fmt.Errorf("request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "OpenChainBench/wormhole-vaa-latency") + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("http: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + body, _ := io.ReadAll(io.LimitReader(resp.Body, 200)) + return fmt.Errorf("status %d: %s", resp.StatusCode, string(body)) + } + var parsed vaaResponse + if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil { + return fmt.Errorf("decode: %w", err) + } + fresh := 0 + for _, v := range parsed.Data { + if v.ID == "" || seen.contains(v.ID) { + continue + } + ts, err1 := time.Parse(time.RFC3339, v.Timestamp) + ua, err2 := time.Parse(time.RFC3339Nano, v.UpdatedAt) + if err1 != nil || err2 != nil { + // Skip malformed rows silently — wormholescan occasionally + // backfills with non-RFC3339 nano timestamps; not worth + // polluting the error counter with parse noise. + seen.add(v.ID) + continue + } + delta := ua.Sub(ts).Seconds() + if delta < 0 || delta > 3600 { + // Guard against clock skew / backfilled VAAs whose + // updatedAt refers to a much later re-indexing event. + seen.add(v.ID) + continue + } + slug := chainSlug(v.EmitterChain) + vaaLatencySeconds.WithLabelValues(slug).Observe(delta) + vaaSeenTotal.WithLabelValues(slug).Inc() + seen.add(v.ID) + fresh++ + } + dedupeCacheSize.Set(float64(seen.size())) + log.Printf("poll: %d rows, %d fresh, dedupe cache=%d", len(parsed.Data), fresh, seen.size()) + return nil +} + +func main() { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + log.Println("wormhole-vaa-latency: starting") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := &http.Client{Timeout: requestTimeout} + seen := newLRUSet(dedupeCacheMaxLen) + var mu sync.Mutex + + go func() { + http.Handle("/metrics", promhttp.Handler()) + http.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(200) + _, _ = w.Write([]byte("ok")) + }) + log.Printf("metrics: listening on %s", metricsListenAddr) + if err := http.ListenAndServe(metricsListenAddr, nil); err != nil { + log.Fatalf("metrics server: %v", err) + } + }() + + // Prime once immediately so scrapes get non-empty series without + // waiting the first interval. + mu.Lock() + if err := poll(ctx, client, seen); err != nil { + log.Printf("poll error: %v", err) + pollErrors.Inc() + } + mu.Unlock() + + ticker := time.NewTicker(pollInterval) + defer ticker.Stop() + + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + + for { + select { + case <-ticker.C: + mu.Lock() + if err := poll(ctx, client, seen); err != nil { + log.Printf("poll error: %v", err) + pollErrors.Inc() + } + mu.Unlock() + case <-sig: + log.Println("shutdown") + return + } + } +} diff --git a/harnesses/wormhole-vaa-latency/cmd/script/metrics.go b/harnesses/wormhole-vaa-latency/cmd/script/metrics.go new file mode 100644 index 00000000..8da7e695 --- /dev/null +++ b/harnesses/wormhole-vaa-latency/cmd/script/metrics.go @@ -0,0 +1,53 @@ +package main + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +// wormhole_vaa_latency_seconds is a histogram of finalization latency, +// bucketed by source chain (Wormhole emitterChain). Latency is defined +// as `updatedAt − timestamp` from the wormholescan `/api/v1/vaas` +// payload: source-chain observation → Guardian quorum + indexer. +// +// Buckets are geometric across the observed distribution (BSC ~5-11s, +// Ethereum ~15-25s, Solana ~17-26s, Moonbeam ~40s, worst case >5min on +// slow chains during backfills). +var ( + vaaLatencySeconds = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "wormhole_vaa_latency_seconds", + Help: "Time from source-chain observation to Guardian quorum for a Wormhole VAA, by source chain.", + Buckets: []float64{2, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300, 600}, + }, + []string{"source_chain"}, + ) + + vaaSeenTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "wormhole_vaa_seen_total", + Help: "Total number of unique VAAs observed by this monitor, by source chain.", + }, + []string{"source_chain"}, + ) + + pollErrors = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "wormhole_vaa_poll_errors_total", + Help: "Total number of failed pollings of the wormholescan VAA feed.", + }, + ) + + dedupeCacheSize = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "wormhole_vaa_dedupe_cache_size", + Help: "Number of recent VAA ids the monitor keeps for deduplication.", + }, + ) +) + +func init() { + prometheus.MustRegister(vaaLatencySeconds) + prometheus.MustRegister(vaaSeenTotal) + prometheus.MustRegister(pollErrors) + prometheus.MustRegister(dedupeCacheSize) +} diff --git a/harnesses/wormhole-vaa-latency/go.mod b/harnesses/wormhole-vaa-latency/go.mod new file mode 100644 index 00000000..87a46d0a --- /dev/null +++ b/harnesses/wormhole-vaa-latency/go.mod @@ -0,0 +1,18 @@ +module wormhole-vaa-latency + +go 1.24.0 + +require github.com/prometheus/client_golang v1.23.2 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/sys v0.36.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect +) diff --git a/harnesses/wormhole-vaa-latency/go.sum b/harnesses/wormhole-vaa-latency/go.sum new file mode 100644 index 00000000..f415d46b --- /dev/null +++ b/harnesses/wormhole-vaa-latency/go.sum @@ -0,0 +1,46 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/public/logos/wormhole.png b/public/logos/wormhole.png new file mode 100644 index 0000000000000000000000000000000000000000..4c2c2a49ca6a9a7e2a028b641709fd46f51f07f2 GIT binary patch literal 9558 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 literal 0 HcmV?d00001 diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index 6026318f..eab8ccf5 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -732,6 +732,12 @@ export const PROVIDER_REGISTRY: Record = { "Gelato Network runs a rollup-as-a-service platform and operates RPC infrastructure for OP Stack + Arbitrum Orbit chains it hosts. On the RPC latency cluster it appears as the Kraken Ink chain's Gelato-backed official endpoint (`rpc-gel.inkonchain.com`), running active-active alongside the QuickNode-backed sibling.", twitter: "@gelatonetwork", }, + wormhole: { + url: "https://wormhole.com", + description: + "Wormhole is a cross-chain messaging network. A network of 19 Guardian nodes observes events on source chains and signs VAAs (Verified Action Approvals) once source-chain finality is reached; 13-of-19 Guardian signatures constitute quorum. Any signed VAA can then be relayed to any destination chain Wormhole supports (30+ EVM, Solana, Sui, Aptos, Near, Cosmos SDK chains) to unlock the corresponding action.", + twitter: "@wormhole", + }, infura: { url: "https://www.infura.io", description: diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index 98d9fb8a..38122e2b 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -99,6 +99,7 @@ const RAW: Record = { lighter: "/logos/lighter.svg", debridge: "/logos/debridge.svg", "near-intents": "/logos/near-intents.svg", + wormhole: "/logos/wormhole.png", // ─── Public RPC providers ─── publicnode: "/logos/publicnode.avif", diff --git a/src/lib/spec.ts b/src/lib/spec.ts index 64188bcf..a85d5550 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -301,12 +301,8 @@ const loadBenchmarkUnfilteredCached = unstable_cache( // v31: 084 indexer-latency dropped entirely (spec + harness deleted; // HyperSync + The Graph required paid credentials for sustained // cadence). Bench SET shrank. - // v38: mobula dropped from pm-data-freshness (they stopped serving 2026-07-19). - // v39: bench 091 osmosis-rpc ship (Cosmos SDK Kind added to harness). - // v40: benches 092 hyperliquid-rpc + 093 tron-rpc ship (bench SET grew by 2). - // v41: benches 094-100 ship (cosmos-hub, injective, neutron, world-chain, kaia, ink, opbnb — bench SET grew by 7). - // v42: drop thirdweb from 4 new benches (ink, world-chain, kaia, opbnb) to unblock prod deploy stuck on /products/thirdweb 500. - ["bench-unfiltered-v42", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + // Wormhole VAA latency ship (bench 101). + ["bench-unfiltered-v43", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -510,12 +506,8 @@ const loadAllBenchmarksCached = unstable_cache( // v32: bumped with bench-unfiltered-v30 (081 slug rename ws-head-latency // -> ws-head-latency-ethereum + ungate on prod). // v34: bumped with bench-unfiltered-v31 (084 indexer-latency dropped). - // v41: bumped in lockstep with bench-unfiltered-v38 (mobula drop). - // v42: bumped in lockstep with bench-unfiltered-v39 (osmosis-rpc ship). - // v43: bumped in lockstep with bench-unfiltered-v40 (hyperliquid-rpc + tron-rpc ship). - // v44: bumped in lockstep with bench-unfiltered-v41 (7 chains ship batch). - // v45: bumped in lockstep with bench-unfiltered-v42 (drop thirdweb from 4 new benches, unblock prod). - ["all-benchmarks-v45", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + // Wormhole VAA latency ship (bench 101). + ["all-benchmarks-v46", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); export const loadAllBenchmarks = cache(loadAllBenchmarksCached);