Documentation / Scoring / Network subscore

Documentation · Scoring

Network subscore

The 20% network dimension splits into five independently scored subtests, each carrying its own 100% baseline and chainweb-minimum threshold. This page is the operator reference for what each subtest measures, how the contributions sum, where the upload bench actually uploads to, and how the multi-hub future will reshape the per-node upload target.

Five subtests, 20% total

Each subtest contributes its weight clamped against the 100% baseline. Below the chainweb-minimum threshold a node still earns a fraction of the slice (the math is linear past zero) — the chainweb-min column is the floor the slice was calibrated against, not a hard cutoff. Above the 100% baseline the contribution is capped at the slice weight.

SubtestWeight100% baselineChainweb-min baselineNotes
download8%50 Mbps10 MbpsTop-5 mean across 8 regions; single collapsed row (v.H.1.1j/k/l).
latency0%Retired in v.H.1.0o — operator dashboards read ping from the per-region table.
upload0%Retired in v.H.1.1l after three iterations failed to produce a reliable Mbps reading; 2 parts redistributed to jitter + loss.
jitter2%5 ms50 msAgainst closest reachable region. v.H.1.1l doubled from 1% as part of the upload redistribution.
loss2%0%2%Against closest reachable region. v.H.1.1l doubled from 1% as part of the upload redistribution.
Total20%Sum of the five slice ceilings.

Renormalisation is intentionally absent: a missing subtest credits zero rather than redistributing its slice across the present ones.

v.H.1.1f — top-5-of-8 scoring. The 16% download slice is partitioned across the top 5 reachable regions by Mbps, not all 8. Geographic physics caps far-region throughput on a 250 ms RTT bandwidth-delay product, so a node in Europe will always score low on Asia/Pacific endpoints and vice versa. Chainweb’s peer-selection prefers low-latency peers anyway, so scoring on the 5 best regions matches the network usage the node actually performs. Per-region slice = 16% / 5 = 3.2% each, summed for the top 5 reachable. When fewer than 5 regions are reachable, the sum scales down proportionally.

v.H.1.1j — display collapsed to a single row. The bench step record table that surfaces in the per-node detail panel now shows ONE net.download row carrying the full 16% (16 parts) weight, with raw = top-N mean Mbps (N = min(5, reachable)) and a per-region diagnostic tail listing each region’s measured Mbps or skip. v.H.1.1l adds an expand-collapse caret ( / ) on this row so operators can drill into the 8 region rows and see which 5 fed the score (highlighted gold), which 3 sat in the bottom (gray), and which were unreachable (red). The 8 regions still run; the per-region detail lives both in the dropdown AND the “Compact per-region rows” librespeed table below.

v.H.1.1l — upload retired; jitter + loss doubled. The 100 MB upload subtest (previously 2 parts of NET) was retired after three iterations (v.H.1.1c URL fix, v.H.1.1j parser ENOMARKER fallback, v.H.1.1k per-curl-exit errno mapping) failed to produce a reliable Mbps reading on the operator fleet. The 2 parts redistributed to jitter (1 → 2) and loss (1 → 2), both lower-is-better stability metrics that better match the chainweb pain points (peer-gossip RTT variance + fork-resolution re-transmits). Net allocation: 16 + 2 + 2 = 20 parts of NET. The bash bench script no longer emits the NET_UP markers; legacy stamped breakdowns that contain a net.upload row render that row at 0 parts.

Eight Linode speedtest regions

Download and latency are sampled across eight Linode public speedtest endpoints chosen to span the major continents. The list is hardcoded in the bench handler today; expanding the list is a future-spec discussion (more regions = finer geographic resolution, but more wall-time per bench and more cross-Atlantic noise).

Region codeCityGeographic area
frankfurtFrankfurtEU — Germany
londonLondonEU — United Kingdom
newarkNewarkNA — US East
dallasDallasNA — US Central
fremontFremontNA — US West
singaporeSingaporeSEA
tokyoTokyo (tokyo2)East Asia — Japan
sydneySydney (sydney1)Oceania — Australia

Each region is fetched from speedtest.<region>.linode.com/100MB-<region>.bin (Tokyo and Sydney use the tokyo2 / sydney1suffixes per Linode’s current mirror naming). Failures on one region do not block the others; an unreachable region simply contributes zero to the per-region slice.

Upload-sink endpoint

The upload subtest needs a server willing to accept a large POST and discard it. Linode’s public speedtest mirrors only serve downloads, so the AH hub hosts its own purpose-built sink. The bench script generates 100 MB of random data with dd and POSTs it to the sink, parsing the resulting Mbps from curl’s --write-out '%{size_upload} %{time_total}' telemetry.

  • Endpoint URL: https://upload.ancientholdings.eu/sink (default). Overridable at runtime via the system_state.hub_upload_sink_url flag — used during hub-topology migrations to point bench traffic at a staging hub.
  • Method: POST. Body content is arbitrary (typically 100 MB of random bytes from /dev/urandom); the sink reads the stream and discards it.
  • Response: 200 OK with the body discarded — no echo, no checksum, just an acknowledgement that the bytes arrived.
  • Rate limit: per-IP rate-limited at the route handler — abusive clients get a 429; a well-behaved bench (one POST per Force-fresh wave) stays well under the limit.
  • Logging: request size and wall-time duration are logged for hub-side observability; no client identifiers are retained beyond the standard nginx access log retention window.

Bench-side, the relevant snippet:

dd if=/dev/urandom of=/tmp/upload.bin bs=1M count=100
curl -X POST \
  --data-binary @/tmp/upload.bin \
  --max-time 30 \
  --write-out '%{size_upload} %{time_total}' \
  "$(hub_upload_sink_url)"

Mbps is derived from size_upload (bytes) and time_total (seconds); a 30-second hard timeout means even a completely-stalled upload bounds the bench wall-time.

Multi-hub forward-compat

Today every node uploads to the same shared hub-side sink. That is fine for a single-hub topology — every operator measures the same path the hub will accept their traffic on in production. When multi-hub topology lands in a follow-up spec (not Hipparchus), each node will instead upload to its assigned hub’s sink, so the bench measures the path the node will actually use rather than an idealised free-pick.

The seam already exists in code: lib/hub-config.ts:getHubUploadSinkUrl() carries a MULTI-HUB-TODO: comment marker that flags where the per-node lookup will land. The signature today takes no arguments and returns the single shared URL; the multi-hub variant will take a nodeId and resolve per-node. Operators do not need to act on this today — the marker exists for forward visibility, not for any current configuration step.

Operators with worse network routing to their assigned hub will, in the multi-hub world, see lower upload scores by design. The bench measures the path the node will use; favourable scoring requires favourable routing.

Pre-flight RTT classification

The bench begins with a per-region RTT probe that classifies each of the eight regions as near, medium, distant, or unreachable before the download pass runs. The thresholds and the per-classification download sizes (100 MB full vs. 25 MiB byte-range) are documented in the master scoring chapter rather than duplicated here — see /docs/scoring · Distance classification.