-
Notifications
You must be signed in to change notification settings - Fork 104
leios testnet
Leveraging DeltaQ tracing mechanism (including the proposed
TraceRecvBurstSDU):
- Aggregated
TraceRecvDeltaQSampleevents (one per peer connection per ~10 s — protocol-mixed today, since the per-protocol split is the unimplemented Shape B intrack.md;estBurstSrides on this same mixed sample): a ~100-node testnet with ~60 peers each gives you ~600 samples/sec cluster-wide, ~8 GB/day at ~150 B/sample (see the Shape-B projection under "Production cost" for the per-protocol multiplier). Shippable to Timescale/Influx or S3+Parquet; Prometheus would strain at this cardinality. - Raw
TraceRecvDeltaQObservationandTraceRecvBurstSDUevents are far higher volume (per-SDU). Ship them selectively — only enable for a subset of nodes, or during measurement windows — and land them in S3 as Parquet for offline analysis. This is the pattern the mempool-measurements dataset already uses. -
PeerRTT.quantileis a liveSTMreader — you can either poll it periodically at whatever cadence you want, or (better) emit it as a periodic aggregate on the same 10 s cycle. -
TraceTCPInfois already wired in the mux bearer, but conditionally: it requires Linux and thetracetcpinfocabal flag onnetwork-muxat compile time (default off; flag description flags it as "costly"). When enabled, per-SDU it emits kernel state (tcpi_retrans,tcpi_lost,tcpi_snd_cwnd,tcpi_advmss,tcpi_rcv_space) — the primary loss-rate and rwnd answer for the validation frame. In practice: compile a dedicatedtracetcpinfobuild for the Linux measurement nodes only, run the vanilla build everywhere else. Treat the resulting numbers as testnet / design-phase telemetry only, not runtime signals — the flag is off in production builds by design (seeleios-overlap.md's Frame 1 subsection on TCPInfo for the design-phase framing). Additional motivation:tcpi_rttcross-checked against the branch'sPeerRTT.quantile(application layer) is one of the RTT-attribution cross-checks inimproved-peer-ranking.md— a peer whose application-layer RTT is much larger than the kernel's TCP-layer RTT is a signature of application-level adversarial delay. For a BBR-vs-CUBIC completion-time experiment, extend the same (already-wired)TraceTCPInfowithtcpi_delivery_rate— on a BBR socket this is the kernel's bottleneck-bandwidth estimate, i.e. a direct measurement of theestBurstSceiling — and record the per-connection controller (getsockopt(TCP_CONGESTION)) as a trace dimension. The existingtcpi_retrans/tcpi_lostalready supply the loss covariate; the CC tag is what makes an outcome attributable — without it, "BBR didn't help" is indistinguishable from "there was no loss for it to help with". The outcome instrument itself (the per-EB completion-time CDF) needs no change: run a BBR node subset against a CUBIC subset on matched paths and compare.
The mux/DeltaQ traces above are the transport layer. The campaign's
headline measurements also need two application-layer logs (from
cardano-tracer, not the mux) — listed here for a single inventory,
with detail in the linked docs:
-
Per-EB arrival log —
(node, EB_id, arrival_ts)at every measurement node, joined with per-EB certification outcome. This is the cross-node per-EB arrival correlation thatleios-priority-measurement.mdargues is the #1 measurement to deploy (it yields$F_{\text{full}\mid C}$ plus ~a dozen by-products).cardano-traceralready emits block-arrival events; the EB analog is a small extension. Optionally enriched per EB with a closure-complete timestamp (closure coverage) andarrived_from_peer(path reconstruction) — see that doc. -
Per-vote arrival log —
(election_id, voter, node, ts)from LeiosNotify/LeiosFetch, for the vote-diffusion half of the certification budget (see Vote diffusion, head-of-line blocking, and Praos priority).
So the infrastructure question reduces to "hook initDeltaQTracer' up to
cardano-tracer and add a Grafana dashboard." References to Frame's
target the leios-overlap.md report, which discusses expanded ouroboros-network
tracing machinery to validate concerns in the DeltaQ report and which in turn
is used to analyze Leios itself.
Validation needs known ground-truth inputs and
measured outputs. For eg. to validate the Mathis relation in
the deltaq report, measure estBurstS and p, which requires
either the TraceTCPInfo kernel telemetry already emitted
by the mux (Linux + tracetcpinfo cabal flag; see the
trace-collection section above and
leios-overlap.md's Frame 1 subsection on TCPInfo)
or the S-residual probe (a noisy secondary derivation).
leios-overlap.md recommends both a controlled
tc netem microbench (where you dial p and RTT explicitly) and a testnet
campaign for different purposes:
-
Microbench: does the model form (Mathis or CUBIC) match observed
throughput at known
p, validating the equation. -
Testnet: what is the empirical distribution of
estBurstS, RTT quantiles, per-protocol tx-arrival latency across a realistic cross-continental mesh? Feeds the inputs to the equation and tells you whether the CIP-0164 assumed values are conservative or optimistic.
Parameter driving (what should CIP-0164 say?) is exactly what a testnet
gives you well. Concretely, the observations feed into the following levers,
grouped by the same three tiers used in
leios-overlap.md's Frame 1 subsection:
| Leios parameter | Data you'd use | Tier |
|---|---|---|
S_EB_tx maximum |
Largest closure whose measured completion-time p95 meets the 7 s voter deadline — sweep size directly; estBurstS×size only to interpolate between sampled sizes |
Protocol parameter |
L_vote (currently 4 s, tunable component of the 7 s voter deadline) |
p95/p99 of voter validate CDF — 3·L_hdr + L_vote + measured closure-fetch completion (not estBurstS·closure_size, which misses the RTO tail that sets the percentile) + µ_eff·N_txs |
Protocol parameter |
L_diff (certRB diffusion budget, currently 7 s) |
p95/p99 of measured blended-multi-hop certRB (8 kB) diffusion completion time (not estBurstS×8 kB) |
Protocol parameter |
Round length (3·L_hdr + L_vote + L_diff, currently 14 s) |
Sum of the two above, plus header diffusion time | Protocol parameter |
p (loss-rate assumption in report.md §4.2, currently 10⁻⁴) |
TraceTCPInfo tcpi_retrans / tcpi_lost distribution across connections |
Rationale-doc assumption |
Mathis-vs-CUBIC choice (report.md:1046-1173) |
TraceTCPInfo tcpi_snd_cwnd trace picks the actual growth law |
Rationale-doc assumption |
long_hop_owd (currently 134 ms) |
Empirical p95 of PeerRTT binned by peer geography |
Rationale-doc assumption |
| rwnd-vs-cwnd binding (§5.4 caveat 1) |
min(tcpi_snd_cwnd, tcpi_rcv_space) reveals which is the actual limit |
Rationale-doc assumption |
BlockFetch per-peer byte watermarks (calculatePeerFetchInFlightLimits) |
Empirical PeerGSV from the branch's window; cross-check via tcpi_delivery_rate × tcpi_rtt on Linux (field not yet in the TCPInfo/Linux.hsc binding — small addition) |
Node-implementation constant |
| SPO tuning guidance (MSS, IW10, congestion-control choice) |
TraceTCPInfo per-connection deltas from expected defaults |
Node-implementation constant (ops guidance, not spec) |
TCPInfo-fed rows are Linux-only, testnet / design-phase only (see the trace-collection section above). All other rows are portable and can be collected on any mux bearer.
For the runtime-adaptive lever the branch also enables (a voter refusing to vote when its local mesh health drops below CIP-0164's assumed budget), see the reader-view section below — it belongs in Frame 2, not in this design-time parameter-driving table.
For the Leios use case specifically:
-
Topology: 30–100 nodes across ap-northeast-1, eu-central-1, us-east-2 (mirroring the mempool-measurements setup so
π₁and network data live in the same dataset), plus 5–10 "adversarial edge" nodes on non-AWS or consumer-grade uplinks to probe §7 limitation 6 (South America ↔ APAC, deep-buffer bottleneck links). A pure-AWS mesh misses the tail (higher loss, lower bandwidth, etc.) that actually determines feasibility, ie. successful Leios rounds. -
Baseline traffic: run Praos with mainnet-scale block production, plus synthetic tx-submission load matching mainnet mempool arrival rates. The Leios pipeline itself may or may not be running; if it is, you're measuring end-to-end; if it isn't, you're measuring the underlying transport under representative load.
-
Instrumentation:
TraceRecvDeltaQSampleon every peer for aggregates. RawTraceRecvDeltaQObservationon 3–5 designated "measurement nodes" (one per region) for high-resolution offline analysis. -
Duration: a week, per §5 of the mempool-measurements dataset. Any shorter and you miss diurnal loss patterns — the day-night cycle of network usage, where packet loss on the same path can swing by orders of magnitude between low-traffic hours (~1e-5) and evening peak hours (~1e-3). A short measurement biases the observed
ptoward whichever part of the cycle you sampled; a week captures the full cycle plus weekend-vs-weekday variation, giving stable tail estimates. Directly plottable fromtcpi_retransagainst local time-of-day. -
Controlled perturbations: on a sub-mesh, inject
tc netemshaping to force known p, RTT, and loss patterns. This gives you the ground-truth pairs needed to falsify Mathis vs CUBIC (i.e., Experiment 1 fromleios-overlap.md, run inside the testnet rather than on a two-node isolated rig). -
LeiosFetch-scheme validation (if a candidate scheme is deployed on the testnet). Compare the four configurations from
leios-fetch-scheme.md's suggested experimental design: memo's scheme as written (baseline), memo + Improvement #1 (hybrid classification only), memo + Improvements #1–#8 (full adaptive variant with fusion and diversity-aware churn), and the erasure-coded variant (Alternative B, if prototyped). Metrics per configuration: empirical$F_{\text{full}\mid C}(14)$ , P99 completion time, redundant egress per round, and adversarial resistance under the injection scenarios in item 7 below. -
Adversarial injection: designated adversarial nodes on the sub-mesh with controlled attack profiles —
tc netemfor the transport-level ones, a little adversarial serving logic for the behavioural ones. Tests defensive mechanisms that would otherwise be untested until real-world adversaries appear — several of them the delivery-gaming and ranking-system attacks catalogued inimproved-peer-ranking.md:-
Slow-loris: adversarial node accepts fetch requests then
delays response indefinitely — no attempt to hide, so it looks
slow. Tests Improvement #3's throughput-collapse detection
(
estBurstSrising) and the ranking (it scores low). -
RTT inflation: adversarial node adds artificial delay to all
responses. Tests the RTT-attribution cross-checks
(
PeerRTT.quantilevstcpi_rttdivergence, geographic minimum RTT), both covered inimproved-peer-ranking.md. -
Pacing (idle-gap): times its SDUs so
estBurstSreads fast — a high-bandwidth look — while the closure actually arrives slowly, the mirror image of slow-loris. Tests the promptness-gated burst window, coverage shrinkage (short bursts starve coverage, pullingestBurstSback to the average), and the actual-vs-predicted backstop. -
Byte-padding: serves other traffic (or other mini-protocols) fast
while dribbling the closure we asked for, so its aggregate
estBurstSstays flattering. Tests per-fetch completion timing (T_wait) and the actual-vs-predicted check on persistence — the signals that isolate our specific fetch from the peer's aggregate. - Reputation farming → surgical withholding (multi-round): behaves well over low-stakes rounds to earn a top rank cheaply, then withholds the one deadline-critical fetch. Tests the score-based churn (worst slice per hour + random resample) and whether a single well-timed defection escapes it — the peer-side analog of the producer-side Targeted release (T22) below.
- Baseline poisoning (multi-round): inflates its own residuals to widen the tolerance band and hide under it. Tests that the actual-vs-predicted band is drawn from the honest-peer population, not the suspect's own samples.
- Chunk-hedge slow-loris: adversary is one of many NBL peers each getting a chunk; it slow-lorises its assigned chunk. Directly tests the memo's key adversarial concern about block-completion under partial-peer failure.
-
Targeted release to voters (T22): an adversarial producer
serves the EB body/closure only to a configured subset of
committee voters — just enough to certify — and to no other
node. Certification succeeds while the rest of the mesh has
received nothing from the producer;
$\hat{F}$ restricted to the non-voter nodes is then a direct empirical$G_{\text{adv}}$ (seeleios-conditional-diffusion.md's adversarial-diffusion section). Tests the CIP's$L_{\text{diff}}$ backstop: whether the >25% honest holders can re-diffuse 12 MB to everyone within 7 s. -
Topology-induced peer concentration (non-adversarial but
shares the measurement path): configure a sub-mesh with a
stake-concentrated "dense" region and a sparse region with
only 2–3 high-stake SPOs. Measure whether churn drives
mesh-wide big-ledger peer sets toward the dense region (as
predicted by the per-block-origin model in
leios-fetch-scheme.md's Improvement #8) and whether sparse-origin blocks then see low mesh-wide seed-hop coverage. Compare against a diversity-aware-churn deployment to quantify how much the mitigation buys.
Sub-mesh scale for adversarial injection should mirror the real-world adversary size assumption (e.g., ~4% of the big-ledger pool, per the arithmetic in
leios-fetch-scheme.md's seed-node section). -
Slow-loris: adversarial node accepts fetch requests then
delays response indefinitely — no attempt to hide, so it looks
slow. Tests Improvement #3's throughput-collapse detection
(
The mempool-measurements dataset that fed pi1_derivation.md is already
collecting some of what you'd need — per-node tx-arrival timestamps.
Extending that pipeline to also collect this branch's DeltaQ traces gives you
π₁ and the network side from the same nodes in the same time window,
which lets you cross-validate: does a period of high π₁ correlate with
degraded estBurstS (i.e., is mempool divergence caused by network
stress)? That's an interesting question the report can't answer today.
Similarly, the IOG benchmarking cluster that runs network-benchmark
topologies is a natural fit — it's already designed to run cross-continental
Cardano nodes under controlled load.
A real cross-continental testnet is the right validation vehicle,
especially for parameter-driving Leios. The traces are already collectible
via the existing cardano-tracer infrastructure. Combining testnet
measurement with a controlled tc netem microbench gives you both the
inputs (empirical RTT/S distributions on real paths) and the
falsification (does Mathis or CUBIC fit at known p) that the report
currently lacks. The main gaps are (a) adversarial-path coverage — pure-AWS
meshes under-represent the tail, and (b) getting the branch's per-protocol
transport signals — estBurstS and matched RTT — onto the deployed nodes
for a direct reading of tx-submission transport health (the 1-hop
pre-diffusion test's transport axis; without them it is an external-consumer
reconstruction, per leios-main-vs-branch.md).
Neither is a fundamental obstacle.
That covers the validation-and-parameter-driving frame. The rest of this document covers three topics that don't fit inside that frame:
- Production cost of the DeltaQ machinery — running the branch's RTT/DeltaQ machinery on public relays (with hundreds of inbound peers) has real cost. What that cost is, what mitigations exist, and how they interact with the two Leios frames.
- Adaptive Leios via runtime measurement — the branch enables Leios mini-protocols to observe their own network conditions at runtime and adapt behaviour, rather than baking assumed conditions into CIP-0164 forever. This is the reader-view expansion below.
- Fully-blended diffusion when 1-hop fails — what happens, how to diagnose it, and how DeltaQ traces expose dynamics the report itself doesn't model. Covered further down.
Existing Cardano practice keeps DeltaQ analysis strictly on the trace
path (rather than in-band with protocol logic) precisely because it's
potentially expensive, and BlockFetch derives its PeerGSV from
KeepAlive-driven samples (once every ~10 s) rather than a richer
signal for the same reason. This part covers what the branch adds on
top of that existing cost, when it matters, and how to mitigate.
Main already emits TraceRecvDeltaQObservation per SDU and runs
TraceStats.step on every observation. That cost is proportional to
SDU rate across all connections. The branch layers on:
-
Cookie mint (egress). PRNG draw +
OrdPSQinsert per SDU, amortised by 1 ms mint-batching to roughly "one cookie per outbound burst" rather than per SDU. -
Cookie match (ingress).
PSQ.atMostViewper received SDU: matched-echo lookup + age-out of expired cookies in one operation. O(log n) where n = outstanding sends. -
Per-protocol StatsA lookup (only if Shape B is implemented —
it is not today; the current aggregator keeps a single
protocol-mixed
StatsA).Map MiniProtocolNumaround the existing per-size record structure. Note what this does and does not cost (see the Shape-B projection below): the hot path barely moves (oneMap.adjustover ≤7 keys per observation, negligible against the cookie PSQ work); what scales ~5× is resident state and per-period emit work, not per-observation CPU. Not part of the branch's current cost. - Burst tracker. Single-slot cursor per connection, essentially free.
-
PeerRTTt-digest update. O(log δ) per matched echo, δ = 100. -
TVarwrite for the reader view: one STM commit per matched echo.
The per-SDU addition is roughly a doubling of main's existing DeltaQ work. Storage per connection: a few kB for the t-digest, a few kB for the (single, protocol-mixed) StatsA state, plus the cookie PSQ (capped at a few thousand outstanding — see the memory note below).
Rough arithmetic for a heavily-loaded public relay (500+ connections, ~10 k SDUs/s aggregate, ~5–10 µs of DeltaQ work per SDU): 5–15% of a core (unbenchmarked estimate — flagged for measurement) and cookie PSQ storage of ~100–250 MB worst-case at the 4096-cap (≈ 4096 entries × ~60–120 B boxed × 500 connections; per-entry size and CPU cost are estimates, pending benchmarking). Not catastrophic in isolation, but additive with everything else on the node — and the kind of overhead current Cardano practice explicitly refuses to pay in the block-fetch decision path.
The cost model above is for the current single-StatsA,
protocol-mixed aggregator. Shape B (track.md) buckets by
MiniProtocolNum, emitting one sample per active protocol per
connection per period instead of one per connection. Its cost
multiplies by the active-protocol count (≤5 upper bound; 2–4 in
practice, since not every hot protocol carries traffic on every
connection in a 10 s window):
| Quantity | Current (single) | Shape B (≤5×) |
|---|---|---|
| Sample rate, cluster (100 nodes × ~60 peers) | ~600/s | ~3,000/s |
| Aggregated-trace volume, cluster (@~150 B) | ~8 GB/day | ~40 GB/day |
| Resident StatsA state, per connection | ~1–3 kB | ~5–15 kB |
| StatsA state, per 500-conn relay | ~1.5 MB | ~7.5 MB |
| Per-observation (hot-path) CPU | baseline | ~unchanged |
The hot path barely moves: one extra Map.adjust over ≤7 keys per
observation, negligible against the cookie PSQ's per-SDU
atMostView. What scales ~5× is resident state and the 0.1 Hz
per-period emit work (5 regressions + 5 samples instead of 1) — so
Shape B leaves the "5–15% of a core" ballpark unchanged and multiplies
sample count, trace volume, and state instead. On the bounded
measurement nodes (~30–50 connections) it is trivial either way; the
~40 GB/day aggregated cluster volume is shippable to Timescale/Influx
or S3+Parquet (Prometheus would strain at the per-connection-per-
protocol cardinality), and is dwarfed by the raw
TraceRecvDeltaQObservation / TraceRecvBurstSDU streams the campaign
already ships selectively.
Almost every Cardano mini-protocol carries data from responder to
initiator — block-fetch, chain-sync, keep-alive. Tx-submission is
inverted: the initiator sends transactions to the responder. See
track.md's cross-cutting note on this
for the general framing.
Practical consequence for cost mitigation: from a relay's perspective, the tx-submission traffic we care about for pre-diffusion monitoring arrives on inbound connections — where this node is the responder, and where peer count is highest (tens to hundreds). Blocks arrive on outbound connections, which is the low-peer-count side.
Any mitigation that turns off DeltaQ machinery "on inbound because no Leios logic reads it" is therefore wrong — it silences the exact tx-submission signal Frame 2's pre-diffusion diagnostic depends on (see The failure-mode diagnostic table). Direction-based gating is not a safe cost-reduction strategy.
Ordered by leverage. Choose a combination based on which frame you're supporting and how much per-peer fidelity the consumer actually needs.
1. Statistical sampling across inbound peers.
Frame 2's pre-diffusion diagnostic asks a mesh-aggregate question — "is gossip healthy across my peers?" — not a per-peer one. Instrument a random 30–50 of 500 inbound peers rather than all 500. Statistically indistinguishable for aggregate purposes at ~10x lower cost. Rotate the sample every few minutes to avoid systematically favouring a subset.
Does not help if the consumer needs per-peer signals, but for the pre-diffusion diagnostic specifically it's a large, safe reduction.
2. Protocol-scoped enablement.
Only run cookie/burst machinery for mini-protocols whose signals are consumed:
- Tx-submission on inbound → for pre-diffusion monitoring.
- Block-fetch on outbound → for peer selection.
- Skip chain-sync (no consumer for its RTT signal here) and keep-alive (already provides a lightweight RTT signal, doesn't need cookies).
Roughly halves per-connection state and per-SDU cost by trimming the
per-MiniProtocolNum map to just the protocols in use.
3. Probe sampling per SDU.
Cookie 1-in-N SDUs rather than every SDU. Reduces PSQ churn linearly. RTT is stationary enough that 1-in-4 or 1-in-10 sampling still gives hundreds of samples per minute per peer — well above KeepAlive's 6-per-minute baseline.
4. Aggregate-only mode.
For consumers that only ever ask "median across my peers", collapse the per-peer RTT windows into a single shared window per protocol. Trade per-peer visibility for cost. Not compatible with peer-selection use cases (which need per-peer numbers) but fits Frame 2 pre-diffusion monitoring cleanly.
5. Compile-time flag.
Gate the whole Network.Mux.RTT module behind a deltaq_rtt cabal
flag, mirroring the existing tracetcpinfo pattern. Off by default.
Ultimate opt-out for operators unwilling to accept any DeltaQ cost.
Downstream consequence: a node with the flag off cannot participate in any Frame-2 behaviour. This bifurcates the network into "Frame-2-capable" and "vanilla" nodes — a real deployment concern requiring governance consideration, not just an ops decision.
6. Consumer-side aggregation.
Move StatsA aggregation off the mux path; ship raw
TraceRecvDeltaQObservation and TraceRecvBurstSDU events to
cardano-tracer and aggregate at the consumer. Trades in-mux CPU for
trace-forwarding bandwidth. Practical for testnet measurement nodes
where the consumer is close by; impractical for production nodes
where raw per-SDU trace volume would swamp the forwarder.
Frame 1 (validation / parameter driving) runs on ~30–50 instrumented measurement nodes with a bounded connection count. Full machinery is affordable at that scale — no mitigation needed for the campaign itself.
Frame 2 (adaptive Leios in production) requires the machinery to run on public relays with hundreds of inbound connections. A defensible production configuration:
- Statistical sampling across inbound peers (~30–50 of ~500), with rotation.
- Protocol-scoped enablement (tx-submission on inbound, block-fetch on outbound).
- Probe sampling at 1-in-4 or 1-in-10.
- Compile-time flag available as the final opt-out for operators who refuse the cost.
The compile-time flag creates a network bifurcation. Nodes with
the flag off can't participate in Frame-2 behaviours (voter
self-throttle, adaptive S_EB_tx, runtime DeltaQ report's Rec 3 enforcement).
Whether Frame-2 behaviours are advisory (nodes MAY adapt) or mandatory
(nodes MUST adapt) is a CIP-0164 governance question the branch
doesn't answer.
Frame 2 changes the shape of the network's protocol behaviour in a
way Frame 1 does not. In Frame 1 the branch only measures — nodes
either produce more or less data, but the protocol is unchanged. In
Frame 2, nodes voluntarily deviate (throttle, opt out) based on
local observation. That deviation is only sound if the deviation
criteria are so rare that variation doesn't move P_cert, or if
every node's deviation happens under sufficiently correlated
conditions.
The cost mitigations above interact with this. Statistical sampling
introduces variance in what each node observes, which introduces
variance in when each node throttles. If observation is
uncorrelated across nodes, the network gets graceful degradation. If
observation is correlated (all nodes throttle at the same moment
because they observe the same shock), the mesh loses quorum en masse
and P_cert collapses. This is the cross-node covariance question
from the blended-diffusion section, applied at the level of adaptive
decisions rather than pre-diffusion state.
So cost mitigation isn't just an engineering optimisation — it interacts with the operational safety of the Frame-2 lever. That interaction is worth measuring on the testnet before committing to any particular mitigation combination in production.
The whole campaign — and Frame 2 especially — trusts the branch's own telemetry. But that telemetry shares a runtime with the workload it measures, and several signals degrade precisely during the heavy-round windows that matter most.
-
Self-inflicted bufferbloat in
PeerRTT. RTT cookies are stamped at SDU construction, before the blocking socket write (theEgress.hsbatching comment notes stamping otherwise inflates the samples). So while a node is doing its own bulk egress, its outbound cookies sit behind megabytes of its own queued closure bytes, and the RTT it measures to a peer is inflated by its own send-buffer occupancy, not the path.TCP_NOTSENT_LOWAT(Linux/Darwin) bounds this by capping the not-yet-sent backlog (gap 17's stack); without it, a node'sPeerRTTis least trustworthy exactly when it is busiest. - Echo timing is workload-shaped. A cookie echo rides the peer's next outbound SDU; on a quiet reverse path the earliest echo is the next KeepAlive fire (~10 s away). So both the sample rate and the measured value depend on reverse-direction traffic, not just the path.
-
GC pauses land in the samples. A multi-second GC pause during
12 MB deserialisation/validation is charged to whatever cookie or
burst gap straddles it — inflating RTT and depressing
estBurstSduring the busiest rounds. - Sample rate covaries with load. More traffic → more cookies → more samples; quiet peers contribute few. Cross-peer quantile comparisons inherit the bias.
-
Mixed-version selection bias. The cookie fields reinterpret SDU
header bytes (see
track.md); a peer on old code never echoes, so in a partially-upgraded mesh telemetry comes only from upgraded peers — which skew toward better-run operators. That biases the very campaign meant to set CIP parameters, and the wire-compatibility / version-negotiation story for the cookie fields is not yet written down.
The sharp consequence for Frame 2. This is why the adaptive decisions
key on the estimate and on observed fetch progress, not on a raw
PeerRTT.quantile compared to an assumed p95 — which would compare unlike
quantities: the observed value embeds the node's own bufferbloat, peer
scheduling, and GC, inflated exactly in the stressed windows where the
decision fires. Observed progress sidesteps most of these artifacts — the
inbound payload arrives at its real rate whatever the node's outbound
send-buffer is doing, and a synchronised GC pause is caught by the
cross-peer common-mode reject. Whatever thresholds remain must be
calibrated against the observed distribution under load, not the path
assumption; the RTT cross-checks (PeerRTT vs tcpi_rtt,
geographic-minimum RTT) stay for attribution, per the companion
improved-peer-ranking.md.
A cheap first-line lever is quantile selection. The no-echo and
GC-pause confounds are sparse and pile up at the extreme top of the
distribution, so reading a somewhat lower quantile (say p85–p90 rather
than p99) sits below where those artifacts dominate while staying
tail-sensitive enough to flag real degradation — a deliberate trade of
a little extreme-tail sensitivity for robustness against the
contributions we want to exclude. Since PeerRTT.quantile takes the
quantile at read time, the consumer picks that operating point
directly. This helps the sparse-outlier confounds only: sustained
self-bufferbloat during bulk egress shifts a broader part of the
distribution (needs TCP_NOTSENT_LOWAT), and the mixed-version bias is
about which peers contribute, not one peer's tail.
On the testnet, quantify the bias directly: compare a node's
self-measured PeerRTT against external ping / kernel tcpi_rtt
during its own bulk-egress windows versus idle.
The Leios report and CIP-0164 both operate in what one might call the
static-analytical mode: pick network conditions (RTT distribution,
congestion model, loss rate), solve for parameters (L_vote, L_diff,
S_EB_tx) such that P_cert ≥ threshold, freeze those parameters into the
protocol. Once frozen, every node applies them identically, blind to whether
the actual mesh conditions at that moment resemble the assumed ones. A voter
that cannot fetch its closure in time still votes on the same deadline as
one that can; a block producer still packs closures to the same S_EB_tx
regardless of what its actual peers can absorb.
The reader view breaks this: a live Leios voter (or block producer, or
non-voter fetcher) can read, at runtime, not merely the RTT to each peer
but the full time-to-deliver estimate ramp(RTT) + estBurstS × size
for a transfer over that link (the companion
improved-peer-ranking.md), refined mid-fetch
by the observed progress of the transfer in flight. That is the category
shift from offline-analytical to online closed-loop. We key these
decisions on the estimate, not raw RTT; the companion explains why.
Ordered roughly by conservatism:
-
Self-diagnosis / operator alerting. The mildest use. Compare the node's measured time-to-deliver for a typical closure — its estimate to representative peers, or the observed completion of recent fetches — against the budget CIP-0164 assumed. If it exceeds the envelope by some margin, log a warning. Doesn't change protocol behaviour; just tells operators their node is operating outside the CIP-0164 design envelope.
-
Adaptive peer preference. BlockFetch already selects peers by
PeerGSV, but its update path is via KeepAlive's ~10 s cadence — coarse. The window-based signal from this branch is per-connection, updated on every response, and covers all miniprotocols. Peer-selection could react seconds faster — ranking on the estimate, and on measuredT_waitwhere fetches have completed, which is what the companion builds to be adversarially robust, rather than on raw RTT drift. -
Adaptive
S_EB_txsizing at production time. A block producer preparing an EB can invert the estimate: from its measured link conditions to typical peers, pick the largest closure sizeSwhose predicted diffusion timeramp(RTT) + estBurstS × Sstill fits the certification budget, and cap belowS_EB_tx_maxaccordingly — voluntarily accepting a smaller EB to keep certification probability up. Global CIP-0164 sets a ceiling; the node sets the actual size per-round from its measured delivery capacity. This is also the mesh's stabilizing feedback against sustained-overload livelock — see the Cascade to livelock section below. -
Adaptive Freshest-First Delivery (FFD). FFD is the "when I'm behind on diffusion, prioritise the newest EB and drop the older" mechanism the report defers modelling (§7 limitation 7). Its trigger is fundamentally "am I going to make my deadline?" — time remaining against the estimate for the work remaining. Before a fetch that is
ramp(RTT) + estBurstS × work_remaining; once one is in flight it is the live projection from observed progress — this round's measured rate on the bytes still outstanding — which also captures the RTO tail thatestBurstS × sizemisses (the reason the parameter table prefers measured completion). Drop the older EB when that projection says the newest won't otherwise land. Static FFD thresholds are guesses; the reader view makes the projection possible. -
Voter self-throttling. The most aggressive use, and the one that most needs the robust signal. The decision is FFD's own "am I going to make my 7 s deadline?" (item 4): project this round's completion from the voter's observed fetch progress — the live rate on the closure actually arriving, against the bytes still unfetched and the time left — and bow out if it won't finish, rather than voting and later failing to diffuse the certRB. Anchoring on observed progress is essential, not incidental: because it is direct measurement, a set of peers inflating their echoed RTT cannot trick the voter into standing down (see Why this doesn't come for free, below).
PeerRTT.quantileabove the CIP-0164 assumption is at most a coarse pre-fetch prior and a mesh-health cross-check. This trades quorum size for quorum quality — fewer votes, but the ones cast are more likely to diffuse.
The interesting property that runs through 3–5: they let a node voluntarily
degrade under stress in ways the static analysis can't. The report's
P_cert collapses to ≈ 0 under catastrophic pre-diffusion failure because
every voter tries and every voter fails. An adaptive scheme where
stressed voters bow out could keep the quorum threshold met among the
healthier subset — turning a hard cliff into a graceful degradation.
Behaviours 3 and 5, and the livelock stabiliser below, all want the same
gate: back off only when the mesh is broadly overloaded, not when one or a
few peers are struggling — which is the normal background and means nothing.
The per-peer machinery already draws that line in one special case: the
cross-peer common-mode reject of
improved-peer-ranking.md suppresses a stall
seen on every peer at once as our-side-or-network, not any peer's fault.
Mesh-stress detection generalises it from synchronised stalls to
synchronised degradation, and poses it as a belief we update rather than a
threshold we trip.
The observation, normalised per peer. Absolute rates can't be compared —
a far peer is legitimately slow — so score each peer against its own
baseline (the peer-ranking prior μ_p): is its recent delivery (estimate,
or measured T_wait) more than a margin below μ_p? The per-tick
observation is the count x of such peers out of N active; a large x/N
is a correlated dip below per-peer norms — the signature of a mesh event
rather than of the ever-present few slow peers. A second, slower channel
guards the sustained case, since the adaptive baselines eventually follow
a lasting slide and stop firing: the mesh's aggregate delivery against the
fixed CIP-0164 envelope (the self-diagnosis comparison above), which
cannot adapt away. This two-channel split — relative dip-count for speed,
fixed envelope for the frame — is the relative signals, absolute anchors
invariant of
improved-peer-ranking.md.
The belief, by recursive Bayes. Rather than threshold x/N, carry a
posterior P(stressed) over a latent mesh state that evolves slowly (two
states with a low switch probability, or a continuous level on a random
walk), updated each tick from the observation. This is the dual of the
peer-ranking shrinkage — there we borrow the population to sharpen one peer;
here we pool the peers to estimate what they share, the common shift — and
it inherits three properties a bare index would have to bolt on:
- Hysteresis, from the transition prior. A state a priori slow to switch means one noisy tick barely moves the posterior while sustained correlated evidence moves it fast — the damping the adaptive behaviours require, derived rather than dialled in, with no separate band.
-
Small-
Nshrinkage, from conjugacy. Withx ~ Binomial(N, θ)under a Beta prior on θ, the posterior is Beta and wide when few peers are active, so "2 of 3 down" yields low belief on its own. "Sharpens with peer count" is then just the posterior tightening asNgrows, not a fallback. -
Reliability-weighting. Weight each peer's evidence by its own precision
(coverage, variance), so a well-established peer counts and a thin or new
one barely does — the same
σ²/τ²decomposition the coverage scalekuses, now on the shared factor. Pool it robustly as well — a median or trimmed aggregate over the per-peer evidence, not a raw mean — so a few outlier or adversarial peers cannot swing the belief: the "median, not worst" guard, applied to the whole distribution.
A second dimension: mempool fragmentation. The same filter generalises
past transport. Failing pre-diffusion — the referenced txs not yet in the
local mempool when an EB arrives — shows up as an elevated observed π₁
(the missing-tx fraction; Diagnosing pre-diffusion below), and when that
is persistent across rounds and broad across EB sources (the mempool
systematically behind whoever produced the block, not one front-running
producer) it is a second stress tell. Keep it a second latent dimension
rather than folding it into transport, because the two are distinct: a
healthy pipe can coexist with a fragmented mempool (a gossip pathology,
partition, or withholding). The joint belief over {transport-stress, mempool-fragmentation} is then the continuous form of the
failure-mode diagnostic table below —
its quadrants are the belief's modes, and its transport-vs-π₁ cross is the
causal split (elevated π₁ with degraded transport is one cause driving
both; with a healthy pipe is the distinct pre-diffusion failure).
The two dimensions drive the same back-off lever through different terms of
the estimate, which keeps the coupling clean: transport stress raises the
rate term (ramp + estBurstS), while mempool fragmentation raises the
size term — a fragmented mempool makes fetchers cache-miss and pull the
missing txs, so the effective closure balloons. That is exactly the
"failing pre-diffusion → larger closure fetches" effect noted elsewhere, and
behaviour 3's size-aware sizing already caps S_EB_tx when size rises,
for the same certification-budget reason. One belief, two entry points.
Deciding, and by cause. Act when P(stressed) crosses a point set by the
asymmetric loss — a missed overload risks the livelock cascade, a false
backoff costs some throughput — not a hand-picked threshold. Separate the
cause with a third state, {healthy, network-stressed, self-paused}, where
agreement between app-layer RTT and kernel tcpi_rtt is the observation that
tells a real path slide from our own GC/scheduler pause: back off production
under network stress, where shedding load helps; don't bother under a
self-pause, where it doesn't. A parallel split is spatial — cluster the
degraded peers by region or path: tight clustering is a regional partition
or regional congestion rather than global overload, and calls for routing
around the region, not a mesh-wide back-off.
The lever, and the cost. On high belief, cap S_EB_tx below the CIP
ceiling (the Cascade to livelock
stabiliser), trim voting commitment (behaviour 5), defer non-urgent fetches;
release as the belief decays, the transition-prior damping keeping the
release from synchronising across the mesh. The filter is O(1) per tick — one
conjugate Beta update and one multiply-normalise on the state posterior — so
it costs nothing over counting, and reuses the per-peer μ_p, T_wait, and
common-mode signals already maintained.
Calibration and validation. The filter's knobs come from the testnet traces, not from feel. The transition prior follows the measured autocorrelation timescale of degradation episodes — how long stress actually persists — so its built-in hysteresis matches reality rather than a guessed horizon.
The decision threshold — how high P(stressed) must climb before we back
off — is set by weighing the two mistakes, whose costs are lopsided:
missing a real overload risks the livelock cascade, whereas a false
backoff merely forfeits some throughput for a round. That imbalance is the
asymmetric loss, and because a miss is far dearer than a false alarm it
pulls the trip point well below an even 0.5 — the valve fires early on
purpose.
That threshold only means something if the belief is calibrated — if its
numbers mean what they say. A calibrated P(stressed) = 0.8 means that
across all the moments it read 0.8, the mesh really was stressed about 80%
of the time; one that reads 0.8 when the truth is nearer 50% is
overconfident and would trip wrongly. Two standard tools measure this
against the recorded outcomes. A reliability diagram bins the
predictions and plots, per bin, the average predicted probability against
the observed frequency of stress — perfect calibration lies on the diagonal,
and departures show over- or under-confidence. The Brier score condenses
the same check into a single number: the mean squared gap between the
predicted probability and the 0/1 outcome (lower is better). Where either
shows miscalibration, the prior and threshold are adjusted to close it.
Finally, the belief earns its place as a safety valve only if it leads certification failures by enough margin to act on — measured by its cross-correlation with the failure-autocorrelation series (below). A signal that merely coincides with failures is a diagnosis, not a valve.
Studying an episode after the fact — measuring the lead, fitting the
transition prior, checking calibration — needs the right traces, and the
governing principle is to log the belief's inputs, not just
P(stressed). A logged belief can only be replayed under the parameters
that were live when it was written; the observations behind it can be re-run
through the filter under any transition prior or threshold, which is
exactly what re-tuning and calibration require. Emit three streams on one
monotonic clock, each carrying a slot/round reference for alignment.
The mesh-stress tick — one record per filter update (≈ 1 Hz, faster if episodes are short):
- the timestamp and current slot;
- the belief
P(stressed)and its componentsP(transport),P(mempool)(and the three-state cause posterior, if run); -
the observation that drove the update —
x(peers below their ownμ_pby the margin) andN(active peers), the robust aggregate value, the aggregateπ₁over recent EBs, the envelope ratio (measured time-to-deliver ÷ CIP-0164 budget), the app-vs-kernel-RTT agreement summary, and the per-region degraded counts; - the filter parameters in effect (transition probability, threshold, margins), so a replay knows the live configuration it is reproducing.
The round outcome — largely the per-EB certification log already
proposed as the campaign's #1 measurement
(leios-priority-measurement.md), one
record per round:
- the slot/round id and timestamp (same clock);
-
certified?/failed?and the failure mode; - the per-EB observed
π₁, the closure size actually fetched, and the fetch'sT_wait.
This is both the outcome series the belief is cross-correlated against and the source of the failure-autocorrelation series itself.
Valve and action markers — one record per event:
- the timestamp, whether the belief crossed the arm or the release
threshold, and the action taken (the
S_EB_txcap applied, a voter bow-out, a deferred fetch).
These delimit episodes and carry the follow-on question past lead — whether acting actually helped.
A fourth stream is optional and sampled: a per-peer snapshot (peer id,
region, current estimate / T_wait, μ_p, coverage/precision, degraded
flag, app-vs-kernel RTT), taken every few seconds or on threshold crossings
rather than every tick. It is too heavy for the hot path, but it
reconstructs which peers led and the spatial structure when an episode
warrants a closer look.
What each analysis then draws on:
-
Lead — the lag at peak cross-correlation of the
P(stressed)series against the failure indicator, segmented per episode by the markers. - Transition prior — the persistence timescale read off the observation series' autocorrelation.
-
Calibration — the
P(stressed)samples binned against the realised "stressed" outcome (defined from the round log: the round failed, orT_wait/π₁breached a margin), scored as a Brier score or reliability diagram. - Re-tuning — replay the observation records through the filter under candidate parameters; the live node never has to reproduce a configuration.
Three problems, in decreasing order of severity:
-
Adversarial gaming — being tricked into standing down. A colluding set that delays echoing our cookies inflates our locally-observed RTT to them. Against peer demotion this is self-correcting — a peer that echoes slow ranks itself down and we route around it. The dangerous reaction is self-throttling (item 5): if a bow-out keyed on locally-observed RTT, a small colluding set could inflate it and push a targeted voter out of the committee. The primary defence is structural, and it is why item 5 keys on observed progress: base the bow-out on the closure bytes actually arriving, not on RTT. A colluder can delay its echoes but cannot make the payload we are receiving appear absent — a voter on track to finish votes regardless of an inflated RTT. Aggregating across peers (median, not worst) and the cross-peer common-mode reject — a genuine mesh-wide slowdown shows on all peers at once, an attack on a few — are secondary cross-checks.
-
Feedback loops in the mesh. If some nodes self-throttle and others don't, the ones still voting have to carry more of the diffusion load, potentially degrading the observed RTT for them, which could cascade. This is a classic control-theory pitfall. Any adaptive behavior needs damping — a hysteresis band, or a stochastic component in the throttle decision — to avoid synchronized oscillation across the mesh.
-
Governance / spec-compliance. CIP-0164 is a consensus-critical spec; adaptive voter behaviour that varies decisions based on local observation is a soft deviation from "every voter runs the same protocol." Whether an adaptive voter's cast/not-cast decision is spec-compliant, or whether the CIP needs to explicitly allow (or mandate) it, is a governance question — not a technical one. The technical prerequisite (the reader view) is there; the political prerequisite isn't.
Praos already has adaptive elements (peer selection, BlockFetch churn,
ChainSync candidate ordering) that consume PeerGSV. So the pattern —
miniprotocol reads network telemetry, adjusts behavior — is not new to
Cardano. What's new is:
-
Leios's deadlines are tighter. Praos's mean block interval is 20 s (active-slot coefficient 0.05 over 1 s slots), but that is not a per-block diffusion budget — blocks can be one slot apart, and the operative diffusion target is Δ ≈ 5 s. Praos typically delivers within 1–2 s, so ~2–3× headroom against Δ. Leios's 7 s voter deadline against a 4.4 s modelled long-haul fetch has ~1.6×. Still tighter — but the gap is ~2–3× vs ~1.6×, not 10× vs 1.6×, so the "category shift" is real but milder than a naive slot-interval comparison suggests. Variance that Praos absorbed starts to matter.
-
Certification is atomic. A slow block in Praos still gets adopted eventually; a slow EB simply doesn't certify. There's no "eventually" — either you make the 7 s deadline or you don't. That's exactly the regime where local decision-making beats global static settings.
-
The report's own recommendation 3 depends on it. "Ensure effective tx-submission pre-diffusion" (
report.md:1584-1588) is currently unsatisfiable at protocol design time because pre-diffusion is a runtime property. But it's satisfiable at runtime: a node observing degraded pre-diffusion (via per-mini-protocol DeltaQ traces on tx-submission) can react — refuse to vote, reduce closure size, alert. ThePeerRTTreader view (and its per-protocol siblings) is the API that turns Recommendation 3 from unsatisfiable to enforceable.
The reader view means Leios doesn't have to bet its safety on the network conditions assumed at CIP-0164 design time being the ones present at runtime. It can measure, and adjust. That's a lever the report's static-analytical framing doesn't consider, and probably the single largest place where the branch's work changes what Leios can do, as distinct from what the report can verify.
The reader-view section covered adaptive protocol behaviour in the good
case, where the mesh is broadly healthy and a node is fine-tuning around
CIP-0164's assumed baseline. This part covers the degraded case: when
1-hop pre-diffusion has failed and the full EB closure has to traverse the
multi-hop mesh, with the report's α · P_{cert,1-hop} mixture collapsing
towards the blended-diffusion tail. Two subsections — one on diagnosing
which failure mode you're in, one on investigating the dynamics of the
mode itself.
The report's Limitation 1 and Recommendation 3 both hinge on
pre-diffusion — the assumption that transactions have already reached
local mempools via tx-submission gossip before the EB referencing them
is produced. Testing whether that holds, and diagnosing why when it
doesn't, needs two signals from different layers. Neither is sufficient
on its own; conflating them (as the report's α mixture parameter
implicitly does at report.md:1584-1588) loses information a production
node needs.
Transport-layer (mux-layer, from the branch's traces).
Per-mini-protocol DeltaQ traces bucketed to tx-submission give, per peer,
the time-to-deliver estimate for the tx exchanges — ramp(RTT) + estBurstS × size over RequestTxs → ReplyTxs (and the lighter
RequestTxIds → ReplyTxIds) — folding round-trip and throughput into one
figure, with a dead channel (the peer not responding at all) the
degenerate case. Interpretable as "how quickly can the tx-gossip pipeline
actually deliver right now, per peer?" Raw per-exchange RTT is a component
of that estimate and a cross-check, not the verdict on its own.
Application-layer (from EB processing, not mux). At each EB arrival, cross-reference the EB's referenced tx-hash list against the local mempool. The fraction not present is the observed π₁ for that EB, at this node, at this moment. Interpretable as "how out of sync was my mempool at the moment I needed it?"
The report's mempool-measurements dataset already collects the outcome
signal at coarse granularity (per-node, per-block). The mux traces
contribute the transport signal, per-connection, at higher temporal
resolution. Both are needed.
Composing the two — each judged against its per-region-pair baseline
(below) — gives a diagnostic that distinguishes qualitatively different
failure modes. Transport: Degraded means the tx-delivery estimate
(or the measured completion of RequestTxs → ReplyTxs) sits above baseline
— round-trip and throughput together, not a raw-RTT threshold; π₁:
Elevated means the missing-tx fraction sits above baseline.
| Transport health | Observed π₁ | Interpretation | Response |
|---|---|---|---|
| Normal | Normal | Healthy pre-diffusion. | — |
| Degraded | Elevated | Slow gossip → mempool lagging. The characteristic transient-stress signature (peer churn, bufferbloat, brief network hiccups). | Prefer healthier peers via peer-selection churn; wait it out. Escalate if severity persists or grows (see below). |
| Normal | Elevated | Pipeline fine yet mempool out of sync. Points at adversarial withholding, a targeted partition affecting only tx-submission for a subset of peers, or a Cardano-specific gossip pathology. | Alert operators; refuse to vote in the affected round; force reconnect to fresh peers. Distinct from network stress and warrants a stronger response. |
| Degraded | Normal | Slow gossip but overall state healthy — a caching/burst effect, or a lagging monitor whose window hasn't caught up. | No action; sanity-check the transport metric. |
The Degraded × Elevated row deserves a severity distinction in practice, scaled by how far the delivery estimate and π₁ sit above baseline. A mild version — delivery time modestly up, π₁ from 0.06 to 0.15 — is the transient signature this row identifies. A severe version — delivery time several-fold up, π₁ from 0.06 to 0.4 — is a compound failure warranting the same response as Normal × Elevated (throttle production, refuse to vote). A raw RTT multiple is at most a cross-check on cause here — a high app-RTT against a healthy kernel RTT points at echo-holding rather than a congested path — not the severity measure itself. Baseline calibration on the testnet is what tells you where "mild" ends and "severe" begins for each region-pair.
The value of the split is that "pre-diffusion is failing" is currently a
single conflated signal in the report. A production node armed with
the transport-vs-outcome split can react to which kind of failure is
in play, and reserve the strongest responses (refusing to vote,
alerting operators) for cases that actually warrant them. And this table is
the discrete reading of the mesh-stress joint belief
(Detecting mesh-wide stress above) over
{transport-stress, mempool-fragmentation} — its four quadrants are that
belief's modes. A live node runs the belief, with the hysteresis and
small-N shrinkage that come with it; the table stays the offline
diagnostic snapshot.
The table's "Normal" and "Elevated" categorisations are only useful
with a per-region-pair baseline distribution — mean and tail of the
tx-delivery estimate (or measured RequestTxs → ReplyTxs completion),
mean and tail of observed π₁, all measured under representative load. A week of the testnet campaign's
baseline collection (see
What a good testnet campaign looks like
above) establishes those baselines. Without them, the categorisation is
guesswork.
Forced-blended experiments in the next section intentionally drive the mesh into the Normal × Elevated quadrant on the suppressed nodes — their tx-submission is fine with peers that also don't suppress, but their local π₁ shoots up. Confirmatory: the diagnostic table should reproduce the induced failure mode. If it doesn't, either the induction is not doing what you think or the diagnostic is not measuring what you think — both worth debugging.
Collected and analyzed DeltaQ data fits well as inputs to the report's blended-diffusion model, indirectly but usefully covers the composed end-to-end multi-hop dynamics, and — most valuably — can reveal a family of dynamics the report itself doesn't model.
The report constructs its blended-diffusion CDF (report.md:359-436) by
convolving:
- A per-hop transfer-time CDF, parameterised by the throughput model (Mathis or CUBIC) at assumed p and RTT bin (short/medium/long).
- A path-length distribution (1..5 hops with the report's regular-random-graph probabilities).
- An RTT-bin mixture (short/medium/long equally likely per hop).
Every piece of that has to hold up empirically for the report's conclusion (14 s delivery only 14% under Mathis, 99% under CUBIC) to reflect reality.
The single-hop CDF — item 1 above — is exactly what per-connection
estBurstS and PeerRTT produce. On a real testnet:
- Every burst response (blockfetch chunk, EB body, missing-closure fetch)
generates an
estBurstSsample: throughput of that hop under the actual congestion regime, actualp, actual buffering. No Mathis/CUBIC assumption in sight. - Every matched cookie produces an RTT sample; a
PeerRTTquantile per peer gives you empirical per-hop CDFs binned by peer-distance category.
Plugging those CDFs into the report's convolution instead of the
analytical Mathis/CUBIC ones gives you a directly-empirical estimate of the
blended-diffusion CDF at each S_EB_tx. That's the cleanest validation
path: keep the report's convolution engine, replace its analytical inputs
with measurements, compare outputs.
This alone would settle whether the 14% (Mathis) or 99% (CUBIC) figure at 12 MB is closer to reality on a given mesh.
Stratify, and sample the tail. That per-hop CDF is only as good as
its conditioning. Its tail — the slow completions that set
leios-conditional-diffusion.md
model consumes; the aggregated sample's per-SDU estDeltaQVVar is a
different, low-level quantity — within-burst micro-jitter, a
CC-smoothness diagnostic (BBR pacing vs CUBIC ACK-clocking), not this
input.
The catch is sample count, and it is worse than it looks. The per-hop
tail must be resolved deeper than the per-node deficit you ultimately
care about: since one slow hop makes a slow path, a per-node deficit of
~1e-4 needs the per-hop tail down to ~1e-4/E[hops] ≈ a few×1e-5 — i.e.
~1e⁵–1e⁶ bursts per bin, from the raw per-burst stream
(TraceRecvBurstSDU, shipped selectively per the trace-collection
section), not the aggregated 10 s sample. Aggregate volume doesn't
rescue it: ~100 nodes × ~30k EBs/week ≈ 3e6 per-hop transfers sliced
into dozens of bins is only ~10⁴/bin — short by one-to-two orders in the
sparse (rare-RTT/loss) bins that often matter most, with no closed form
to interpolate BBR's tail from a few moments (cf. the
tail-power caveat).
So reserve the per-hop route for what it is uniquely good at:
prediction — convolving the measured per-hop CDF over a different
topology (i.e. a different path-length distribution) or a hypothetical
CC deployment (the BBR-on-far-peers what-if). For the headline
One blended-diffusion input is answerable from the peer graph alone —
no DeltaQ traces, no per-EB tagging, no consumer plumbing. The main
one is structural path-length distribution, the assumption the
report leans on hardest for its blended-diffusion CDF
(report.md:359-436).
Operational shape:
- Query each testnet node's peer-selection state (already introspectable via existing node interfaces).
- Dump the graph — edges are directed peer connections; vertices are nodes.
- Compute the shortest-graph-path distribution, degree distribution, clustering coefficient, and hub concentration.
What it answers. Does the mesh's structure resemble the report's regular-random-graph assumption (degree ~10, no significant clustering, path-length ~3.6)?
What it does not answer. What paths EBs actually take. Peer-selection preferences and BlockFetch decisions can route diffusion onto longer or shorter paths than the shortest-graph-paths. That gap is filled by reconstruction (next subsection).
Ordering the two. Topology snapshot first, reconstruction second:
- Topology snapshot is a one-day exercise. Immediate feedback on whether the mesh's structure resembles what the report assumes. If the answer is "no", that alone is a finding worth reporting before running the more expensive reconstruction. Repeat weekly on a live testnet to catch topology drift.
- Reconstruction (next subsection) is the higher-fidelity but more expensive measurement, and its number is what the report's convolution actually depends on.
The comparison between the two produces one of four outcomes, each telling a different story about what should happen next:
| Topology says | Reconstruction says | Interpretation | Follow-up |
|---|---|---|---|
| Matches assumed ~3.6 | Matches topology | Mesh is what the report assumed; peer-selection isn't biasing diffusion. | Report's convolution well-founded; confidence in 12 MB feasibility increases. |
| Matches assumed ~3.6 | Longer than topology (e.g. ~4.8) | Structure is fine but peer-selection pushes diffusion onto longer paths. | Investigate peer-selection behaviour; unexplained bias in the diffusion routing. |
| Longer than assumed (e.g. ~5.2) | Matches topology | Mesh structure is worse than assumed; diffusion follows what the structure allows. | Recalculate the report's convolution with the empirical path-length. |
| Longer than assumed | Shorter than topology | Peer-selection is routing around long paths (via hubs, for instance). | Model has a hidden resilience mechanism worth documenting. |
Doing only one of the two measurements leaves ambiguity that the other resolves.
The convolution assumes hops are independent and identically distributed. The mesh may not honour that. To check, you need to observe end-to-end delivery, not just single-hop:
-
Tag every EB (or synthetic bulk payload) with a stable ID, log
(node, ts, EB_id, arrival_or_forward)events across the whole testnet. -
Also tag each BlockFetch / LeiosFetch fetch session at each node
with a local session ID that references the EB hash it's pulling.
The EB-arrival log above is application-layer and answers "when did
EB X arrive at node Y"; a per-fetch session tag on the local traces
answers "which mux-layer signal belongs to which fetch". Under the
interface-saturation scenario in Dynamics the report doesn't model
(item 1), this makes the cross-node correlation direct: "during
session
$S$ for EB$X$ on connection B↔C, B's other Wantons saw oldest-byte-age rise by$\Delta$ , and peers D/E/F sawestBurstSdrop by$\varepsilon$ " — instead of timestamp-guessing which spike belongs to which fetch. No wire-format change needed: the EB hash is already on the wire (BlockFetch / LeiosFetch requests reference EBs by hash), so the session ID can be derived deterministically from(peer, EB_hash, fetch_start_time)and only needs to appear in the local BlockFetch / LeiosFetch client trace stream to key mux-layer signals against. - Reconstruct actual paths by matching each node's arrival time against its forwarding events, plus the known peer graph. You now have the actual multi-hop trajectory of each EB across the mesh.
- Compare the observed end-to-end arrival CDF against the DeltaQ- convolved prediction. If they diverge, the independence assumption is wrong — head-of-line effects at intermediate hops, or graph-topology skew, are the likely culprits.
This isn't in the mux traces themselves — it's a higher-level analysis
layered on top. But the mux traces plus a modest per-EB-arrival log gives
you enough to do it. cardano-tracer already emits block-arrival events;
the same pattern for EBs would suffice.
The same per-EB arrival log that enables path-length reconstruction
also lets us directly measure Yves Hauser's §5.6 conditional
probability: given certification succeeded at $t_v = 7\text{ s}$,
did all $N$ honest nodes receive the EB body by $t = 14\text{ s}$?
See leios-conditional-diffusion.md
for the full analysis; the operational recipe is:
- Log
(node, EB_id, arrival_ts)at every measurement node (same log as the cross-node correlation section above). - Log certification outcome per EB (from
cardano-tracer's block-diffusion / EB-certification traces). - For each EB
$j$ with$C_j$ (certification succeeded), check whether$\max_i T_j^{(i)} \le 14\text{ s}$ . - Empirical estimator:
$\hat{F}_{\text{full}\mid C}(14) = \frac{\left|{j : C_j \text{ AND all arrived by 14 s}}\right|}{\left|{j : C_j}\right|}$ .
Yves's formula predicts leios-conditional-diffusion.md.)
Detection tail power: ~30 k EBs/week makes the coarse gradations
(0.9 / 0.99 / 0.999) all trivially distinguishable; zero observed
failures bounds the true failure rate at leios-conditional-diffusion.md).
As a by-product, the same data gives:
- The identity of the slowest node per EB
(
$\arg\max_i T_j^{(i)}$ ) and the temporal regime of that identity across rounds (persistent / rotating / bimodal). Per-node operational intervention pays off only under the persistent regime; the rotating and bimodal (churn-induced) regimes each call for different levers. Seeleios-conditional-diffusion.md's "Slowest-node identity and its temporal structure". - Empirical vs modelled
$G(t)$ — divergence tells us whether$G$ is over- or under-estimated at each body size. - The correlation matrix between per-node arrival times — directly
addresses Yves's caveat 1 (i.i.d. assumption) and gap 2 of
leios-report-gaps.md.
The appendix in
leios-fetch-scheme.md
identifies a tendency for peer-selection churn to concentrate the
mesh's big-ledger peer sets around block-production regions,
producing bimodal per-round coverage (high on dense-origin rounds,
low on sparse-origin rounds). The actual strength of this tendency
on real networks is an empirical question. The same per-EB arrival
log used for
1. Per-round orphan-set identification. For each certified EB, identify the set of nodes that did not receive delivery from a big-ledger seed (their winning arrival was via a non-big-ledger path, or arrived after a threshold indicating multi-hop). Peer-metadata (big-ledger flag per connection) distinguishes the winning path.
2. Block-origin classification per round. For each certified EB, identify the block producer's geographic / ASN region. Classify the round as dense-origin or sparse-origin based on the producer's location (or, more robustly, based on stake-weighted region density in a rolling window). This is the key axis of the analysis.
3. Per-origin coverage rates (
-
$c_D$ = mean mesh-wide seed-hop coverage (fraction of nodes reached by a big-ledger seed) on dense-origin rounds. -
$c_S$ = same for sparse-origin rounds. -
Empirical
$k = c_S / c_D$ .(This is the same estimand as in
leios-fetch-scheme.md's appendix — a per-round-type mesh-wide coverage fraction, not a fraction of rounds.)
If
4. Per-round bimodality analysis. Instead of asking "which nodes are persistently orphaned", ask "which rounds have widespread orphaning". Histogram the mesh-wide orphan fraction across all rounds. Under the churn tendency, expect a bimodal distribution (peak near zero for dense-origin rounds, peak near 0.75–0.8 for sparse-origin rounds). Under weak tendency, expect a unimodal distribution centered on the mesh average.
5. Cross-node covariance within rounds. On sparse-origin rounds, compute the covariance between arrival times across nodes. High within-round covariance is the signature of a shared bottleneck (the multi-hop diffusion route from a sparse origin). This directly measures Yves's caveat 1 applied to the seed-node lottery.
6. Peer-graph diversity metrics (secondary). For each node, compute ASN / country / geographic diversity of the 5 big-ledger peers, peer tenure, and average RTT. Correlate diversity with per-node orphan-frequency. Under the tendency, diversity should correlate positively with fewer sparse-origin orphanings; under weak tendency, no strong correlation.
7. Effect on
- Peer-metadata service — MaxMind or equivalent for ASN / country lookups from IP addresses. Roughly a few MB static database, daily refresh. Cheap.
- Peer-graph tracking — per-node record of which peers were big-ledger vs NBL at each round. Small addition to the per-EB arrival log.
-
Block-producer identity per round — required to classify
rounds as dense-origin or sparse-origin. Available from the
block header / chain-sync metadata already emitted by
cardano-tracer; needs to be joined into the per-round dataset. - Regional partitioning of stake — a rough "dense vs sparse" region cutoff for classifying block-origins. Could be geographic (continents), by-ASN, or by empirical stake density. Multiple partitionings for robustness against a bad partition choice.
Under the four-configuration experiment (item 6 of "What a good testnet campaign looks like"):
-
Memo baseline: establish empirical
$c_D$ ,$c_S$ ,$k$ , and the per-round bimodality profile. If$k \approx 1$ (no strong concentration), the tendency isn't dominant on this mesh and the other configurations may not add much. If$k \ll 1$ (strong concentration), we have a real problem to fix. - +Improvement #1 (hybrid classification): minimal effect on churn dynamics — classification refines within the existing peer set but doesn't change churn selection. Concentration should be similar to baseline.
-
+Improvements #1–#8 (full adaptive): Improvement #8's
diversity-aware churn should measurably raise
$c_S$ (sparse-origin coverage) and hence$k$ . Regional variance in$F_{\text{full}\mid C}$ across round-types should shrink. - Erasure-coded variant: orphaning behaviour differs entirely (no single seed peer needed). Different question — not directly comparable but useful as an alternative.
The dominant question the measurement answers: how much does
diversity-aware churn actually raise coverage on sparse-origin
rounds? If the answer is "meaningful gain" (
Blended diffusion only matters when 1-hop pre-diffusion has failed. To exercise it on a testnet you have to force pre-diffusion to fail:
- Suppress tx-submission on a controlled subset of nodes (or the whole mesh) for a window. Every EB in that window has to be blended-diffused.
- Introduce artificial mempool divergence by having a fraction of nodes accept transactions from a private endpoint that others don't see. π₁ rises artificially; blended-diffusion becomes the norm.
- Withhold: an adversarial-simulation variant where a bloc of nodes deliberately don't forward tx-submission but do forward blocks. Tests the report's §7 limitation 5 (adversarial withholding).
Under any of these, DeltaQ traces then show you the actual blended-diffusion dynamics under load. The report predicts what should happen; the trace shows what does.
This is where DeltaQ trace collection is most valuable, because it can challenge the report's framing rather than just confirming its numbers.
-
Inter-connection interface contention at forwarding nodes. When node B is forwarding a 12 MB closure from A to C, the fat LeiosFetch flow to C consumes a large fraction of B's outbound interface bandwidth. B's concurrent thin flows on other connections — tx-submission to D, chain-sync to E, KeepAlive echoes to F — share the interface with the fat flow. This is a distinct mechanism from the intra-connection head-of-line blocking covered in Vote diffusion, head-of-line blocking, and Praos priority below: SDU-level round-robin keeps scheduling into the bearer fair, but bytes still couple below the mux (batching, kernel send buffer, TCP loss-recovery, reply ordering) on any single shared connection. The concern here is different again — inter-connection competition for B's shared network interface across its many connections, which degrades the thin flows only if the interface is genuinely saturated. Detecting this can go via cross-node correlation or local self-monitoring at B, using different signals:
-
Cross-node.
estBurstSis receiver-side — each of B's peers logs its ownestBurstSfor traffic from B, and a simultaneous drop across multiple peers of B during B's bulk-transfer window is the signature. -
Local at B (self-monitoring). Under NIC saturation, kernel
TCP throttles B's socket-to-D (the thin flow), the socket's
send buffer fills, and the mux write from the tx-submission
Wanton blocks — so bytes accumulate in the Wanton and
oldest-byte-age on B's per-mini-protocol egress queue grows.
A rising oldest-byte-age on B's thin-flow Wantons during B's
fat-flow window is the local signature of interface saturation
— per-mini-protocol, per-connection granularity, exactly what
we need. The dual signal is socket write-block latency:
time the mux egress thread spent blocked on a socket write
with the kernel send buffer full — the mux-visible echo of
the kernel-side congestion. Both traces are proposed in
track.md's "Measurements blocked on missing traces" (items 2 and 6) but not yet emitted; adding them would give B direct self-monitoring for this scenario, covering both sides of the stall (queue backing up, mux blocking on the socket). Complementary signals available today: aggregate outbound bandwidth from OS-level interface counters, and TCPInfo (tcpi_snd_cwnd,tcpi_retrans,tcpi_lost) which shows the kernel-side congestion state driving the mux stall.
The report models per-hop transfer time as if each connection has independent access to the wire (see
leios-report-gaps.mdgap 5 — per-node interface contention across concurrent connections is not modelled). -
Cross-node.
-
Round-to-round feedback. A round with heavy blended diffusion consumes mesh bandwidth that would otherwise carry tx-submission, which raises π₁ for the next round, which raises the blended-diffusion load again, which... The report treats rounds as independent Bernoulli trials. Time-series DeltaQ traces across many consecutive rounds show whether the mesh has this positive-feedback autocorrelation. If it does, the tail probability of consecutive-round failure is worse than the report's Bernoulli model implies.
-
α as a time-varying observable rather than a scalar. The report defers α (probability that pre-diffusion is operating normally) to a single number in the
α · P_cert,1-hopmixture, and treats it as a system-wide constant. But at each moment, per node, the observed fraction of arriving EBs whose π₁ stays below the 1-hop threshold is a direct sample of the α distribution — computable from EB arrival processing joined against local mempool state. That converts a hidden static parameter into a time series with a distribution, a tail, and cross-node covariance — the last of which is the important one: whether pre-diffusion failures hit the mesh in lockstep or independently is the difference between a globalP_certcollapse and quorum still meeting from healthier nodes, and the report's single-scalar α silently assumes the lockstep case. -
Recovery dynamics. After a pre-diffusion failure, how does the mesh recover? Do voters flag the miss and re-fetch aggressively? Does the tx-submission backlog burst-catch-up? DeltaQ traces around the failure event show the recovery signature — most importantly, whether recovery is monotone or oscillatory. Monotone recovery is safe; oscillation is a warning about latent instability.
-
Storm dynamics under concurrent EBs. Nothing forces exactly one EB per round to be certified concurrently — there could be multiple candidates propagating simultaneously. DeltaQ traces during multi-EB windows probe whether the mesh gracefully shares bandwidth or whether one EB starves the others.
-
Path-length skew. The report assumes a regular random graph. Real testnets have topology bias (regional clustering, hub nodes, degree variance). Reconstructed EB paths from cross-node correlation reveal the actual path-length distribution and whether it matches the report's assumed distribution. If typical paths are actually 4.5 hops rather than 3.6, the whole convolution shifts.
Item 2 above (round-to-round feedback) names the mechanism; this subsection names the pathological outcome and enumerates the measurements that bound it.
The failure mode. Sustained mempool arrival at or above the
level at which max-size EBs are produced imposes network cost
(EB body diffusion, closure fetch, vote diffusion, certRB
diffusion) and CPU cost (apply/reapply for validation) each
round. If certification fails at that operating point, the work
is not wasted — TxCache retains the TX bodies for reuse — but the
transactions themselves stay in the mempool, the next producer
sees a similar-or-larger backlog, and produces another max-size
EB. If the reason certification failed is a saturation in the
components TxCache does not amortize (network capacity for EB body
/ votes / certRB, per-node validation CPU, slow-node tail
dominance of
The mechanism behind this — why the work-side self-damps (mempool
backpressure plus the retained closure cache), where a genuine sustained
failure lives instead (route (a), the non-tx / voter-saturation channel,
versus the self-damping work-side route (b)), the adaptive-sizing
lever, the Praos fall-through floor and its dependence on
Praos-over-Leios mux priority, and the adversarial two-tier form — is
developed in
leios-queueing-theory.md's appendix.
This section keeps the problem statement above and the measurements that
bound it below.
Ordered from cheapest (existing traces) to most involved (induced- failure experiments).
Detecting the feedback loop. Available today or with minimal instrumentation.
-
Per-round certification outcome autocorrelation. From
cardano-tracer's EB-certification traces, compute$\mathrm{Corr}(\mathrm{fail}(R), \mathrm{fail}(R+k))$ for$k = 1, 2, \ldots$ . Positive autocorrelation is the cascade signature; the decay rate quantifies memory depth. The independent-Bernoulli null hypothesis gives$\mathrm{Corr} = 0$ . -
EB body size vs prior-round outcome. Cross-tabulate round
$R+1$ 's body size against$\mathrm{fail}(R)$ . If body size systematically grows after a failure, that's the direct feedback signature. Available today from block-production traces. - Mempool occupancy trajectory across consecutive-failure runs. Track depth and capacity: reaching capacity and staying there is the full-mempool backpressure regime (expected under sustained overload — new admissions defer and inbound tx-pulls quiesce), not by itself the runaway; the runaway question is answered by the self-damping series below, not by depth. From the mempool-measurements dataset, which already carries a static hint of the load→π₁ coupling: its us-east-2 node shows π₁ ≈ 0.44 at >85% utilisation versus ≈ 0.06 at low load — cache-miss rate (and per-round closure-fetch cost) climbs sharply with utilisation, the direction that drives the work-side load up before caching damps it.
- Self-damping vs sustained — the per-round series. The work-side signature is whether the reactive load decays. Log, per round: the closure cache-miss fraction, closure-fetch bytes split hit/miss, TxCache occupancy, and the inbound-tx-pull backpressure flag. A miss fraction falling round over round while pulls are backpressured off is the benign self-damping stall; one that stays flat or climbs is a non-self-damping channel — disjoint-tx cache-locality erosion, or a route-(a) failure caching cannot touch — which is the case that actually sustains. It is also what distinguishes a decaying failure-autocorrelation (self-damping) from a persistent one (route (a)).
Quantifying TxCache's role. Splits the retry-cost savings attributable to TxCache from other sources.
-
Per-node TxCache hit rate, split by "TX belongs to a failed
EB from round
$R-k$ " vs "TX arrived via ordinary tx-submission". Reveals what fraction of retry savings is TxCache's contribution. - Closure size on retry. For consecutive EBs sharing overlapping TX sets, measure the actual closure-fetch bytes pulled at each node. Reduction vs full closure is TxCache's realised savings — per node, per round, per overlap fraction.
-
CPU cost of apply/reapply on retry, split by cache-hit status.
If TxCache caches validation state, retry cost should drop
sharply; if only TX bodies, revalidation cost is largely
unchanged. Uses existing
post-cip/apply-reapplyinstrumentation.
Finding the collapse threshold and recovery signature. Controlled experiments that induce the failure mode.
- Sustained-load-to-collapse sweep. Increase tx-submission arrival rate until certification failure rate crosses a threshold (e.g., >5% consecutive failures over a rolling window). Drop the arrival rate back to baseline and measure whether certification recovers. If certification stays stuck after load drops → livelock confirmed. Report the smallest load level above which recovery does not occur (the "livelock onset" boundary).
-
Induced-failure recovery experiment. Force a round to fail —
withhold votes at a controlled slice of the committee, or
inject a controlled network shock via
tc netem— then observe recovery: how many rounds until certification returns to baseline, and whether the trajectory is monotone or oscillatory. Signatures per item 4 above. This also separates the two adversarial tiers: hold a producer subset at max body size and confirm honest rounds still recover (cheap, self-healing tier); add continuous saturation of a committee slice (tc netem) plus withheld votes and confirm recovery does not occur (expensive tier) — the livelock-onset boundary should appear only with the continuous channel present. -
Time-to-recovery distribution. Across many induced failures
at different load levels, measure
$\Pr(\text{recovery within } k \text{ rounds})$ as a function of load. A regime where recovery time diverges beyond the tx-arrival timescale is where livelock lives. -
EB-issuance-rate (production-gap) sweep. Vary the EB rate — or
inject synthetic long gaps — while holding a producer subset at max
body size (the work-side kick), and measure recovery probability
against rate. Per
threat-model-additions.md's T42, a sparse (Praos-cadence) rate should let the work-side collapse heal across the next long interblock gap with no lever, whereas a dense rate makes healing gaps rare and the collapse persists — turning the EB-density dependency from a stated open question into a measured onset curve. Run it with the continuous voter-saturation channel off (the gap heals) and on (it does not) to separate the work-side route from the schedule-independent one.
Tuning the stabilizing lever. Once producers use adaptive body sizing, calibrate its response.
-
Adaptive body sizing calibration. At the collapse threshold,
how much body-size reduction restores certification? Report
the operating curve
$S_{EB\text{-tx}}(\mathrm{load})$ that keeps the certification success rate above a target. -
Adaptive-sizing loop stability. When producers throttle,
next-round body size shrinks, certification recovers, mempool
grows, next-round producer sees lower degradation, body size
grows again. Whether this loop converges to a fixed point or
oscillates is the open question flagged in
leios-conditional-diffusion.md's follow-ups. Measure the loop-gain from perturbation experiments. - Voter self-throttle activation rate. How often does the self-throttle fire, and does the fire rate correlate with load? A throttle firing so often that certification never happens is a different failure mode (throttle-induced stall) but it's how the mesh avoids the livelock. Measurable from voter-participation traces once the runtime lever is enabled.
Cross-referenced signals from other measurements.
- Per-EB arrival log with certification outcome — the same log
powering
$\hat{F}_{\text{full}\mid C}(14)$ (perleios-priority-measurement.md). -
estBurstSandPeerRTTdegradation cross-referenced against the failure-autocorrelation series — links the transport-layer signal to the round-outcome signal. - Interface-saturation self-monitoring (oldest-byte-age and
socket-write-block latency, per
track.mditems 2 and 6) during heavy-round windows — signals whether the network layer is the binding constraint.
Traces for cascade replay. The analyses above are point measurements, mostly from existing datasets; replaying an episode — re-running it under a counterfactual TxCache size, EB-issuance rate, lever setting, or mux priority — needs the cascade's inputs logged per node per round: the same "log the inputs, not the output" principle as Traces for offline study, but for the cascade rather than the belief. One record per node per round (heavy — sample it, or enable it only during induced-failure runs), on the shared monotonic clock with a slot/round ref:
- mempool — occupancy vs capacity, and a tx-id-set digest (rolling hash or bloom) so cross-node fragmentation is reconstructable;
- tx-submission — the inbound-pull backpressure flag and outbound serve activity (the mechanism: pulling quiesces on a full mempool, serving does not);
- the EB seen or produced — its referenced tx-id set (or digest) and advertised size (feeds the disjoint-tx / overlap analysis);
-
closure-fetch — bytes and tx count split cache-hit/miss,
T_wait, andarrived_from_peer; - TxCache — occupancy, hit/miss counts, and evictions (the bounded-cache gap);
- the deadline budget — time consumed by EB-body diffusion, closure-fetch, apply/reapply, vote collection, and certRB diffusion, each against the round deadline, so a failure attributes to the work-side or to a route-(a) component;
- the base chain (RB) — whether the round's ranking block carried a certificate or fell through to carrying txs directly, and in the fall-through case the tx count / bytes it drained (the Praos floor engaging, and at what rate — distinguishes healed at the floor from above the floor);
- base-chain diffusion vs the Praos bound — the RB's own diffusion time (cert- or tx-carrying) against Praos's ~5 s Δ, so a crash through the floor — the base chain itself missing its bound — is visible, not just a Leios-layer failure;
- egress contention — the shared bearer's state (queue depth / oldest-byte-age / socket-write-block latency, per the cross-referenced track.md items, but recorded per round) and the RB-vs-closure sharing on it, so a delayed RB attributes to self-inflicted bufferbloat (Leios closures crowding the base-chain block) rather than external degradation;
- outcome — certified / failed + mode; for a voter, voted / missed-deadline / withheld.
With this, an episode replays as a simulation seeded by the logged arrivals, EB contents, and topology: it confirms the mechanism (self-damping versus a sustained channel), attributes the binding constraint, separates a collapse that healed at the Praos floor from one that crashed through it, and answers the counterfactuals — would a larger cache, a sparser EB schedule, the lever, or Praos-over-Leios mux priority (WFQ / a separate bearer) have changed the outcome — that the point analyses alone cannot.
-
leios-report-gaps.mdgap 4 — the modelling gap this analysis is a downstream consumer of. - Item 2 (round-to-round feedback) and item 4 (recovery dynamics) above — the surrounding dynamics this section makes concrete.
-
leios-conditional-diffusion.mdlever 2 — the actual stabilizing lever this section relies on. -
leios-overlap.md's "Where the fit is not clean" — treats TxCache as a scalar π₁, which is the framing this section extends into dynamic-stability terms. -
leios-queueing-theory.md— reads this whole failure mode through queueing/performance theory (non-work-conservation, metastability, the reflecting Praos floor, regeneration via production gaps, order-statistic fragility), giving the narrative here its structural underpinning.
If this is your goal, the testnet setup differs slightly from the plain validation campaign above:
- Explicit forcing knob: a scripted way to enable/disable tx-submission per node. Cardano-node already supports peer-filtering hooks that could do this.
-
Per-EB arrival log: augment
cardano-traceroutput to include(node, EB_id, arrival_ts, forward_target_ts)records. Not currently emitted per-EB but the trace event exists at block-diffusion level. - Round-cadenced captures: raw DeltaQ traces on measurement nodes during a specific window around a forced-blending event, not just steady-state sampling. Higher temporal resolution during the interesting events, aggregated the rest of the time.
- Multi-round runs: at least 100 rounds under each of {full pre-diffusion, forced-blended, adversarial-withholding} to get tail estimates. The report's rare-event probabilities (14%, 31%) need substantial sample sizes to validate.
-
Tail estimation. The report's most stressed predictions are rare-event certification failures. A week-long testnet with ~100 nodes at 1 EB/20 s gives ~30 k certified EBs; with zero failures observed that bounds the failure rate at
$\lesssim 10^{-4}$ (rule of three), so the campaign resolves$F_{\text{full}\mid C}$ to ~0.9999. Rarer events (a$10^{-5}$ failure rate is ~0.3 expected failures/week) stay paper-only unless you run longer or with more nodes — and failure clustering (gap 4) shrinks the effective sample further. -
Testnet vs mainnet mesh topology. Even if you match the number of nodes and regions, real SPO topology has network-of-network effects (relays behind stake pools, edge nodes on residential uplinks, community relays), that a controlled testnet doesn't reproduce. The blended-diffusion path distribution on mainnet may look quite different from a synthetic mesh. This is the same caveat as before but bites harder for blended-diffusion because path-length skew propagates through the convolution multiplicatively.
DeltaQ traces give you (a) the single-hop CDF inputs to the report's convolution directly, (b) with modest additional per-EB arrival logging, the end-to-end multi-hop composed behavior, and (c) — most interestingly — exposure to dynamics the report doesn't model at all: inter-protocol contention, round-to-round feedback, recovery signatures. To exercise blended diffusion specifically, you also need a way to force pre-diffusion to fail on the testnet. The report's static-scalar α becomes a measurable time series once you're collecting traces this way, which is a meaningful qualitative upgrade to the analysis.
The validation campaign as described logs EB body arrivals and
certification outcomes but not votes, and the report models no vote
traffic at all (votes enter only through the sortition math). That
leaves a hole: when certification fails or lags on the testnet, we
cannot attribute it — was the EB body late to voters, were the votes
late back to the certifying producer, or were votes stuck behind bulk
on a shared bearer? This section adds the vote-path measurement and
the intra-connection head-of-line story it depends on. It is the
measurement counterpart to
leios-report-gaps.md gap 17.
The 7 s voter deadline (3·L_hdr + L_vote) and the 450-of-600 quorum
are a round-trip budget: the EB body must reach voters, they must
validate and vote, and 450 votes must reach the certifying producer —
all inside the window. Yves's MsgLeiosVotesRequest / MsgLeiosVoteDelivery, small and
latency-critical — and they share bearers with the 12 MB closure
flows. Add per-vote arrival logging: (election_id, voter, node, ts)
from the LeiosNotify/LeiosFetch trace stream, so the vote-diffusion
CDF becomes a first-class measured object alongside the body CDF.
Cheap (votes are small, the trace points exist), and it closes the
other half of the certification-timing budget so a failed round is
attributable rather than merely counted.
The mux's SDU-level round-robin is often summarised as "mini-protocols can't block each other on a connection". That is true only of scheduling into the bearer. Below and after that point, four couplings still head-of-line block latency-critical traffic (votes, Praos headers) behind a 12 MB closure on a shared connection:
-
Egress batching.
buildBatchsubmits up tobatchSize(128 kB, or 100 SDUs) perwriteMany; an SDU arriving mid-batch waits behind the whole write. Within a batch protocols interleave fairly — the SDU that didn't fit is the one that waits — and the batch size can be tuned down if this dwell is material. -
Socket send-buffer FIFO. The egress thread issues a blocking
sendAll/sendMany; with kernelwmemautotuned to MB scale on high-BDP paths, a small SDU written "fairly" still sits behind megabytes of already-queued closure bytes, draining at path rate (hundreds of ms to seconds).TCP_NOTSENT_LOWAT(Linux/Darwin, precedented — Nick's mininet test-bed sets it at 512 kB) caps the not-yet-sent backlog the kernel accepts before blocking the writer, cutting the dwell from "wmem ÷ path rate" toward "≈ one BDP ÷ path rate". Costs: the egress thread wakes more often, and the cap must stay ≳ BDP to keep the pipe full. Worth exposing as a bearer option (default off, recommended for Leios bulk connections). -
TCP loss-recovery. In-order delivery stalls every
mini-protocol on the connection for ~RTT (fast retransmit) up to an
RTO on a single loss. A 12 MB closure is ~8,600 packets; at
p = 1e-4 that is ~0.9 expected loss events per transfer (~9 at the
1e-3 evening-peak rate), so roughly one such stall per closure per
connection at baseline loss. Crucial nuance: two LeiosFetch
instances on the same connection (below) escape reply-ordering
HOL but not this coupling — only a separate connection does.
NOTSENT_LOWATdoesn't help here either; it's a delivery-side, not a queue-ahead, stall. - Intra-protocol reply ordering (the one the CIP flags). typed-protocols' granular states force a LeiosFetch server to reply in request order, so a vote reply queued behind a 12 MB closure reply in the same LeiosFetch instance is head-of-line blocked — "threatening freshest-first delivery or even motivating inflations of L_vote and/or L_diff" (CIP-0164:1436-1439).
Congestion control is orthogonal to most of this. A recurring hope is that switching the bulk flow's controller (CUBIC → BBR) relieves the couplings. On a shared bearer it mostly does not: CC governs the rate the socket drains, not the order of bytes within one sequence space, so it cannot let a vote jump ahead of closure bytes the mux already queued in front of it. Couplings 1, 2 and 4 are byte-ordering effects, untouched by the controller. CC touches only coupling 3, and only indirectly through loss/retransmit frequency: on a non-congestive-loss path BBR drains the closure bytes ahead of a vote faster and stalls in-order delivery less often — a latency gain, but via the throughput mechanism, not queue discipline — while on a congestive bottleneck the advantage erodes or inverts: BBR's overshoot (STARTUP dominates for these short transfers) is loss-blind (v1; v3 less so), so it can sustain loss rather than backing off as CUBIC would, and the extra loss feeds coupling 3's stalls. The bufferbloat resistance that makes BBR a good latency neighbour is a cross-flow effect: it needs the vote traffic on a separate flow to bite (see connection separation), not a shared bearer.
The CIP's own suggested fix for coupling (4) (CIP-0164:1439-1443): "run two instances of LeiosFetch and reserve one for requests that are small and urgent (e.g. small blocks, a few missing transactions, or perhaps any vote); the existing infrastructure would naturally interleave those with the larger and/or less urgent requests." That last clause is the mux — the SDU round-robin (WFQ on the perf branch, below) interleaves the two instances so the small/urgent instance's SDUs are never stuck behind the bulk instance's replies. This is a protocol-structure problem whose solution is a mux-scheduling arrangement, so it sits squarely in this repo's scope.
The experiment (needs the vote-path log above): deploy both arrangements — single-instance LeiosFetch vs two-instance — and measure vote-arrival latency at the certifying producer under heavy closure load. Prediction: single-instance shows vote replies tailing behind 12 MB closure replies (a bimodal vote-latency distribution, the slow mode aligned with concurrent closure fetches); two-instance flattens it. A clean, self-contained validation of a CIP recommendation using this branch's machinery. Note the split fixes reply-ordering (4) but not loss-coupling (3) — for that the urgent instance must be on a separate connection.
Scheduling between the two instances (and between Leios and Praos
generally) is what an open draft PR (branch
origin/mw/mux-single-peer-performance) adds to the mux: weighted fair
queuing over egress
queues, plus a per-protocol burst token bucket. WFQ's
proportional-share semantics maps directly onto the CIP's tolerant
("does not need to be perfectly strict", CIP-0164:1187-1209)
Praos-over-Leios priority — strict priority would risk starving Leios
entirely; weights don't. The full priority stack is therefore:
WFQ (scheduling between protocols/instances) + two-instance LeiosFetch
or experimental typed-protocols server-side reordering (reply
ordering) + NOTSENT_LOWAT (kernel buffer, coupling 2) + connection
separation for the most latency-critical traffic (loss coupling 3).
One open design question for WFQ: the certified-EB urgency inversion
(a certified EB becomes as urgent as the RB it blocks,
CIP-0164:1193-1198) makes the right weight time-varying, so either
weights must be runtime-adjustable per protocol instance, or urgent
certified-EB fetches go on the high-weight instance. See
leios-report-gaps.md gap 17
for the gap framing; none of these pieces is merged to main yet.
This weighting is part of what the sustained-overload Praos fall-through
floor needs: on the default fair round-robin a large closure bufferbloats
the egress and can delay the fallback base-chain block meant to floor a
livelock at Praos, so Leios overload can degrade the base chain rather than
merely degrade to it. WFQ schedules the base-chain SDUs ahead, but cannot
undo a closure already ahead in the same connection's TCP sequence space —
that residual needs the connection separation below (or QUIC). Until both
land, the floor is not guaranteed — see the Praos floor in
leios-queueing-theory.md's appendix.
Of the four couplings, only #3 — TCP loss-recovery, where in-order
delivery stalls every mini-protocol on the bearer until a lost
segment is retransmitted — survives every same-connection mitigation.
WFQ, two-instance LeiosFetch, and NOTSENT_LOWAT all act above one
TCP sequence space, so a loss still stalls everything sharing it.
Escaping #3 means putting the smallest, most latency-critical traffic
— votes above all, arguably Praos headers — on a separate channel
with its own sequence space. This is the heaviest hammer in the stack;
reserve it for traffic where a loss-induced stall of the bulk flow
would blow a hard deadline (votes race the 7 s certification budget).
Three ways to separate, in increasing divergence from today's model:
- A second TCP bearer per peer — small/urgent protocols on one socket, bulk LeiosFetch on another. Fully escapes #3 (the urgent socket has its own sequence space, so a closure retransmit can't stall a vote) while keeping TCP's reliability, congestion control, and the mux model unchanged. Cost: a second connection's worth of kernel state, handshake, versioning, and teardown per peer, and the two flows compete at the bottleneck as independent TCP flows (each with its own cwnd — fine here, since the urgent flow is tiny and wins its share easily unless the NIC is genuinely saturated).
-
UDP for the urgent traffic. Votes are an unusually good fit:
each is a single sub-MTU datagram (a BLS sig plus a few IDs, well
under 1500 B), self-authenticating (signed — forgery is caught), and
gossiped by fan-out where some loss is tolerable (you need 450 of
600; a datagram dropped to one peer is recoverable from another, or
by re-request). UDP then buys exactly what we want: no head-of-line
blocking (each datagram independent — a lost one never stalls the
next), no per-message connection state, no handshake, no send-buffer
bufferbloat behind bulk. The complications bite hardest here:
- No network-usage bounding. TCP self-limits via cwnd; UDP does not. We would have to add application-level pacing / rate-limiting, and getting it wrong feeds the very congestion the bulk traffic already stresses (or self-DoS). Unbounded UDP is also a spoofing / reflection surface — signed votes catch forged content, but processing spoofed datagrams (verify cost) is itself a DoS vector needing source validation and rate caps.
- No reliability or ordering. "Did I get the votes I need?" becomes application-level accounting, not a TCP guarantee. Votes tolerate this (quorum + redundant senders); Praos headers do not (you need the header to fetch the block), so headers want a reliable separate channel, not raw UDP.
- NAT / firewall traversal. Many SPO deployments permit only the established TCP node-to-node port; a UDP side-channel needs its own firewall rules and hole-punching — a network-wide operational change touching multi-implementation consistency (gap 13) and the operational-vs-spec boundary (gap 16).
-
Outside the mux telemetry. DeltaQ / cookie-RTT /
estBurstSride the TCP bearer; a UDP channel is invisible to them, so the very signals this campaign is built on wouldn't cover the urgent path — parallel instrumentation needed.
- QUIC — the principled middle ground: independent streams within one UDP-based connection, so a lost packet stalls only its stream (escapes #3 like separate connections) but with built-in congestion control (userspace, per-connection — CUBIC/BBR selectable, not fixed; one controller per connection, not per-stream), reliability, and encryption (unlike raw UDP) over a single connection (unlike a second TCP bearer). One residual: the shared cwnd means QUIC escapes the delivery-order stall (#3) but not a connection-wide rate coupling — a loss cuts the whole connection's window, and bulk saturating it can briefly (~1 RTT) delay an urgent send. But that is soft and priority-schedulable (the scheduler hands the window to the urgent stream; bulk absorbs the cut; leaving cwnd headroom mitigates it) — a mild #2, not the hard #3. Cost: adopting QUIC in a stack whose mux is TCP today — a large change — but it is the option that solves the head-of-line coupling without the raw-UDP bounding problem or the two-connection overhead. But being UDP-based it inherits raw UDP's reachability caveat: a firewall permitting only the TCP node port blocks QUIC too. It handles NAT-binding maintenance better than raw UDP (built-in keep-alives, connection migration), but the binary "is UDP allowed at all" it cannot escape. In Cardano's permissionless operator base that reachability dependency — on thousands of operators' firewalls no one controls — is arguably its biggest weakness, bigger than the one-time engineering cost. So QUIC needs a TCP fallback (as every HTTP/3 deployment ships), and its HOL/priority wins land only on UDP-reachable paths.
Three caveats apply to any separation:
- It gives up the single-bearer streaming properties. One TCP bearer per peer is a single ordered, reliable, congestion-controlled stream with unified backpressure and one telemetry point. Splitting the traffic surrenders cross-protocol ordering (usually harmless — the mini-protocols are independent — but no longer guaranteed), the single congestion-control loop (now two competing cwnds, or CC we implement ourselves over UDP), unified backpressure (two domains), and shared-fate simplicity (partial failure — urgent up, bulk down, or vice versa — is a new state to handle). The mux's per-protocol fairness and telemetry must be replicated on the second channel.
- It escapes the delivery-stall coupling, not interface contention. Separation removes coupling #3; it does not remove inter-connection NIC competition (gap 5) — the urgent flow still shares the interface with the bulk flow. Being tiny it wins its share easily unless the NIC is truly saturated, but "separate connection" is not "separate bandwidth".
- It surrenders the mux's bandwidth arbitration, inverting Praos-over-Leios. On one bearer the mux scheduler (WFQ) can weight Praos above Leios; split the bulk flow off and the egress bottleneck arbitrates the two connections by priority-blind per-flow TCP fairness, so the single Leios closure fetch becomes co-equal (~50%) with the entire Praos suite on the main bearer — worse under fan-out, where per-flow fairness rewards Leios's many concurrent closure-serving flows. BBR compounds it (its design is to not defer — the opposite of the requirement). Restoring the priority needs an external scheduler: local egress qdisc priority-classing (moving the guarantee from the in-process mux to per-node OS config — gaps 13/16), or a scavenger CC (LEDBAT) that yields but sacrifices Leios throughput. QUIC is the exception — one cwnd plus internal stream priority keeps Praos>Leios without splitting into competing flows.
Once separated, the bulk flow's congestion control becomes a latency
lever for the urgent flow. They now share the bottleneck as
independent flows, so the bulk controller's queueing discipline is felt
by the urgent flow. A CUBIC bulk flow fills a deep buffer until loss
(bufferbloat), inflating the standing queue the vote flow must traverse;
BBRv3 caps its inflight near one BDP and periodically drains, holding a
shallower queue — so BBR bulk is a better latency neighbour than
CUBIC. Two precisions keep this honest: BBR bounds the queue by model
(inflight ≈ BDP, windowed min-RTT filter, periodic ProbeRTT), not by
throttling on observed RTT rise, so it reduces rather than eliminates
the standing queue; and its help scales with the BBR-managed share of
the bottleneck load — it cannot undo a third flow's bufferbloat, so
scheduling (fq/AQM/priority-classing the vote flow) stays the robust
isolation tool, BBR complementary to it. That share is favourable here,
though: EB closures are the dominant bulk on the connection and are our
own egress, so putting them on BBR manages most of what would bloat the
buffer — the latency benefit largely lands even without AQM tuning. This
is the flip
side of the shared-bearer note in
the intra-connection couplings:
CC is orthogonal to head-of-line blocking on one socket, but a real
lever once the flows are separated.
Net: for votes — tiny, signed, loss-tolerant, deadline-critical — a separate channel is well-motivated, and QUIC streams (or, more conservatively, a second TCP bearer) are the sound realizations; raw UDP is tempting for its zero-HoL simplicity but reintroduces the congestion-control and operational problems that TCP and QUIC give for free. It is the last resort in the priority stack — warranted only where the bulk bearer's loss-recovery stall is what actually threatens the deadline.