diff --git a/benchmarks/osmosis-rpc.yml b/benchmarks/osmosis-rpc.yml new file mode 100644 index 00000000..5f6f3387 --- /dev/null +++ b/benchmarks/osmosis-rpc.yml @@ -0,0 +1,211 @@ +# OpenChainBench. Bench № 091 + +slug: osmosis-rpc +number: "091" +title: Fastest free Osmosis RPC, live no-key endpoint latency +seo_title: "Fastest free Osmosis RPC 2026" +seo_description: "{{best_name}} leads free Osmosis RPC at {{best_p50}} (Tendermint status p50, 24h). 5 no-key providers measured every 60s from 3 regions." +subtitle: HTTP round-trip latency for the Tendermint `status` method against every free, no-key public Osmosis Cosmos SDK endpoint, audited every 60 seconds from 3 regions. + +category: RPCs +status: live +metric: RPC latency +unit: ms +higher_is_better: false + +seo_intro: | + Osmosis is the first Cosmos SDK chain in the RPC latency cluster. + CometBFT (Tendermint) consensus, JSON-RPC 2.0 wire but a chain-family + specific method set: the probe is Tendermint `status` (returns + `sync_info.latest_block_height` as a decimal string plus + `latest_block_hash`) rather than `eth_getBlockByNumber`. Everything + else on this page is the same bench pattern used on Ethereum, Base, + Solana, Polkadot and the 20+ other chains: identical POST every 60 + seconds from three regions, latency measured client-side at + millisecond precision, per-region breakdowns first class. Provider + cohort at launch: Osmosis Foundation's `rpc.osmosis.zone`, Polkachu, + PublicNode (Allnodes), Imperator and LavenderFive. Lava, OnFinality, + Numia, AutoStake, BlockApsis, WhisperNode, Enigma-Validator, + StakeTown and reece.sh were audited alongside and excluded (Lava + 403 on the public osmosis endpoint, OnFinality osmosis public + gateway timing out at probe cadence, Numia 401 without a key, the + others returned connect errors or moved paths during the launch + sweep); each will be revisited when a stable no-key path reappears. + +abstract: | + Per-chain member of the RPC latency cluster, extended for Cosmos SDK. + We measure the round-trip latency of a single, identical JSON-RPC + call (Tendermint `status`) against every no-key public Osmosis + Cosmos SDK endpoint that sustains continuous probing, 5 providers at + launch, every 60 seconds, from us-east, eu-west and Singapore. The + harness classifies every response (ok / http_err / jsonrpc_err / + stale / timeout) with a Cosmos-scaled staleness gap (40 blocks ≈ 4 + minutes at Osmosis's ~6 s block time), so the leaderboard rewards + sustained, honest availability rather than a fast error message. + The cross-chain view lives on the parent `rpc-capabilities` + benchmark; this page is the Osmosis-scoped answer with per-region + breakdowns as a first-class dimension. + +methodology: + - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus `avg(quantile_over_time(...))`; per-region breakdowns are first-class on this page via the region tabs." + - "Payload: `{\"jsonrpc\":\"2.0\",\"id\":,\"method\":\"status\",\"params\":[]}`. Plain HTTP POST, identical for every endpoint, no API key in any request. Non-cacheable by design: the rotating id defeats edge caches that would answer a bare status query without touching a node." + - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms → 10 s), so percentiles are computed via Prometheus `quantile_over_time` over the last 24 hours." + - "Call-result classification: `ok` (HTTP 200 + non-empty result with a parsable decimal `sync_info.latest_block_height`), `http_err`, `jsonrpc_err` (HTTP 200 carrying an error body), `stale` (more than 40 blocks behind the cross-provider tip, roughly 4 minutes at Osmosis's ~6 s block time), `timeout`. Latency without reliability is a misleading ranking signal." + - "No archive-depth loop on this chain. Cosmos SDK chains access historical state via ABCI queries keyed on module + KV-store, which does not map onto the flat `eth_getBalance(address, height)` probe cleanly." + - "No cross-provider hash quorum on this chain at v1. Tendermint `status` does return `latest_block_hash` on the same block the height is reported for, so a quorum check is technically feasible, but the initial Osmosis add-on ships without it to keep the reliability change surface small; the hash normalisation across Cosmos chains will be validated before wiring bench 083 here." + - "This page is part of the per-chain RPC cluster derived from the cross-chain [rpc-capabilities](https://openchainbench.com/benchmarks/rpc-capabilities) benchmark; the identical harness, cadence and exclusion rules apply on every chain." + - "Chain scope: every query on this page is pinned to `chain=\"osmosis\"`. Provider coverage at launch: 5 no-key endpoints (Osmosis Foundation, Polkachu, PublicNode, Imperator, LavenderFive). Excluded by the launch audit: Lava (osmosis.tendermintrpc.lava.build 403 without a key despite public branding), OnFinality (osmosis public gateway timing out at probe cadence, unlike their stable Polkadot gateway), Numia (401 without key), AutoStake (404, path may have moved), BlockApsis + WhisperNode + Enigma-Validator + StakeTown + reece.sh (connect errors, likely IPv6-only or geo-gated), Ankr and Chainstack (paid Cosmos tiers only)." + +findings: + - "{{best_name}} currently leads free Osmosis RPC at {{best_p50}} (Tendermint `status` p50, 24h) across 5 measured providers." + - "{{name:osmosis-official}} is operated by the Osmosis Foundation itself; the bench measures the same public endpoint that Keplr and Osmosis Frontier route through by default, so the number here reflects what a wallet or dApp integrator sees end-to-end." + - "{{name:polkachu}} runs one of the largest Cosmos validator + public RPC networks; latency here is directly comparable to the multi-chain gateways because we probe the same `status` method under the same 60 s cadence rule." + - "{{name:publicnode}} extends its universal multi-chain footprint from Ethereum + Polkadot onto Cosmos without a keyed path; the reliability column is what to read alongside p50 here since Cosmos JSON-RPC has different failure modes than EVM (peer set churn during epoch boundaries, gRPC gateway restarts)." + +faq: + - q: "What is the fastest free Osmosis RPC right now?" + a: "{{best_name}} currently leads at {{best_p50}} (Tendermint `status` p50 over the last 24h), measured against 5 no-key providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples, so the answer on this page is the answer right now, not a quarterly snapshot. Use the region tabs to see the leader from the origin closest to your deployment." + - q: "Which Osmosis RPCs work without an API key?" + a: "Five endpoints sustain continuous keyless probing at launch: Osmosis Foundation's `rpc.osmosis.zone`, Polkachu (`osmosis-rpc.polkachu.com`), PublicNode / Allnodes (`osmosis-rpc.publicnode.com`), Imperator (`rpc-osmosis.imperator.co`) and LavenderFive (`rpc.lavenderfive.com/osmosis`). Every listed endpoint was live-verified with a `status` POST returning a parsable decimal `sync_info.latest_block_height` before inclusion. Excluded by the audit: Lava's `osmosis.tendermintrpc.lava.build` returned 403 without a key, OnFinality's public osmosis gateway kept timing out at probe cadence, Numia rejected without an API key (401), Ankr and Chainstack require paid Cosmos tiers, and several community-run endpoints (BlockApsis, WhisperNode, Enigma-Validator, StakeTown, reece.sh) returned connect errors during the sweep." + - q: "Does the fastest Osmosis RPC change by region?" + a: "Often. Cosmos public infra is heavily weighted toward validator ops in specific regions; a gateway that wins from Amsterdam can lose from Singapore by multiples, especially for community-run providers with a single data centre. The region tabs at the top of the page re-scope every number on the page to a single origin; pick the one closest to where your requests actually originate." + - q: "How is Osmosis RPC latency measured here, technically?" + a: "One identical Tendermint JSON-RPC POST (`status`) every 60 seconds against each provider from each of 3 regions, using the same plain HTTP client the EVM and Substrate clusters use. The response is a `result` object containing a `sync_info` block; the harness reads `sync_info.latest_block_height` (a decimal string on Cosmos SDK, unlike EVM / Substrate which hex-encode) and parses it with `strconv.ParseUint`. Wall-clock round-trip is recorded at millisecond precision; p50/p90/p99 are computed via Prometheus `quantile_over_time` over 24 hours. Responses are classified (`ok` / `http_err` / `jsonrpc_err` / `stale` / `timeout`) so an endpoint stuck on an old head or returning errors behind HTTP 200 is never ranked as fastest." + - q: "Why is the probe `status` and not `abci_info` or `block`?" + a: "Two reasons. First, Tendermint `status` returns `sync_info.latest_block_height` on every Cosmos SDK chain in a shape the cluster's cross-provider tip machinery understands without a chain-specific code path. Second, `abci_info` returns application-level metadata that a polite CDN can synthesise without touching the underlying node, defeating the point of a latency probe on the RPC path; `block` requires a height parameter and would either bias toward providers with a fast archive path or duplicate the head-block work `status` already does. `status` forces the endpoint to actually read the head from the consensus engine, which is what integrators care about." + - q: "Why is there no archive-depth or hash-quorum column for Osmosis?" + a: "Cosmos SDK chains access historical state via ABCI queries keyed on module + KV-store, which has no chain-agnostic depth analog for the EVM `eth_getBalance(address, height)` probe the cluster uses on other chains. Hash quorum (bench 083 cross-provider height-hash agreement) is technically feasible on Cosmos because `latest_block_hash` in the `status` response corresponds to the same block the height is reported for, but the initial Osmosis add-on ships without it to keep the reliability change surface small; the hash normalisation across Cosmos chains will be validated before wiring the quorum path here. Both columns are omitted honestly rather than filled with a proxy that would misrepresent the chain." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities + +prometheus: + window: 24h + freshness_metric: rpc_latency_milliseconds + +# Per-cell (region) ranking matrix for scoped badge claims. Chain is +# fixed for the whole bench, so cells key on region alone. +rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="osmosis"}) + +# Region is the only dimension: chain is baked into every query. +dimensions: + region: + - { value: all, label: All regions } + - { value: us-east, label: US-East } + - { value: eu-west, label: EU-West } + - { value: sgp, label: Singapore } + +providers: + - slug: osmosis-official + name: Osmosis Foundation + tag: Osmosis Foundation's official public Cosmos SDK RPC + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single Tendermint `status` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to Osmosis Foundation's official public RPC (`rpc.osmosis.zone`)." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="osmosis-official", chain="osmosis"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="osmosis-official", chain="osmosis"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="osmosis-official", chain="osmosis"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="osmosis-official", chain="osmosis"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="osmosis-official", chain="osmosis"}) / sum(ocb:rpc_call:rate_24h{provider="osmosis-official", chain="osmosis"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="osmosis-official", chain="osmosis"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="osmosis-official", chain="osmosis"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="osmosis-official", chain="osmosis", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="osmosis-official", chain="osmosis", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="osmosis-official", chain="osmosis", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="osmosis-official", chain="osmosis", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="osmosis-official", chain="osmosis", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="osmosis-official", chain="osmosis", region="sgp"}[1h]) + + - slug: polkachu + name: Polkachu + tag: Community Cosmos validator + public RPC network + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single Tendermint `status` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to Polkachu's no-key Osmosis RPC endpoint." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="osmosis"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="polkachu", chain="osmosis"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="polkachu", chain="osmosis"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="polkachu", chain="osmosis"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="polkachu", chain="osmosis"}) / sum(ocb:rpc_call:rate_24h{provider="polkachu", chain="osmosis"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="polkachu", chain="osmosis"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="osmosis"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="osmosis", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="osmosis", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="osmosis", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="osmosis", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="osmosis", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="osmosis", region="sgp"}[1h]) + + - slug: publicnode + name: PublicNode + tag: Allnodes-operated, 70+ chains, includes Cosmos + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single Tendermint `status` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to PublicNode's no-key Osmosis RPC endpoint." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="osmosis"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="publicnode", chain="osmosis"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="publicnode", chain="osmosis"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="publicnode", chain="osmosis"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="publicnode", chain="osmosis"}) / sum(ocb:rpc_call:rate_24h{provider="publicnode", chain="osmosis"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="publicnode", chain="osmosis"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="osmosis"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="osmosis", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="osmosis", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="osmosis", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="osmosis", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="osmosis", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="osmosis", region="sgp"}[1h]) + + - slug: imperator + name: Imperator + tag: Cosmos-native infra provider, IBC + Osmosis ecosystem focus + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single Tendermint `status` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to Imperator's no-key Osmosis RPC endpoint." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="imperator", chain="osmosis"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="imperator", chain="osmosis"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="imperator", chain="osmosis"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="imperator", chain="osmosis"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="imperator", chain="osmosis"}) / sum(ocb:rpc_call:rate_24h{provider="imperator", chain="osmosis"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="imperator", chain="osmosis"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="imperator", chain="osmosis"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="imperator", chain="osmosis", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="imperator", chain="osmosis", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="imperator", chain="osmosis", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="imperator", chain="osmosis", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="imperator", chain="osmosis", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="imperator", chain="osmosis", region="sgp"}[1h]) + + - slug: lavenderfive + name: LavenderFive + tag: Cosmos validator + no-key RPC path, multi-chain + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single Tendermint `status` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to LavenderFive's no-key Osmosis RPC endpoint." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lavenderfive", chain="osmosis"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="lavenderfive", chain="osmosis"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="lavenderfive", chain="osmosis"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="lavenderfive", chain="osmosis"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="lavenderfive", chain="osmosis"}) / sum(ocb:rpc_call:rate_24h{provider="lavenderfive", chain="osmosis"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="lavenderfive", chain="osmosis"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="lavenderfive", chain="osmosis"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lavenderfive", chain="osmosis", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="lavenderfive", chain="osmosis", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lavenderfive", chain="osmosis", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="lavenderfive", chain="osmosis", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lavenderfive", chain="osmosis", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="lavenderfive", chain="osmosis", region="sgp"}[1h]) diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go index 25d179a6..a334cae1 100644 --- a/harnesses/rpc-capabilities/cmd/script/config.go +++ b/harnesses/rpc-capabilities/cmd/script/config.go @@ -40,6 +40,15 @@ type Chain struct { // polkadotStaleBlockGap), no archive-depth loop (Polkadot's // state model does not map onto the eth_getBalance-by-depth // probe cleanly). + // "cosmos": Tendermint / CometBFT `status` method against the + // Cosmos JSON-RPC, block-based staleness (Osmosis ~6 s blocks, + // see cosmosStaleBlockGap), no archive-depth loop. Cosmos chains + // access historical state via ABCI queries keyed on module + + // KV-store, which does not map onto the flat eth_getBalance + // probe. Consensus quorum is opt-out for now because + // latest_block_hash on Tendermint changes on every block just + // like EVM, but a first pass ships without it to keep the + // initial Osmosis add-on isolated to latency + reliability. Kind string } @@ -93,6 +102,32 @@ func chains() []Chain { {Slug: "publicnode", Name: "PublicNode", URL: envDefault("RPC_URL_POLKADOT_PUBLICNODE", "https://polkadot-rpc.publicnode.com")}, }, }, + // ─── Osmosis (Cosmos SDK, CometBFT / Tendermint) — first + // Cosmos chain in the cluster. Probed via Tendermint JSON-RPC + // `status` (returns sync_info.latest_block_height + + // latest_block_hash). Providers verified keyless with a live + // `status` POST returning a parsable decimal height on + // 2026-07-23. Excluded by that sweep: Lava + // (osmosis.tendermintrpc.lava.build 403 without a key despite + // the "public" branding), OnFinality (public osmosis endpoint + // timing out at probe cadence, unlike their stable Polkadot + // gateway), Numia (401 without key), AutoStake (404, path may + // have moved), BlockApsis + WhisperNode + Enigma-Validator + + // StakeTown + reece.sh (curl-side connect errors, likely IPv6- + // only or geo-gated). Ankr and Chainstack require paid Cosmos + // tiers. + { + Slug: "osmosis", + Name: "Osmosis", + Kind: "cosmos", + Providers: []Provider{ + {Slug: "osmosis-official", Name: "Osmosis Foundation", URL: envDefault("RPC_URL_OSMOSIS_OFFICIAL", "https://rpc.osmosis.zone")}, + {Slug: "polkachu", Name: "Polkachu", URL: envDefault("RPC_URL_OSMOSIS_POLKACHU", "https://osmosis-rpc.polkachu.com")}, + {Slug: "publicnode", Name: "PublicNode", URL: envDefault("RPC_URL_OSMOSIS_PUBLICNODE", "https://osmosis-rpc.publicnode.com")}, + {Slug: "imperator", Name: "Imperator", URL: envDefault("RPC_URL_OSMOSIS_IMPERATOR", "https://rpc-osmosis.imperator.co")}, + {Slug: "lavenderfive", Name: "LavenderFive", URL: envDefault("RPC_URL_OSMOSIS_LAVENDERFIVE", "https://rpc.lavenderfive.com:443/osmosis")}, + }, + }, // ─── Solana mainnet — added 2026-07-12, all 5 endpoints keyless // and live-verified (getSlot + getLatestBlockhash + getVersion, // mutually consistent advancing slots). Excluded by that sweep: diff --git a/harnesses/rpc-capabilities/cmd/script/probe.go b/harnesses/rpc-capabilities/cmd/script/probe.go index a8b92ddb..3856911e 100644 --- a/harnesses/rpc-capabilities/cmd/script/probe.go +++ b/harnesses/rpc-capabilities/cmd/script/probe.go @@ -35,6 +35,13 @@ const ( // gap gives a 12s-block chain, scaled for the faster relay // cadence. polkadotStaleBlockGap uint64 = 40 + // cosmosStaleBlockGap: Osmosis (CometBFT) commits one block every + // ~6 s (same order as Polkadot's relay), so 40 blocks ≈ 4 min + // gives the same reliability tolerance the EVM cluster gets on + // 12 s-block chains. Kept as its own constant so a future Cosmos + // chain with a faster block time (e.g. Injective ~0.65 s) can + // override without touching the Polkadot reader. + cosmosStaleBlockGap uint64 = 40 ) // chainTips tracks the highest block seen for each chain across all @@ -197,6 +204,8 @@ func probeOne(ctx context.Context, c Chain, p Provider) { block, result, latency, err = callLatestSlot(probeCtx, p.URL) case "polkadot": block, hash, result, latency, err = callSubstrateHeader(probeCtx, p.URL) + case "cosmos": + block, hash, result, latency, err = callCosmosStatus(probeCtx, p.URL) default: block, hash, result, latency, err = callLatestBlock(probeCtx, p.URL) } @@ -210,6 +219,8 @@ func probeOne(ctx context.Context, c Chain, p Provider) { gap = solanaStaleSlotGap case "polkadot": gap = polkadotStaleBlockGap + case "cosmos": + gap = cosmosStaleBlockGap } if tip > 0 && block+gap < tip { result = "stale" @@ -221,9 +232,14 @@ func probeOne(ctx context.Context, c Chain, p Provider) { // so a dead endpoint ages out instead of freezing. Skipped on // chains whose probe cannot return the current-block hash // (Solana: getSlot returns a number only; Polkadot: header - // carries parentHash, not the current block hash). + // carries parentHash, not the current block hash; Cosmos: + // Tendermint `status` returns `latest_block_hash` on the same + // block the height is reported for, so quorum is technically + // feasible but the v1 Osmosis add-on ships without it to keep + // the reliability change surface small — revisit once the + // hash normalisation across Cosmos chains is validated). switch c.Kind { - case "solana", "polkadot": + case "solana", "polkadot", "cosmos": // no consensus participation default: if result == "ok" || result == "stale" { @@ -401,3 +417,77 @@ func callSubstrateHeader(ctx context.Context, url string) (block uint64, hash st } return n, hdr.ParentHash, "ok", latencyMs, nil } + +// tendermintStatus is the shape returned by CometBFT / Tendermint +// `status` on every Cosmos SDK chain. Only `sync_info` is parsed here; +// `node_info` + `validator_info` are ignored because they carry +// operator-scoped metadata that doesn't feed the reliability signal. +// `latest_block_height` is a decimal string (unlike EVM / Substrate +// which hex-encode). `latest_block_hash` is hex (no `0x` prefix) so it +// slots into the same consensus.observe path if ever wired. +type tendermintStatus struct { + SyncInfo struct { + LatestBlockHeight string `json:"latest_block_height"` + LatestBlockHash string `json:"latest_block_hash"` + } `json:"sync_info"` +} + +// callCosmosStatus is the Cosmos SDK probe path: Tendermint `status` +// via JSON-RPC POST with a rotating request id (same anti-cache rule as +// the EVM header fetch). Returns the current block height + hash so the +// caller plugs the probe result into the shared tips machinery. +// Consensus participation is opted out in probeOne for v1. +func callCosmosStatus(ctx context.Context, url string) (block uint64, hash string, result string, latencyMs float64, err error) { + body := []byte(fmt.Sprintf( + `{"jsonrpc":"2.0","method":"status","params":[],"id":%d}`, + time.Now().UnixNano(), + )) + req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)") + client := &http.Client{Timeout: probeTimeout} + + start := time.Now() + resp, err := client.Do(req) + latencyMs = float64(time.Since(start).Milliseconds()) + + if err != nil { + if ctx.Err() != nil || strings.Contains(err.Error(), "deadline exceeded") || strings.Contains(err.Error(), "Timeout") { + return 0, "", "timeout", latencyMs, err + } + return 0, "", "http_err", latencyMs, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + _, _ = io.Copy(io.Discard, resp.Body) + return 0, "", "http_err", latencyMs, fmt.Errorf("status %d", resp.StatusCode) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return 0, "", "http_err", latencyMs, err + } + var r rpcBlockEnvelope + if err := json.Unmarshal(raw, &r); err != nil { + return 0, "", "http_err", latencyMs, err + } + if r.Error != nil { + return 0, "", "jsonrpc_err", latencyMs, fmt.Errorf("rpc -%d: %s", r.Error.Code, r.Error.Message) + } + if len(r.Result) == 0 || string(r.Result) == "null" { + return 0, "", "jsonrpc_err", latencyMs, fmt.Errorf("empty result") + } + var st tendermintStatus + if err := json.Unmarshal(r.Result, &st); err != nil { + return 0, "", "jsonrpc_err", latencyMs, err + } + if st.SyncInfo.LatestBlockHeight == "" { + return 0, "", "jsonrpc_err", latencyMs, fmt.Errorf("tendermint status missing latest_block_height") + } + n, err := strconv.ParseUint(st.SyncInfo.LatestBlockHeight, 10, 64) + if err != nil { + return 0, "", "jsonrpc_err", latencyMs, fmt.Errorf("non-numeric latest_block_height: %q", st.SyncInfo.LatestBlockHeight) + } + return n, st.SyncInfo.LatestBlockHash, "ok", latencyMs, nil +} diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index c36eec36..31c95cae 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -600,6 +600,30 @@ export const PROVIDER_REGISTRY: Record = { "Parity-operated Polkadot relay-chain public RPC endpoint (rpc.polkadot.io). Substrate JSON-RPC (chain_getHeader, state_getRuntimeVersion, system_health), keyless, rate-limited per IP. The reference endpoint any Polkadot integrator points at before wiring a dedicated node.", twitter: "@ParityTech", }, + "osmosis-official": { + url: "https://rpc.osmosis.zone", + description: + "Osmosis Foundation-operated public Cosmos SDK RPC endpoint (rpc.osmosis.zone). Tendermint / CometBFT JSON-RPC (status, block, abci_query), keyless, rate-limited per IP. The reference endpoint Keplr and the Osmosis Frontier route through by default.", + twitter: "@osmosiszone", + }, + polkachu: { + url: "https://polkachu.com", + description: + "Community-run Cosmos validator and public RPC network. Operates no-key Tendermint RPC endpoints across 40+ Cosmos SDK chains (osmosis-rpc.polkachu.com, cosmos-rpc.polkachu.com, etc.) with archive support on a paid tier.", + twitter: "@Polkachu_com", + }, + imperator: { + url: "https://imperator.co", + description: + "Cosmos-native infrastructure provider with a strong IBC and Osmosis ecosystem focus. Runs no-key Tendermint RPC endpoints (rpc-osmosis.imperator.co) alongside its analytics and data products for Cosmos appchains.", + twitter: "@imperatorco", + }, + lavenderfive: { + url: "https://lavenderfive.com", + description: + "Cosmos validator running multi-chain no-key RPC endpoints under rpc.lavenderfive.com/ and lcd.lavenderfive.com/. Covers Osmosis, Cosmos Hub, Neutron, Injective and 20+ other Cosmos SDK chains.", + twitter: "@LavenderFive", + }, dwellir: { url: "https://www.dwellir.com", description: diff --git a/src/lib/chains.ts b/src/lib/chains.ts index 2e1fed7b..23c250eb 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -317,6 +317,14 @@ export const CHAINS: ChainEntry[] = [ description: "Substrate-based relay chain coordinating a parachain ecosystem via shared security. GRANDPA finality with BABE block production, ~6 s block time.", }, + { + slug: "osmosis", + label: "Osmosis", + category: "L1", + nativeSymbol: "OSMO", + description: + "Cosmos SDK appchain and the largest IBC-connected DEX. CometBFT (Tendermint) consensus with deterministic finality per block, ~6 s block time. TokenFactory + IBC v3 native, x/superfluid staking on LP shares.", + }, ]; export const CHAIN_BY_SLUG = new Map(CHAINS.map((c) => [c.slug, c])); diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index d21e6e4a..c0e15c50 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -403,6 +403,7 @@ const ALIASES: Record = { "solana-official": "solana", "solana-labs": "solana", "polkadot-official": "parity", + "osmosis-official": "osmosis", hood: "robinhood", coin: "coinbase", "megaeth-official": "megaeth", diff --git a/src/lib/spec.ts b/src/lib/spec.ts index 1393b62e..c533be7e 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -297,7 +297,7 @@ 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. - ["bench-unfiltered-v36", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + ["bench-unfiltered-v37", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -456,7 +456,7 @@ 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). - ["all-benchmarks-v39", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + ["all-benchmarks-v40", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); export const loadAllBenchmarks = cache(loadAllBenchmarksCached);