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
27 changes: 2 additions & 25 deletions benchmarks/base-rpc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ methodology:
- "Call-result classification: `ok` (HTTP 200 + non-empty result), `http_err`, `jsonrpc_err` (HTTP 200 carrying an error body), `stale` (more than 20 blocks behind the cross-provider tip), `timeout`. Latency without reliability is a misleading ranking signal."
- "Archive depth: every 5 minutes we issue `eth_getBalance` at (head − depth) for depths from Geth's default pruned cap up to 5M blocks, exposing which free endpoints actually serve historical state."
- "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, methodology and exclusion rules apply on every chain."
- "Chain scope: every query on this page is pinned to chain=\"base\". Provider coverage: 6 no-key endpoints (PublicNode, dRPC, Tenderly, Nodies, Merkle, Base). Merkle blocks datacenter IPs since 2026-07, so it is probed through a residential proxy; its latency includes proxy overhead and is flagged as such. Exclusions follow the cluster-wide rules documented on the parent benchmark."
- "Chain scope: every query on this page is pinned to chain=\"base\". Provider coverage: 5 no-key endpoints (PublicNode, dRPC, Tenderly, Nodies, Base). Merkle was delisted 2026-07: its gateway does not serve `eth_getBlockByNumber`, the anti-cache probe this cluster standardized on, so it cannot be measured on equal terms. Exclusions follow the cluster-wide rules documented on the parent benchmark."

findings:
- "{{best_name}} currently leads free Base RPC at {{best_p50}} (`eth_getBlockByNumber` p50, 24h) across 6 measured providers."
Expand All @@ -36,7 +36,7 @@ faq:
- q: "What is the fastest free Base RPC right now?"
a: "{{best_name}} currently leads at {{best_p50}} (`eth_getBlockByNumber` p50 over the last 24h), measured against 6 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 Base RPCs work without an API key?"
a: "The 6 providers on this page: PublicNode, dRPC, Tenderly, Nodies, Merkle, Base. Merkle blocks datacenter IPs since July 2026, so its probes route through a residential proxy; the latency column includes that proxy overhead and the row is flagged accordingly. Every (provider, chain) pair was live-verified no-key before inclusion, and anything that key-gates, region-blocks or rate-limits below our 15-second cadence is excluded rather than listed with an asterisk."
a: "The 5 providers on this page: PublicNode, dRPC, Tenderly, Nodies, Base. Merkle was delisted in July 2026 because its gateway does not serve `eth_getBlockByNumber` (JSON-RPC -32601), the anti-cache probe used across the cluster, so its latency cannot be compared on equal terms. Every (provider, chain) pair was live-verified no-key before inclusion, and anything that key-gates, region-blocks or rate-limits below our 15-second cadence is excluded rather than listed with an asterisk."
- q: "Does the fastest Base RPC change by region?"
a: "Frequently. The headline number averages three probe origins (us-east, eu-west, Singapore), but per-region leaders regularly diverge, a gateway that wins from Virginia can lose from Singapore by multiples. 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 Base RPC latency measured here?"
Expand Down Expand Up @@ -155,29 +155,6 @@ providers:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodies", chain="base", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="nodies", chain="base", region="sgp"}[1h])

- slug: merkle
name: Merkle
tag: Blocks datacenter IPs since 2026-07; probed via residential proxy, latency includes proxy overhead
formula: "50th percentile over 24h of round-trip latency (ms) for `eth_getBlockByNumber` every 60s from 3 regions via a residential proxy (Merkle blocks datacenter IPs since 2026-07); includes proxy overhead, not directly comparable."
queries:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="base"})
p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="merkle", chain="base"})
p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="merkle", chain="base"})
mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="merkle", chain="base"})
success: sum(ocb:rpc_call:ok_rate_24h{provider="merkle", chain="base"}) / sum(ocb:rpc_call:rate_24h{provider="merkle", chain="base"})
sample_size: sum(ocb:rpc_call:increase_24h{provider="merkle", chain="base"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="base"}[1h]))
regions:
- region: us-east
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="base", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="base", region="us-east"}[1h])
- region: eu-west
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="base", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="base", region="eu-west"}[1h])
- region: ap-southeast
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="base", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="base", region="sgp"}[1h])

- slug: base-official
name: Base
tag: Coinbase-operated, Base mainnet RPC
Expand Down
27 changes: 2 additions & 25 deletions benchmarks/bnb-rpc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ methodology:
- "Call-result classification: `ok` (HTTP 200 + non-empty result), `http_err`, `jsonrpc_err` (HTTP 200 carrying an error body), `stale` (more than 20 blocks behind the cross-provider tip), `timeout`. Latency without reliability is a misleading ranking signal."
- "Archive depth: every 5 minutes we issue `eth_getBalance` at (head − depth) for depths from Geth's default pruned cap up to 5M blocks, exposing which free endpoints actually serve historical state."
- "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, methodology and exclusion rules apply on every chain."
- "Chain scope: every query on this page is pinned to chain=\"bnb\". Provider coverage: 5 no-key endpoints (PublicNode, dRPC, Nodies, Merkle, Binance). Merkle blocks datacenter IPs since 2026-07, so it is probed through a residential proxy; its latency includes proxy overhead and is flagged as such. Exclusions follow the cluster-wide rules documented on the parent benchmark."
- "Chain scope: every query on this page is pinned to chain=\"bnb\". Provider coverage: 4 no-key endpoints (PublicNode, dRPC, Nodies, Binance). Merkle was delisted 2026-07: its gateway does not serve `eth_getBlockByNumber`, the anti-cache probe this cluster standardized on, so it cannot be measured on equal terms. Exclusions follow the cluster-wide rules documented on the parent benchmark."

findings:
- "{{best_name}} currently leads free BNB Chain RPC at {{best_p50}} (`eth_getBlockByNumber` p50, 24h) across 5 measured providers."
Expand All @@ -36,7 +36,7 @@ faq:
- q: "What is the fastest free BNB Chain RPC right now?"
a: "{{best_name}} currently leads at {{best_p50}} (`eth_getBlockByNumber` 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 BNB Chain RPCs work without an API key?"
a: "The 5 providers on this page: PublicNode, dRPC, Nodies, Merkle, Binance. Merkle blocks datacenter IPs since July 2026, so its probes route through a residential proxy; the latency column includes that proxy overhead and the row is flagged accordingly. Every (provider, chain) pair was live-verified no-key before inclusion, and anything that key-gates, region-blocks or rate-limits below our 15-second cadence is excluded rather than listed with an asterisk."
a: "The 4 providers on this page: PublicNode, dRPC, Nodies, Binance. Merkle was delisted in July 2026 because its gateway does not serve `eth_getBlockByNumber` (JSON-RPC -32601), the anti-cache probe used across the cluster, so its latency cannot be compared on equal terms. Every (provider, chain) pair was live-verified no-key before inclusion, and anything that key-gates, region-blocks or rate-limits below our 15-second cadence is excluded rather than listed with an asterisk."
- q: "Does the fastest BNB Chain RPC change by region?"
a: "Frequently. The headline number averages three probe origins (us-east, eu-west, Singapore), but per-region leaders regularly diverge, a gateway that wins from Virginia can lose from Singapore by multiples. 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 BNB Chain RPC latency measured here?"
Expand Down Expand Up @@ -132,29 +132,6 @@ providers:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodies", chain="bnb", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="nodies", chain="bnb", region="sgp"}[1h])

- slug: merkle
name: Merkle
tag: Blocks datacenter IPs since 2026-07; probed via residential proxy, latency includes proxy overhead
formula: "50th percentile over 24h of round-trip latency (ms) for `eth_getBlockByNumber` every 60s from 3 regions via a residential proxy (Merkle blocks datacenter IPs since 2026-07); includes proxy overhead, not directly comparable."
queries:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="bnb"})
p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="merkle", chain="bnb"})
p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="merkle", chain="bnb"})
mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="merkle", chain="bnb"})
success: sum(ocb:rpc_call:ok_rate_24h{provider="merkle", chain="bnb"}) / sum(ocb:rpc_call:rate_24h{provider="merkle", chain="bnb"})
sample_size: sum(ocb:rpc_call:increase_24h{provider="merkle", chain="bnb"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="bnb"}[1h]))
regions:
- region: us-east
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="bnb", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="bnb", region="us-east"}[1h])
- region: eu-west
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="bnb", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="bnb", region="eu-west"}[1h])
- region: ap-southeast
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", chain="bnb", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", chain="bnb", region="sgp"}[1h])

- slug: binance
name: Binance
tag: BNB Chain dataseed RPC, Binance-operated
Expand Down
7 changes: 0 additions & 7 deletions harnesses/rpc-capabilities/cmd/script/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ type Provider struct {
Slug string
Name string
URL string
// ViaProxy routes probes through RPC_PROBE_PROXY_URL (residential
// rotating proxy). For providers that block datacenter IPs (merkle
// since 2026-07). Latency then includes proxy overhead; the spec
// discloses it on the row.
ViaProxy bool
}

type Chain struct {
Expand Down Expand Up @@ -148,7 +143,6 @@ func chains() []Chain {
{Slug: "drpc", Name: "dRPC", URL: envDefault("RPC_URL_BASE_DRPC", "https://base.drpc.org")},
{Slug: "tenderly", Name: "Tenderly Gateway", URL: envDefault("RPC_URL_BASE_TENDERLY", "https://gateway.tenderly.co/public/base")},
{Slug: "nodies", Name: "Nodies (POKT)", URL: envDefault("RPC_URL_BASE_NODIES", "https://base-pokt.nodies.app")},
{Slug: "merkle", Name: "Merkle", URL: envDefault("RPC_URL_BASE_MERKLE", "https://base.merkle.io"), ViaProxy: true},
{Slug: "base-official", Name: "Base Official", URL: envDefault("RPC_URL_BASE_OFFICIAL", "https://mainnet.base.org")},
},
},
Expand All @@ -160,7 +154,6 @@ func chains() []Chain {
{Slug: "publicnode", Name: "PublicNode", URL: envDefault("RPC_URL_BNB_PUBLICNODE", "https://bsc-rpc.publicnode.com")},
{Slug: "drpc", Name: "dRPC", URL: envDefault("RPC_URL_BNB_DRPC", "https://bsc.drpc.org")},
{Slug: "nodies", Name: "Nodies (POKT)", URL: envDefault("RPC_URL_BNB_NODIES", "https://bsc-pokt.nodies.app")},
{Slug: "merkle", Name: "Merkle", URL: envDefault("RPC_URL_BNB_MERKLE", "https://bsc.merkle.io"), ViaProxy: true},
{Slug: "binance", Name: "Binance Official", URL: envDefault("RPC_URL_BNB_OFFICIAL", "https://bsc-dataseed1.binance.org")},
},
},
Expand Down
43 changes: 2 additions & 41 deletions harnesses/rpc-capabilities/cmd/script/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,13 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)

// probeProxyClient returns a client routing through RPC_PROBE_PROXY_URL
// (residential rotating proxy) for providers that block datacenter IPs.
// Falls back to a direct client if the env is unset, which surfaces as a
// visible 100% error rate rather than a silent measurement change.
var proxyClientOnce sync.Once
var proxyClientCached *http.Client

func probeProxyClient() *http.Client {
proxyClientOnce.Do(func() {
raw := strings.TrimSpace(os.Getenv("RPC_PROBE_PROXY_URL"))
if raw == "" {
log.Printf("[PROBE] WARNING: ViaProxy provider configured but RPC_PROBE_PROXY_URL is unset; probing direct")
proxyClientCached = &http.Client{Timeout: probeTimeout}
return
}
u, err := url.Parse(raw)
if err != nil {
log.Printf("[PROBE] WARNING: invalid RPC_PROBE_PROXY_URL: %v; probing direct", err)
proxyClientCached = &http.Client{Timeout: probeTimeout}
return
}
proxyClientCached = &http.Client{
Timeout: probeTimeout,
Transport: &http.Transport{
Proxy: http.ProxyURL(u),
DisableKeepAlives: true, // fresh proxy IP per probe
MaxIdleConnsPerHost: 0,
},
}
})
return proxyClientCached
}

const (
// 60s (was 30s): the cluster probes ~23 chains per provider from ONE
// IP per region. Keyless per-IP budgets (1RPC daily cap, dRPC CU/s)
Expand Down Expand Up @@ -128,7 +92,7 @@ type blockHeader struct {
// on cache hits rather than real RPC work. Fetching the full latest
// header with a rotating request id defeats body-keyed edge caches;
// the header's `number` field keeps the staleness check intact.
func callLatestBlock(ctx context.Context, url string, viaProxy bool) (block uint64, result string, latencyMs float64, err error) {
func callLatestBlock(ctx context.Context, url string) (block uint64, result string, latencyMs float64, err error) {
body := []byte(fmt.Sprintf(
`{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":%d}`,
time.Now().UnixNano(),
Expand All @@ -137,9 +101,6 @@ func callLatestBlock(ctx context.Context, url string, viaProxy bool) (block uint
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)")
client := &http.Client{Timeout: probeTimeout}
if viaProxy {
client = probeProxyClient()
}

start := time.Now()
resp, err := client.Do(req)
Expand Down Expand Up @@ -214,7 +175,7 @@ func probeOne(ctx context.Context, c Chain, p Provider) {
tick := func() {
probeCtx, cancel := context.WithTimeout(ctx, probeTimeout)
defer cancel()
block, result, latency, err := callLatestBlock(probeCtx, p.URL, p.ViaProxy)
block, result, latency, err := callLatestBlock(probeCtx, p.URL)

if result == "ok" {
tips.update(c.Slug, block)
Expand Down
Loading