Home / Input Output / ouroboros-leios
May 29, 11-12 PM (0)
May 29, 12-1 PM (1)
May 29, 1-2 PM (0)
May 29, 2-3 PM (1)
May 29, 3-4 PM (0)
May 29, 4-5 PM (0)
May 29, 5-6 PM (0)
May 29, 6-7 PM (0)
May 29, 7-8 PM (0)
May 29, 8-9 PM (0)
May 29, 9-10 PM (0)
May 29, 10-11 PM (1)
May 29, 11-12 AM (0)
May 30, 12-1 AM (0)
May 30, 1-2 AM (0)
May 30, 2-3 AM (0)
May 30, 3-4 AM (0)
May 30, 4-5 AM (0)
May 30, 5-6 AM (0)
May 30, 6-7 AM (0)
May 30, 7-8 AM (0)
May 30, 8-9 AM (0)
May 30, 9-10 AM (0)
May 30, 10-11 AM (0)
May 30, 11-12 PM (0)
May 30, 12-1 PM (0)
May 30, 1-2 PM (0)
May 30, 2-3 PM (0)
May 30, 3-4 PM (0)
May 30, 4-5 PM (0)
May 30, 5-6 PM (0)
May 30, 6-7 PM (0)
May 30, 7-8 PM (0)
May 30, 8-9 PM (0)
May 30, 9-10 PM (0)
May 30, 10-11 PM (0)
May 30, 11-12 AM (0)
May 31, 12-1 AM (0)
May 31, 1-2 AM (0)
May 31, 2-3 AM (0)
May 31, 3-4 AM (0)
May 31, 4-5 AM (0)
May 31, 5-6 AM (0)
May 31, 6-7 AM (0)
May 31, 7-8 AM (0)
May 31, 8-9 AM (0)
May 31, 9-10 AM (0)
May 31, 10-11 AM (0)
May 31, 11-12 PM (0)
May 31, 12-1 PM (0)
May 31, 1-2 PM (0)
May 31, 2-3 PM (0)
May 31, 3-4 PM (0)
May 31, 4-5 PM (0)
May 31, 5-6 PM (0)
May 31, 6-7 PM (0)
May 31, 7-8 PM (0)
May 31, 8-9 PM (0)
May 31, 9-10 PM (0)
May 31, 10-11 PM (0)
May 31, 11-12 AM (0)
Jun 01, 12-1 AM (1)
Jun 01, 1-2 AM (1)
Jun 01, 2-3 AM (0)
Jun 01, 3-4 AM (0)
Jun 01, 4-5 AM (0)
Jun 01, 5-6 AM (0)
Jun 01, 6-7 AM (2)
Jun 01, 7-8 AM (0)
Jun 01, 8-9 AM (0)
Jun 01, 9-10 AM (0)
Jun 01, 10-11 AM (0)
Jun 01, 11-12 PM (1)
Jun 01, 12-1 PM (0)
Jun 01, 1-2 PM (0)
Jun 01, 2-3 PM (2)
Jun 01, 3-4 PM (0)
Jun 01, 4-5 PM (0)
Jun 01, 5-6 PM (0)
Jun 01, 6-7 PM (0)
Jun 01, 7-8 PM (0)
Jun 01, 8-9 PM (0)
Jun 01, 9-10 PM (0)
Jun 01, 10-11 PM (1)
Jun 01, 11-12 AM (0)
Jun 02, 12-1 AM (0)
Jun 02, 1-2 AM (0)
Jun 02, 2-3 AM (0)
Jun 02, 3-4 AM (0)
Jun 02, 4-5 AM (0)
Jun 02, 5-6 AM (35)
Jun 02, 6-7 AM (0)
Jun 02, 7-8 AM (0)
Jun 02, 8-9 AM (0)
Jun 02, 9-10 AM (0)
Jun 02, 10-11 AM (1)
Jun 02, 11-12 PM (0)
Jun 02, 12-1 PM (2)
Jun 02, 1-2 PM (0)
Jun 02, 2-3 PM (0)
Jun 02, 3-4 PM (0)
Jun 02, 4-5 PM (0)
Jun 02, 5-6 PM (0)
Jun 02, 6-7 PM (0)
Jun 02, 7-8 PM (0)
Jun 02, 8-9 PM (0)
Jun 02, 9-10 PM (0)
Jun 02, 10-11 PM (0)
Jun 02, 11-12 AM (0)
Jun 03, 12-1 AM (0)
Jun 03, 1-2 AM (1)
Jun 03, 2-3 AM (0)
Jun 03, 3-4 AM (0)
Jun 03, 4-5 AM (0)
Jun 03, 5-6 AM (1)
Jun 03, 6-7 AM (0)
Jun 03, 7-8 AM (1)
Jun 03, 8-9 AM (1)
Jun 03, 9-10 AM (2)
Jun 03, 10-11 AM (1)
Jun 03, 11-12 PM (0)
Jun 03, 12-1 PM (0)
Jun 03, 1-2 PM (0)
Jun 03, 2-3 PM (3)
Jun 03, 3-4 PM (1)
Jun 03, 4-5 PM (1)
Jun 03, 5-6 PM (0)
Jun 03, 6-7 PM (0)
Jun 03, 7-8 PM (0)
Jun 03, 8-9 PM (0)
Jun 03, 9-10 PM (0)
Jun 03, 10-11 PM (0)
Jun 03, 11-12 AM (0)
Jun 04, 12-1 AM (0)
Jun 04, 1-2 AM (0)
Jun 04, 2-3 AM (0)
Jun 04, 3-4 AM (0)
Jun 04, 4-5 AM (0)
Jun 04, 5-6 AM (0)
Jun 04, 6-7 AM (0)
Jun 04, 7-8 AM (0)
Jun 04, 8-9 AM (0)
Jun 04, 9-10 AM (0)
Jun 04, 10-11 AM (0)
Jun 04, 11-12 PM (0)
Jun 04, 12-1 PM (0)
Jun 04, 1-2 PM (0)
Jun 04, 2-3 PM (0)
Jun 04, 3-4 PM (0)
Jun 04, 4-5 PM (0)
Jun 04, 5-6 PM (0)
Jun 04, 6-7 PM (0)
Jun 04, 7-8 PM (0)
Jun 04, 8-9 PM (0)
Jun 04, 9-10 PM (0)
Jun 04, 10-11 PM (0)
Jun 04, 11-12 AM (0)
Jun 05, 12-1 AM (0)
Jun 05, 1-2 AM (0)
Jun 05, 2-3 AM (0)
Jun 05, 3-4 AM (0)
Jun 05, 4-5 AM (0)
Jun 05, 5-6 AM (0)
Jun 05, 6-7 AM (0)
Jun 05, 7-8 AM (0)
Jun 05, 8-9 AM (0)
Jun 05, 9-10 AM (0)
Jun 05, 10-11 AM (0)
Jun 05, 11-12 PM (0)
61 commits this week May 29, 2026 - Jun 05, 2026
coordinator: throttle re-intersection per address to stop fork-loop spin
A peer stuck on an unreconcilable fork re-intersects in a tight loop
(the original relay wedge spun at ~1.4 orphan/re-intersect per second).
The per-peer-id orphan cooldown in shared-consensus doesn't survive the
reconnect handovers that assign a fresh PeerId each time.

Rate-limit `NetworkCommand::ReIntersect` in the coordinator keyed by peer
*address* (stable across reconnects) with exponential backoff (1s → 30s).
All re-intersections — within a connection and across reconnect handovers
— flow through this command, so one throttle covers both. The first
attempt for an address always passes (legitimate single re-intersects are
never blocked); rapid repeats back off; a peer that goes quiet resets.

Measured on the round-robin divergent-backend repro: orphan/re-intersect
events dropped from ~26/90s (spin) to 3, and the follower makes progress
instead of freezing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
praos: trust the ChainSync intersection anchor as the common ancestor
After reconnecting to a peer on a divergent fork (the round-robin
relay-backend handover), select_chain could perpetually fail to find a
common ancestor: the per-peer fragment is too short to reach the fork
point and `adopted_ancestors` may be truncated by pruning, so the
strictly-better peer is classified OrphanCandidate forever and the tip
freezes (the original relay wedge: "orphan — no common ancestor found",
looping on re-intersection).

`find_intersection` already probes back to genesis, so its anchor is the
authoritative common ancestor. Add a final fallback in select_chain: when
no ancestor is found via the fragment, trust the peer's intersection
anchor if it is a real block we hold and the resulting reorg is within k.
The bounded (<= k) reorg then proceeds via the existing
WaitingForBlocks/range-fetch path. Reorgs deeper than k are refused
(Praos finality — settled blocks). Origin/genesis anchors are excluded:
a switch sharing only genesis needs a full re-sync from block 1 that the
range fetch can't anchor at Origin, and isn't the round-robin case
(those backends share real history at the fork point).

Tests: select_chain_trusts_intersection_anchor_within_k (verified to fail
without the fallback) and select_chain_refuses_reorg_deeper_than_k. 308
shared-consensus tests pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
behaviour: add DropInboundPeers — server-side connection-reset chaos
Adds a config-driven behaviour that randomly resets accepted (inbound)
peer connections, so the remote reconnects and re-runs ChainSync
intersection from scratch. Mimics a relay that RSTs inbound peers — the
reconnect-handover trigger for deep-rollback recovery testing. Compose
with DeepReorg so each reconnect re-intersects against a reorged chain.

- shared-consensus:
  - Behaviour::drop_inbound_peers(slot) -> bool hook (default false;
    composite ORs children); LeiosState::ask_drop_inbound_peers.
  - BehaviourSpec::DropInboundPeers { probability } + registry wiring +
    behaviours::DropInboundPeers (deterministic per-(seed, slot) draw).
- net-core: NetworkCommand::DropInboundPeers; coordinator resets every
  accepted peer (ip_guard.is_some()) via PeerCommand::Disconnect (relies
  on the mux-teardown fix to close the socket promptly).
- net-node: Consensus::should_drop_inbound_peers; the slot loop issues
  the command when the behaviour fires.

Config: `[behaviour] kind="drop-inbound-peers", probability=P`.

Verified live (producer with DeepReorg + DropInboundPeers): the follower
disconnects promptly (mux error, not 60s keepalive) and reconnects each
drop, re-intersecting against the reorged chain.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
mux: RunningMux::abort must cancel the muxer/demuxer, not just the supervisor
`RunningMux::abort()` only aborted the supervisor task. The supervisor
merely *watches* the muxer (egress/writer) and demuxer (ingress/reader)
and aborts the survivor when one fails — it doesn't own the bearer. So
aborting only the supervisor left the muxer and demuxer running, holding
the TCP read/write halves open: the peer never saw EOF and only
disconnected via the 60s keepalive timeout.

Store the muxer/demuxer AbortHandles in RunningMux and cancel all three
in abort(), so tearing a connection down closes the socket promptly and
the remote observes the disconnect immediately. Surfaced while building
a server-side peer-reset behaviour, but affects every abort-driven
teardown (peer Disconnect, shutdown, supervisor cleanup).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
behaviour: add DeepReorg — deliberate deep self-reorg for chaos testing
Adds a config-driven producer-side behaviour that periodically abandons
a chain suffix and forks, so downstream followers must recover from a
deep RollBackward. Reusable as deep-reorg / reorg-resilience tooling
(and the harness for the deep-rollback recovery work).

- shared-consensus:
  - ChainTree::remove_above(block_number) — drop an abandoned suffix and
    recompute best_tip.
  - PraosState::force_rollback(depth) — re-anchor the adopted chain
    `depth` blocks back, prune the suffix, emit InjectRollback so the
    served chain mirrors it. No-op below depth/with no tip. Unit-tested.
  - Behaviour::praos_reorg(slot) -> Option<depth> hook (default None;
    composite returns first Some); LeiosState::ask_praos_reorg consults it.
  - BehaviourSpec::DeepReorg { every_slots, depth } + registry wiring +
    behaviours::DeepReorg (fires once per `every_slots`-aligned slot).

- net-node: PraosConsensus::force_rollback diffuses the rollback;
  Consensus::maybe_force_reorg consults the behaviour each slot; the main
  slot loop calls it before production so the producer forks.

Config: `[behaviour] kind="deep-reorg", every_slots=N, depth=D`.

303 shared-consensus tests pass. Verified live (producer+follower): the
behaviour fires and forks; an honest follower recovers cleanly from
single-producer deep reorgs (the relay wedge needs additional
conditions — multi-peer/reconnect — still under investigation).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
praos: heal non-contiguous candidate chains instead of looping on orphan
A passive follower could wedge permanently once at the live tip: after a
deep rollback the peer re-announces its chain, but our cached copy has a
gap (the peer fragment skipped a header, and that gap block was never
fetched because it never appeared in `missing` — it isn't in the
fragment).  `select_chain` then classified the strictly-better candidate
as a non-contiguous / fork-mismatch orphan, cleared the fragment, and
requested re-intersection — which the peer answers by rolling back to our
tip and re-announcing, looping ~1.4x/s with the tip frozen.

Primary fix (gap-fill): when a strictly-better candidate's cached chain
is non-contiguous, fetch the contiguous range [common ancestor ->
candidate tip] from the announcing peer instead of giving up.  A range
request needs only the two endpoints; the peer streams every intermediate
block, healing the gap so a later pass switches.  Genesis-rooted
ancestors still fall back to the orphan verdict (can't anchor a range at
Origin).

Secondary safety net (fork-tip prune): the periodic gap-bridge could also
fixate on an abandoned fork tip left far ahead in chain_tree after the
rollback — no connected peer offers it, so the bridge re-issued a peerless
(peer_count=0) no-op fetch forever.  `ChainTree::remove_fork_tip` drops
such an unreachable best_tip and recomputes best_tip; retry_select_chain
prunes rather than emit a peerless fetch.

Tests: select_chain_heals_noncontiguous_cached_candidate_via_range_fetch
and retry_prunes_unreachable_best_tip_instead_of_peerless_fetch (both
verified to fail without the respective change).  300 pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
praos: fetch only the frontier gap during catch-up
A follower far behind a peer re-fetched the entire not-yet-validated
backlog on every ChainSync roll-forward: `issue_fetch_internal` built a
range `[anchor .. missing.last()]` and deduped only on the `to` endpoint,
while `in_flight` recorded only that single endpoint. Each new tip
therefore re-issued an overlapping range, so blocks_received grew ~30x
the chain length and block bandwidth climbed super-linearly during deep
catch-up.

Track BlockFetch ranges per block in `in_flight` (matching the per-block
removal already done in on_block_received), filter `missing` against it
so only the frontier gap is requested, and start the range at the first
still-needed block when an in-flight prefix was filtered. on_block_fetch_
failed now clears the whole [from,to] slot range so a failed range can be
retried.

Verified offline (local producer -> stake-0 follower): blocks_received
== validated == tip (1x) vs the prior 30x. New regression test
deep_catch_up_does_not_refetch_in_flight_blocks.

Co-Authored-By: Claude Opus 4.8 <[email protected]>