From 881586ab1bb8818d389fc780da60c6e73e10c546 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 7 Nov 2026 11:07:02 +0100 Subject: [PATCH] BlockVectra joins the Ethereum and Arbitrum RPC pages (#3573) Its keyless public routes for Ethereum and Arbitrum One are probed by the rpc-capabilities harness like the other public providers on those pages, on the same 60 s tick from the same three regions, with no special treatment. Checked by hand before wiring (2026-11-07, about 1 req/s from Europe): chain id 1 and 42161, eth_getBlockByNumber("latest", false) answers, the head 0 to 1 block behind PublicNode, 132 to 203 ms. One call in ten on the Ethereum route came back -32005 "public chain pool is busy"; in production that counts against its success rate like any refusal. Base is NOT added: its route answers -32010 "node is syncing" to every call. It goes on hold until BlockVectra says the node has caught up. An arrival on two benches, so the four cache keys move (bench-unfiltered-v189, all-benchmarks-v182, bench-filters-v107, providers-v62) and the provider-set guard is re-pinned. --- benchmarks/arbitrum-rpc.yml | 24 +++++++++++++++++++ benchmarks/ethereum-rpc.yml | 24 +++++++++++++++++++ .../rpc-capabilities/cmd/script/config.go | 2 ++ src/lib/providers.ts | 4 +++- src/lib/spec-provider-set.test.ts | 6 ++++- src/lib/spec.ts | 14 ++++++++--- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/benchmarks/arbitrum-rpc.yml b/benchmarks/arbitrum-rpc.yml index c74501ab4..0f88fadec 100644 --- a/benchmarks/arbitrum-rpc.yml +++ b/benchmarks/arbitrum-rpc.yml @@ -393,6 +393,30 @@ providers: p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="arbitrum-official", chain="arbitrum", region="sgp"}) series: avg_over_time(rpc_latency_milliseconds{provider="arbitrum-official", chain="arbitrum", region="sgp"}[1h]) + - slug: blockvectra + name: BlockVectra + endpoint: https://api.blockvectra.com/v1/arb_mainnet/public + tag: BlockVectra keyless public Arbitrum One endpoint (3 req/s per IP, 1,000-block history window) + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber(\"latest\", false)` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to BlockVectra's no-key Arbitrum One endpoint." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="arbitrum"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="blockvectra", chain="arbitrum"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="blockvectra", chain="arbitrum"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="blockvectra", chain="arbitrum"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="blockvectra", chain="arbitrum"}) / sum(ocb:rpc_call:rate_24h{provider="blockvectra", chain="arbitrum"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="blockvectra", chain="arbitrum"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="arbitrum"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="arbitrum", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="arbitrum", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="arbitrum", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="arbitrum", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="arbitrum", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="arbitrum", region="sgp"}[1h]) + # API-key cohort (tier: keyed). Queries pin tier="keyed", the label the # rpc-keyed-latency harness emits; the public rows above carry no tier label. - slug: alchemy diff --git a/benchmarks/ethereum-rpc.yml b/benchmarks/ethereum-rpc.yml index 5fcd70a9c..9f5aad0ca 100644 --- a/benchmarks/ethereum-rpc.yml +++ b/benchmarks/ethereum-rpc.yml @@ -393,6 +393,30 @@ providers: p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="meowrpc", chain="ethereum", region="sgp"}) series: avg_over_time(rpc_latency_milliseconds{provider="meowrpc", chain="ethereum", region="sgp"}[1h]) + - slug: blockvectra + name: BlockVectra + endpoint: https://api.blockvectra.com/v1/eth_mainnet/public + tag: BlockVectra keyless public Ethereum endpoint (3 req/s per IP, 1,000-block history window) + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber(\"latest\", false)` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to BlockVectra's no-key Ethereum endpoint." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="ethereum"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="blockvectra", chain="ethereum"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="blockvectra", chain="ethereum"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="blockvectra", chain="ethereum"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="blockvectra", chain="ethereum"}) / sum(ocb:rpc_call:rate_24h{provider="blockvectra", chain="ethereum"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="blockvectra", chain="ethereum"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="ethereum"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="ethereum", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="ethereum", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="ethereum", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="ethereum", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="blockvectra", chain="ethereum", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="blockvectra", chain="ethereum", region="sgp"}[1h]) + # API-key cohort (tier: keyed). Queries pin tier="keyed", the label the # rpc-keyed-latency harness emits; the public rows above carry no tier label. - slug: alchemy diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go index e51ef2143..e665bd5dc 100644 --- a/harnesses/rpc-capabilities/cmd/script/config.go +++ b/harnesses/rpc-capabilities/cmd/script/config.go @@ -300,6 +300,7 @@ func chains() []Chain { {Slug: "nodies", Name: "Nodies (POKT)", URL: envDefault("RPC_URL_ETHEREUM_NODIES", "https://eth-pokt.nodies.app")}, {Slug: "bloxroute", Name: "bloXroute", URL: envDefault("RPC_URL_ETHEREUM_BLOXROUTE", "https://eth.rpc.blxrbdn.com")}, {Slug: "nownodes", Name: "NOWNodes", URL: envDefault("RPC_URL_ETHEREUM_NOWNODES", "https://public-eth.nownodes.io")}, + {Slug: "blockvectra", Name: "BlockVectra", URL: envDefault("RPC_URL_ETHEREUM_BLOCKVECTRA", "https://api.blockvectra.com/v1/eth_mainnet/public")}, }, }, // ─── Polygon PoS (3 providers) ────────────────────────────── @@ -331,6 +332,7 @@ func chains() []Chain { {Slug: "tenderly", Name: "Tenderly Gateway", URL: envDefault("RPC_URL_ARBITRUM_TENDERLY", "https://gateway.tenderly.co/public/arbitrum")}, {Slug: "nodies", Name: "Nodies (POKT)", URL: envDefault("RPC_URL_ARBITRUM_NODIES", "https://arb-pokt.nodies.app")}, {Slug: "arbitrum-official", Name: "Arbitrum Official", URL: envDefault("RPC_URL_ARBITRUM_OFFICIAL", "https://arb1.arbitrum.io/rpc")}, + {Slug: "blockvectra", Name: "BlockVectra", URL: envDefault("RPC_URL_ARBITRUM_BLOCKVECTRA", "https://api.blockvectra.com/v1/arb_mainnet/public")}, }, }, // ─── Optimism (6 providers) ───────────────────────────────── diff --git a/src/lib/providers.ts b/src/lib/providers.ts index 35eb0a2f1..3d524f8d7 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -1031,7 +1031,9 @@ const buildProvidersCached = unstable_cache( // bench-unfiltered-v184. // v61: thirteen chain RPC pages retire, so every provider on them loses // that appearance. Lockstep with bench-unfiltered-v187. - ["providers-v61"], + // v62: BlockVectra gains two appearances, ethereum-rpc and arbitrum-rpc. + // Lockstep with bench-unfiltered-v189. + ["providers-v62"], // 900 s: the provider index feeds products / compare / answers, whose // numbers move slowly, and this revalidate is also the effective ISR // period of those ~500 pages (Next takes the min across a route's caches). diff --git a/src/lib/spec-provider-set.test.ts b/src/lib/spec-provider-set.test.ts index 0a0d6d582..cf981315e 100644 --- a/src/lib/spec-provider-set.test.ts +++ b/src/lib/spec-provider-set.test.ts @@ -53,6 +53,10 @@ describe("the provider set behind the cache keys", () => { const hash = createHash("sha256").update(sets.join("\n")).digest("hex").slice(0, 16); const count = sets.reduce((n, s) => n + s.split(":")[1].split(",").length, 0); + // An ADDITION on 2026-11-07: BlockVectra joins the public cohorts on + // ethereum-rpc and arbitrum-rpc (issue #3573). A provider already on + // the site, two more appearances: v189/v182/v107 and providers-v62. + // // A REMOVAL on 2026-11-06: thirteen chain RPC pages retire (celo, // taiko, kaia, bob, bitlayer, gravity, axelar, hedera, litecoin, // soneium, zircuit, merlin, comdex), every row on them with them. No @@ -181,6 +185,6 @@ describe("the provider set behind the cache keys", () => { "Then set EXPECTED to the hash in this failure, and add the provider", `to PRODUCT_NAMES if it needs a display name. Providers now: ${count}.`, ].join("\n"), - ).toBe("35fc07b8e50a5ad2"); + ).toBe("e0e5b0e42ccb7ee7"); }); }); diff --git a/src/lib/spec.ts b/src/lib/spec.ts index 0de431087..b9deebfc6 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -981,7 +981,11 @@ const loadBenchmarkUnfilteredCached = unstable_cache( // each, and route into the cohort at 60% and 30%. Trnch has the // lowest baseline, 78.1%, and the second highest trap rate at 90%, // the only row whose score sits above its own coverage. - ["bench-unfiltered-v188", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + // v189: BlockVectra joins the public cohorts on ethereum-rpc and + // arbitrum-rpc (issue #3573), one more public row on each. Cached + // entries hold the old provider set. Moves with all-benchmarks-v182, + // bench-filters-v107 and providers-v62. + ["bench-unfiltered-v189", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -1345,7 +1349,9 @@ const loadAllBenchmarksCached = unstable_cache( // v180: lockstep with bench-unfiltered-v187 (thirteen chain RPC pages // retired 2026-11-06; a cached catalog would keep linking them). // v181: lockstep with bench-unfiltered-v188 (bench 313 coverage column). - ["all-benchmarks-v181", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + // v182: lockstep with bench-unfiltered-v189 (BlockVectra on Ethereum and + // Arbitrum). + ["all-benchmarks-v182", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); export const loadAllBenchmarks = cache(loadAllBenchmarksCached); @@ -1601,7 +1607,9 @@ const loadBenchmarkFiltered = unstable_cache( // retired, each with region and tier tabs). // v106: lockstep with bench-unfiltered-v188. The coverage panel is // written under both row slugs, so each tier tab carries it. - ["bench-filters-v106", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + // v107: lockstep with bench-unfiltered-v189. The new public rows carry + // region tabs. + ["bench-filters-v107", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] } );