Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions benchmarks/osmosis-rpc.yml

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions harnesses/rpc-capabilities/cmd/script/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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:
Expand Down
94 changes: 92 additions & 2 deletions harnesses/rpc-capabilities/cmd/script/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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"
Expand All @@ -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" {
Expand Down Expand Up @@ -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
}
24 changes: 24 additions & 0 deletions src/data/provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,30 @@ export const PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
"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/<chain> and lcd.lavenderfive.com/<chain>. Covers Osmosis, Cosmos Hub, Neutron, Injective and 20+ other Cosmos SDK chains.",
twitter: "@LavenderFive",
},
dwellir: {
url: "https://www.dwellir.com",
description:
Expand Down
8 changes: 8 additions & 0 deletions src/lib/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down
1 change: 1 addition & 0 deletions src/lib/logo-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ const ALIASES: Record<string, string> = {
"solana-official": "solana",
"solana-labs": "solana",
"polkadot-official": "parity",
"osmosis-official": "osmosis",
hood: "robinhood",
coin: "coinbase",
"megaeth-official": "megaeth",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"] },
);

Expand Down Expand Up @@ -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);
Expand Down
Loading