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
2 changes: 1 addition & 1 deletion benchmarks/l1-finality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ slug: l1-finality
number: "006"
title: Fastest L1 blockchain finality, live across 11 chains
seo_title: "Fastest L1 finality 2026"
seo_description: "Fastest L1 blockchain finality measured live across 11 chains: Gram, SUI, Hedera, Stellar, Solana, Ethereum, Bitcoin and more. Real times, not whitepaper claims."
seo_description: "{{best_name}} leads L1 finality at {{best_p50}} (p50, 24h) across 11 chains. Ethereum, Solana, Bitcoin, SUI, Gram: live wall-clock measurements, not whitepaper claims."
subtitle: Wall-clock seconds from latest block to the finalized block on Ethereum, Solana, Bitcoin, Gram, SUI, Stellar and 5 more chains, refreshed every 10 seconds.
seo_intro: |
This page measures L1 finality time live for every major Layer-1 blockchain, with p50 / p90 / p99 refreshed every 10 seconds. Stellar finality time is ~5 seconds, the close interval the Stellar Consensus Protocol locks in via federated Byzantine agreement. Solana finality time goes from sub-second on the processed commitment to ~12.8 s on finalized after 32 confirmed slots. Ethereum finality time is about 12.8 minutes in the ideal case, the 2-epoch Casper FFG window; observed wall-clock finality runs longer, and the table below shows the live measurement. Hedera finality time clears in 3-5 seconds via Hashgraph aBFT. SUI finality time and Gram finality time (formerly TON) both sit under one second via Mysticeti DAG-BFT and BAG consensus. BNB and Avalanche finality time land near two seconds through fast-finality forks. Probabilistic chains (Bitcoin, Litecoin, Monero) settle on a confirmation-depth convention measured here in minutes. Bitcoin finality time is roughly one hour at the industry-standard 6 confirmations; the protocol itself never reaches absolute finality.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,15 @@ describe("GET /api/bench/hyperliquid-frontends/history", () => {
source: "archive",
rows: [
{
slug: "0xphantom",
slug: "phantom",
name: "Phantom",
volume_usd: 9_000_000,
fees_usd: 90_000,
fills: 12_000,
rank: 1,
},
{
slug: "0xmetamask",
slug: "metamask",
volume_usd: 4_500_000,
rank: 2,
},
Expand Down
34 changes: 29 additions & 5 deletions src/app/benchmarks/[slug]/[chain]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ArrowLeft, ArrowUpRight } from "lucide-react";
import { getBenchmark } from "@/data/benchmarks";
import { Breadcrumb } from "@/components/breadcrumb";
import { citableAsOf } from "@/lib/citation";
import { liveResults } from "@/lib/provider-filters";
import { liveResults, MIN_DISPLAY_SUCCESS_PCT } from "@/lib/provider-filters";
import { fmtUnit } from "@/lib/format";
import { capDescription } from "@/lib/seo-text";
import { getBenchCreatedAt } from "@/lib/seo/bench-dates";
Expand Down Expand Up @@ -115,9 +115,11 @@ function resolveLeftoverPlaceholders(text: string, b: Benchmark): string {
}

function sortLive(results: ProviderResult[], higherIsBetter: boolean) {
return [...liveResults(results)].sort((a, b) =>
higherIsBetter ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50,
);
return [...liveResults(results)]
.filter((r) => (r.successRate ?? 100) >= MIN_DISPLAY_SUCCESS_PCT)
.sort((a, b) =>
higherIsBetter ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50,
);
}

async function loadChainPage(
Expand Down Expand Up @@ -254,7 +256,9 @@ function buildKeyFacts(data: ChainPageData): string {
if (data.shape === "row") {
const { result, sorted, rank } = data;
const subject = `${result.name} ${metricPhrase(b.metric)}`;
if (result.ms.p50 <= 0) {
const hasGoodData = result.ms.p50 > 0 &&
(result.successRate == null || result.successRate >= MIN_DISPLAY_SUCCESS_PCT);
if (!hasGoodData) {
return `${subject} is measured continuously on this benchmark. Live numbers will appear here as soon as the harness reports fresh samples.`;
}
const p50 = fmtUnit(result.ms.p50, b.unit);
Expand Down Expand Up @@ -303,23 +307,43 @@ export async function generateMetadata({
);
const canonical = `${SITE.url}/benchmarks/${data.benchmark.slug}/${chain}`;
const ogImage = `${SITE.url}/api/og/${data.benchmark.slug}`;
const b = data.benchmark;
const isDraft = b.status === "draft";
const isAwaiting = isDraft && b.editorialStatus === "live";
const isoPubDate = b.lastRunAt
? new Date(b.lastRunAt).toISOString().slice(0, 10)
: undefined;
return {
title,
description,
alternates: { canonical },
...(isAwaiting ? { robots: { index: false, follow: true } } : {}),
openGraph: {
title,
description,
type: "article",
url: canonical,
siteName: SITE.name,
images: [ogImage],
},
twitter: {
card: "summary_large_image",
site: SITE.twitter,
title,
description,
images: [ogImage],
},
other: {
citation_title: title,
citation_author: "OpenChainBench",
citation_publisher: "OpenChainBench",
...(isoPubDate ? { citation_publication_date: isoPubDate } : {}),
citation_doi: "10.5281/zenodo.20800312",
citation_pdf_url: `${SITE.url}/api/stat/${b.slug}`,
citation_public_url: canonical,
citation_language: "en",
citation_journal_title: "OpenChainBench",
},
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/components/ledger-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ProviderLogo } from "@/components/provider-logo";
import { ProviderTypeBadge } from "@/components/provider-type-badge";
import { fmtUnit } from "@/lib/format";
import { buildProviderColors } from "@/lib/series-colors";
import { MIN_DISPLAY_SUCCESS_PCT } from "@/lib/provider-filters";
import { isRegion } from "@/lib/brand";
import { isAll } from "@/lib/dimensions";

Expand Down Expand Up @@ -270,6 +271,12 @@ export function LedgerTable({
// undersized field. "low" rows stay; the row renderer shows them
// with a soft pill instead.
if (r.dataConfidence === "insufficient") return false;
// Success-rate gate. Prometheus gauges retain the last measured
// value when the harness fails, so a row with a stale non-zero p50
// but 0 % real success rate would otherwise rank as #1 on a
// lower-is-better bench. Excluded rows join unresponsiveRows for
// display; they are not silently hidden.
if ((r.successRate ?? 100) < MIN_DISPLAY_SUCCESS_PCT) return false;
if (activePanel) {
const v = activePanel.values[r.slug];
return v != null && Number.isFinite(v) && v !== 0;
Expand Down
4 changes: 3 additions & 1 deletion src/components/ranked-bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMemo, useRef, useState } from "react";
import type { Benchmark } from "@/types/benchmark";
import { fmtUnit } from "@/lib/format";
import { buildProviderColors } from "@/lib/series-colors";
import { MIN_DISPLAY_SUCCESS_PCT } from "@/lib/provider-filters";
import { useChartExclusion } from "@/hooks/use-chart-exclusion";
import { useTopN } from "@/hooks/use-top-n";
import { LiveDot } from "@/components/live-dot";
Expand Down Expand Up @@ -64,7 +65,8 @@ export function RankedBarChart({
// surface doesn't open with a long stack of empty bars that tie at
// the bottom (or, when lower-is-better, falsely lead the ranking).
const scored = benchmark.results.filter(
(r) => r.ms.p50 > 0 || r.ms.p90 > 0 || r.ms.p99 > 0
(r) => (r.ms.p50 > 0 || r.ms.p90 > 0 || r.ms.p99 > 0) &&
(r.successRate ?? 100) >= MIN_DISPLAY_SUCCESS_PCT
);
const sorted = scored.sort((a, b) =>
benchmark.higherIsBetter ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50
Expand Down
24 changes: 24 additions & 0 deletions src/lib/provider-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ export function liveResults(results: ProviderResult[]): ProviderResult[] {
(r) => r.availability !== "unavailable" && r.ms.p50 > 0,
);
}

/**
* Minimum success rate (0-100) for a row to appear in the ranked
* display surfaces (ledger table, bar chart, per-chain comparison).
* Prometheus gauges retain their last value when the harness fails,
* so a chain that once measured <1 s but has been dead for 24 h
* still holds a non-zero p50 while its success rate collapses to 0 %.
* The all-zero filter in liveResults does not catch this case; this
* floor does. Kept deliberately low (5 %) so occasionally-flaky rows
* are still shown, while completely dead ones (0 %, 0.28 %) are
* excluded from the ranking. citation.ts uses a separate 50 % floor
* for the citable leader claim; display and citation thresholds are
* intentionally decoupled.
*/
export const MIN_DISPLAY_SUCCESS_PCT = 5;

/** liveResults filtered by the display success floor. Use for ranked
* surfaces (ledger, bar chart, per-chain pages). Do NOT use in the
* citation path — citation.ts applies its own 50 % floor separately. */
export function displayResults(results: ProviderResult[]): ProviderResult[] {
return liveResults(results).filter(
(r) => (r.successRate ?? 100) >= MIN_DISPLAY_SUCCESS_PCT,
);
}
Loading