diff --git a/docs/methodology/solana-tx-landing-active.md b/docs/methodology/solana-tx-landing-active.md index 5ca87010..d367b35c 100644 --- a/docs/methodology/solana-tx-landing-active.md +++ b/docs/methodology/solana-tx-landing-active.md @@ -56,7 +56,7 @@ We do **not** vary tip amount across cycles. A "tip elasticity" experiment is a 4. Capture `submit_slot = getSlot(commitment="processed")` and `submit_wallclock = time.Now()`. 5. POST the base64-encoded signed transaction to the service's documented submission endpoint (exact URLs published in the harness source) with `skipPreflight = true`, `maxRetries = 0`, `encoding = "base64"`. Per-service auth headers / query params are applied as documented. 6. Capture the returned signature (or fail-fast on RPC error). -7. Poll `getSignatureStatuses` on the public mainnet RPC every 1 second. +7. Poll `getSignatureStatuses` on the public mainnet RPC every 200 ms. The poll cadence is the measurement floor for `latency_ms`. We chose 200 ms because Solana's slot duration is ~400 ms and "confirmed" lands ~1-2 slots after inclusion (~400 ms-1 s realistic), so a 1 s poll quantizes everything into 1 s steps and erases differentiation between services. 200 ms gives 5x the resolution while staying within public RPC budget. 8. On status reaching `confirmationStatus = "confirmed"` or `"finalized"`, record `land_slot` from the response context and `land_wallclock = time.Now()`. Classify as **landed**. 9. If 60 seconds elapse without a non-null `confirmationStatus`, abandon the poll. Classify as **dropped** with reason `timeout`. 10. If the original submission returned a transport error (HTTP timeout, DNS, EOF, connection refused), classify as **dropped** with reason `network_error`. If the upstream returned HTTP 419 / 429 or a JSON-RPC error containing "rate limit" / "too many requests", classify as `rate_limited`. If the submission was rejected with a structured RPC error (`InstructionError`, `BlockhashNotFound`, etc.) or the on-chain status comes back with an `Err`, classify as `invalid`. @@ -156,3 +156,4 @@ Sponsorship policy and the disclosure block live in [`solana-landing-tiered-arch |---|---|---| | v1.0 | 2026-05-21 | Initial pre-registration. V0-Lean scope : 5 services × 1 region × 1 / h. | | v1.1 | 2026-05-21 | Pre-launch reconciliation with implementation : metric names from `ocb_solana_landing_*` to `solana_landing_probe_*` (matches sibling OCB benches), `rate_limited` added as 4th drop reason, latency_ms histogram bucket list adjusted to include 250 ms and 30 s, blockhash commitment rationale clarified, memo format clarified (`ocb---` where cycle_id is itself the 8-byte random hex shared across all per-service probes in the cycle), `solana_landing_probe_enabled` gauge added so dashboards distinguish "prober disabled" from "prober stuck". | +| v1.2 | 2026-05-23 | `getSignatureStatuses` poll interval reduced from 1 s to 200 ms after the first 7 days of live data showed all services collapsing to identical 1.0 s p50. The 1 s poll was the measurement floor (Solana confirmed status arrives in ~400 ms-1 s), so 200 ms restores 5x the resolution. Bench page queries also switched from `histogram_quantile` on the latency_ms histogram to `quantile_over_time` on the latency_ms gauge, since the histogram bucket list only had ~3 buckets in the 1-5 s zone where probes actually land, collapsing p50 to bucket midpoints. | diff --git a/src/components/distribution-chart.tsx b/src/components/distribution-chart.tsx index 0c501a2a..2542d9b6 100644 --- a/src/components/distribution-chart.tsx +++ b/src/components/distribution-chart.tsx @@ -101,25 +101,23 @@ export function DistributionChart({ } }} title={methodologyTooltip(r, isOff)} - className={`py-3 cursor-pointer rounded-sm transition-colors hover:bg-paper-soft/40 ${ + className={`grid grid-cols-[minmax(6rem,9rem)_1fr_auto] sm:grid-cols-[minmax(8rem,12rem)_1fr_auto] items-center gap-3 sm:gap-4 py-2.5 cursor-pointer rounded-sm transition-colors hover:bg-paper-soft/40 ${ isOff ? "opacity-40" : "" }`} > -
+ {/* Identity column — logo + name on one line, no white gap above the track */} + {r.name} - - {fmtUnit(r.ms.p50, unit)} - -
- {/* Track */} + + {/* Track column — inline with the name so the row reads in a single line */}
{!isOff && ( <> @@ -138,6 +136,9 @@ export function DistributionChart({ )}
+ + {fmtUnit(r.ms.p50, unit)} + ); })} diff --git a/src/lib/views.ts b/src/lib/views.ts index 7fa316d4..f28c4514 100644 --- a/src/lib/views.ts +++ b/src/lib/views.ts @@ -22,16 +22,15 @@ export type ViewType = const ALLOWED_BY_UNIT: Record = { // distribution surfaces tail behaviour (p90/p99 vs p50) which is the // signal users actually care about on latency / cost / yield benches. - ms: ["timeseries", "rankedBar", "distribution"], - s: ["timeseries", "rankedBar", "distribution"], - bps: ["timeseries", "rankedBar", "distribution"], - pct: ["timeseries", "rankedBar", "distribution"], - // donut only makes sense when each provider's value is a share of a - // meaningful total. count benches with market-share semantics - // (solana-tx-landing) get the most out of it; other count benches - // also render but the slices represent the relative size of each - // provider's footprint, which is still a useful read. - count: ["countLeaderboard", "rankedBar", "donut", "timeseries"], + // donut is offered everywhere too: on latency / pct / bps it reads as + // "share of the field's total p50", a perfectly valid (if unusual) + // comparison the reader can ignore if they don't want it. Putting the + // option behind the switcher gives the choice instead of guessing. + ms: ["timeseries", "rankedBar", "distribution", "donut"], + s: ["timeseries", "rankedBar", "distribution", "donut"], + bps: ["timeseries", "rankedBar", "distribution", "donut"], + pct: ["timeseries", "rankedBar", "distribution", "donut"], + count: ["countLeaderboard", "rankedBar", "donut", "distribution", "timeseries"], }; /**