From 2dadfc40b9dfc6863f688d73aa4e53bfe4bd1c68 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Fri, 24 Jul 2026 16:10:22 +0200 Subject: [PATCH 1/2] rwa-yield-accuracy: add SyrupUSDC (Maple, ERC-4626) to cohort 4th active probe. Reads Maple SyrupUSDC vault at 0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b via ERC-4626 convertToAssets(1 share) at latest + t-30d + t-7d blocks. NAV appreciation model, same clean methodology as OUSG/USTB. Advertised APY: 482 bps (DefiLlama base APY, excludes SYRUP token rewards). Delivered on-chain: ~346 bps over last 30d. Note: SyrupUSDC is collateralized pool lending, not a T-bill wrapper. Included under bench 089's broader 'on-chain yield-bearing stables' framing. --- benchmarks/rwa-yield-accuracy.yml | 14 ++ .../rwa-yield-accuracy/cmd/script/main.go | 23 +-- .../cmd/script/syrupusdc.go | 138 ++++++++++++++++++ .../rwa-yield-accuracy/promised-yields.yml | 6 + 4 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 harnesses/rwa-yield-accuracy/cmd/script/syrupusdc.go diff --git a/benchmarks/rwa-yield-accuracy.yml b/benchmarks/rwa-yield-accuracy.yml index 7266b720..5e94e365 100644 --- a/benchmarks/rwa-yield-accuracy.yml +++ b/benchmarks/rwa-yield-accuracy.yml @@ -147,3 +147,17 @@ providers: success: rwa_yield_probe_ok{token="ousg"} sample_size: rwa_yield_aum_usd{token="ousg"} series: rwa_yield_delivered_bps_30d{token="ousg"} + + - slug: syrup-usdc + name: SyrupUSDC + tag: "Maple, Ethereum, ERC-4626 NAV" + formula: "ERC-4626 convertToAssets(1 share) delta over 30d, annualized. SyrupUSDC is Maple's yield-bearing USDC vault backed by institutional loans, not T-bills. Compared to the pool's base APY on DefiLlama (excludes SYRUP token incentives)." + queries: + p50: rwa_yield_deviation_bps_30d{token="syrup-usdc"} + p90: rwa_yield_deviation_bps_7d{token="syrup-usdc"} + p99: rwa_yield_deviation_bps_lifetime{token="syrup-usdc"} + mean: rwa_yield_delivered_bps_30d{token="syrup-usdc"} + success: rwa_yield_probe_ok{token="syrup-usdc"} + sample_size: rwa_yield_aum_usd{token="syrup-usdc"} + series: rwa_yield_delivered_bps_30d{token="syrup-usdc"} + diff --git a/harnesses/rwa-yield-accuracy/cmd/script/main.go b/harnesses/rwa-yield-accuracy/cmd/script/main.go index dd7381b6..c2ef8450 100644 --- a/harnesses/rwa-yield-accuracy/cmd/script/main.go +++ b/harnesses/rwa-yield-accuracy/cmd/script/main.go @@ -18,26 +18,19 @@ import ( // this bench is: (1) implement IssuerProbe, (2) add its slug to // promised-yields.yml, (3) append here. // -// Active cohort: USDY (rebase, totalSupply growth), USTB (NAV via -// Chainlink feed), OUSG (NAV via OndoOracle). All three are fully -// on-chain measurable — no off-chain HTTP dependency, no treasury -// wallet assumption. +// Active cohort: +// - USDY (Ondo, rebase) +// - USTB (Superstate, Chainlink NAV feed) +// - OUSG (Ondo, OndoOracle IPriceOracle) +// - SyrupUSDC (Maple, ERC-4626 convertToAssets) // -// BUIDL and BENJI stay dormant: -// - BUIDL: distributor 0x5072Ed40EBa6bE38C2370cAD1Cb1df0202924e53 -// was identified but calls bulkIssuance (mints more BUIDL, not -// USDC transfers). The current dividend.go model that scans USDC -// Transfer events doesn't fit; needs a rebase-style measurement -// PLUS a way to separate yield mints from new subscriptions. -// - BENJI: Ethereum wrapper (0x3DDc...50dc9) has 3 holders, ~$48M, -// no on-chain sharePrice function. NAV is $1.00 by design; yield -// is only knowable via Franklin's off-chain fund page. Reached -// out to digitalassets@franklintempleton.com; unblock once they -// confirm an API endpoint or Chainlink feed. +// BUIDL and BENJI dormant: distributor mints conflate yield with +// subscriptions (BUIDL), Ethereum wrapper lacks on-chain NAV (BENJI). var probes = []IssuerProbe{ NewUSDYProbe(), NewUSTBProbe(), NewOUSGProbe(), + NewSyrupUSDCProbe(), // NewBUIDLProbe(), // TODO: split bulkIssuance mints from subscriptions // NewBENJIProbe(), // TODO: waiting Franklin NAV endpoint confirmation } diff --git a/harnesses/rwa-yield-accuracy/cmd/script/syrupusdc.go b/harnesses/rwa-yield-accuracy/cmd/script/syrupusdc.go new file mode 100644 index 00000000..8d272316 --- /dev/null +++ b/harnesses/rwa-yield-accuracy/cmd/script/syrupusdc.go @@ -0,0 +1,138 @@ +package main + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +// SyrupUSDC is Maple Finance's yield-bearing USDC token — an ERC-4626 +// vault whose underlying position is a book of over-collateralized +// (and, more recently, undercollateralized) institutional USDC loans. +// Share price grows daily as pool interest accrues; no rebase, no +// dividend transfers. +// +// Contract (Ethereum mainnet): +// 0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b (6 decimals, asset=USDC) +// +// Yield model: NAV appreciation via ERC-4626 convertToAssets(). +// Passing 1e6 (one share in 6 decimals) returns the current USDC value +// of one share directly. Called at a historical block, we get the NAV +// that was current at that block — same clean methodology as OUSG and +// USTB, no off-chain data needed. +// +// Scope note: SyrupUSDC is NOT a T-bill wrapper — it's collateralized +// pool lending, so deviation from advertised APY reflects both loan- +// book performance AND yield disclosure honesty. Included in bench 089 +// under the broader framing "on-chain yield-bearing stables promising +// an APY," not the narrow "tokenized T-bill" reading. +// +// Advertised APY tracked here is the pool's BASE APY (yield generated +// by the loan book), not the boosted APY including SYRUP token +// incentives. This matches what we can measure on-chain: convertToAssets +// only reflects underlying pool yield, not off-chain reward +// distributions. + +const ( + syrupUSDCContractEthereum = "0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b" + // One share in the token's 6-decimal base units. Passing this to + // convertToAssets returns USDC-per-share directly. + syrupUSDCOneShareRaw = uint64(1_000_000) +) + +var erc4626ConvertToAssetsSelector []byte + +func init() { + erc4626ConvertToAssetsSelector = crypto.Keccak256([]byte("convertToAssets(uint256)"))[:4] +} + +type syrupUSDCProbe struct { + contract common.Address +} + +func NewSyrupUSDCProbe() IssuerProbe { + return &syrupUSDCProbe{ + contract: common.HexToAddress(syrupUSDCContractEthereum), + } +} + +func (p *syrupUSDCProbe) Slug() string { return "syrup-usdc" } +func (p *syrupUSDCProbe) Issuer() string { return "maple" } +func (p *syrupUSDCProbe) Chain() string { return "ethereum" } + +func (p *syrupUSDCProbe) Measure(ctx context.Context, rpc *ethclient.Client) (*Measurement, error) { + now := time.Now().UTC() + + latest, err := rpc.BlockNumber(ctx) + if err != nil { + return nil, fmt.Errorf("latest block: %w", err) + } + block30dAgo := blockOffsetBySeconds(latest, int64(Window30d.Seconds())) + block7dAgo := blockOffsetBySeconds(latest, int64(Window7d.Seconds())) + + // USDC-per-share at each snapshot. 6-decimal in, 6-decimal out; the + // returned float64 is already scaled to USD. + navNow, err := p.readSharePriceUSD(ctx, rpc, nil) + if err != nil { + return nil, fmt.Errorf("nav now: %w", err) + } + nav30d, err := p.readSharePriceUSD(ctx, rpc, block30dAgo) + if err != nil { + return nil, fmt.Errorf("nav 30d: %w", err) + } + nav7d, err := p.readSharePriceUSD(ctx, rpc, block7dAgo) + if err != nil { + return nil, fmt.Errorf("nav 7d: %w", err) + } + + yield30dBps := annualizedYieldBpsFromNAV(navNow, nav30d, 30.0) + yield7dBps := annualizedYieldBpsFromNAV(navNow, nav7d, 7.0) + + supply, err := readERC20TotalSupply(ctx, rpc, p.contract, nil) + if err != nil { + supply = 0 + } + supplyUnits := supply / 1e6 + + return &Measurement{ + Token: p.Slug(), + Issuer: p.Issuer(), + Chain: p.Chain(), + DeliveredBps30d: yield30dBps, + DeliveredBps7d: yield7dBps, + DeliveredBpsLifetime: 0, + TotalSupplyUnits: supplyUnits, + AUMUSD: supplyUnits * navNow, + NewDistributionsUSD: 0, + MeasuredAt: now, + }, nil +} + +// readSharePriceUSD returns USDC-per-share as a float. Calls +// convertToAssets(1 share) which for a 6-decimal vault yields a +// 6-decimal USDC amount that we scale to USD (divide by 1e6). +func (p *syrupUSDCProbe) readSharePriceUSD(ctx context.Context, rpc *ethclient.Client, blockNumber *big.Int) (float64, error) { + // ABI-encoded call: selector + uint256(1_000_000) left-padded. + amount := new(big.Int).SetUint64(syrupUSDCOneShareRaw) + data := make([]byte, 0, 4+32) + data = append(data, erc4626ConvertToAssetsSelector...) + data = append(data, common.LeftPadBytes(amount.Bytes(), 32)...) + + msg := ethereum.CallMsg{To: &p.contract, Data: data} + result, err := rpc.CallContract(ctx, msg, blockNumber) + if err != nil { + return 0, err + } + if len(result) < 32 { + return 0, fmt.Errorf("convertToAssets: short response") + } + assets := new(big.Int).SetBytes(result[0:32]) + f, _ := new(big.Float).SetInt(assets).Float64() + return f / 1e6, nil +} diff --git a/harnesses/rwa-yield-accuracy/promised-yields.yml b/harnesses/rwa-yield-accuracy/promised-yields.yml index ed4267da..1502f927 100644 --- a/harnesses/rwa-yield-accuracy/promised-yields.yml +++ b/harnesses/rwa-yield-accuracy/promised-yields.yml @@ -54,3 +54,9 @@ issuers: source: "https://ondo.finance/ousg" source_date: 2026-07-22 notes: "Ondo OUSG NAV-appreciation yield estimate (probe inactive; NAV endpoint TBD)." + + - token: syrup-usdc + promised_apy_bps: 482 + source: "https://yields.llama.fi/pools" + source_date: 2026-07-24 + notes: "Maple SyrupUSDC base APY (loan-book yield only, excludes SYRUP token incentives). Sourced from DefiLlama filter {project:maple, symbol:USDC, chain:Ethereum}. Delivered yield measured via ERC-4626 convertToAssets rate delta - matches the base disclosure, not the boosted APY." From 7af002af36be40ee03cc989ceab8fb0f925da79e 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 2/2] 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/token-trade-coverage.yml | 2 +- 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 | 15 + src/lib/logo-manifest.ts | 1 + src/lib/spec.ts | 6 +- 12 files changed, 718 insertions(+), 3 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/token-trade-coverage.yml b/benchmarks/token-trade-coverage.yml index 4aabbcc2..063d2501 100644 --- a/benchmarks/token-trade-coverage.yml +++ b/benchmarks/token-trade-coverage.yml @@ -7,7 +7,7 @@ seo_title: "Best token trade data API 2026 (Mobula vs Bitquery vs Codex)" seo_description: "Which onchain data API returns the most complete trade history for a newly launched token? Live coverage % across Mobula, Bitquery and Codex on Solana, Ethereum, BSC, Base and Stellar." subtitle: For each reference token per chain, we fetch every trade in a fixed measurement window from each provider and compare against the union baseline. Higher percent means the provider returned more of the true trade set, with fewer missed DEXs and fewer dropped fills. category: Aggregators -status: draft +status: live metric: Trade capture rate unit: pct higher_is_better: true 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 1c475fed..cd3906c4 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -711,6 +711,21 @@ export const PROVIDER_REGISTRY: Record = { "ThirdWeb operates a keyless public RPC gateway at `.rpc.thirdweb.com` covering 2000+ EVM chains via chain-id routing. Free tier is rate-limited per IP with no signup; paid tiers unlock higher throughput and dedicated endpoints. Used keyless across 13 chains on the RPC latency cluster (arbitrum, ethereum, polygon, optimism, base, bnb, avalanche, linea, worldchain, kaia, ink, opbnb + more).", twitter: "@thirdweb", }, +<<<<<<< HEAD +======= + gelato: { + url: "https://gelato.network", + description: + "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", + }, +>>>>>>> b8ebfa91 (add bench 101 wormhole-vaa-latency (Wormhole VAA finalization by source chain) (#1374)) infura: { url: "https://www.infura.io", description: diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index e7d3ac9f..65561560 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..1548a37a 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -306,7 +306,8 @@ const loadBenchmarkUnfilteredCached = unstable_cache( // 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"], + // v43: add bench 101 wormhole-vaa-latency (bench SET grew by 1). + ["bench-unfiltered-v43", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -515,7 +516,8 @@ const loadAllBenchmarksCached = unstable_cache( // 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"], + // v46: bumped in lockstep with bench-unfiltered-v43 (add bench 101 wormhole-vaa-latency). + ["all-benchmarks-v46", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); export const loadAllBenchmarks = cache(loadAllBenchmarksCached);