Home / Input Output / ouroboros-leios-sim
Apr 28, 5-6 PM (0)
Apr 28, 6-7 PM (0)
Apr 28, 7-8 PM (0)
Apr 28, 8-9 PM (0)
Apr 28, 9-10 PM (0)
Apr 28, 10-11 PM (0)
Apr 28, 11-12 AM (0)
Apr 29, 12-1 AM (0)
Apr 29, 1-2 AM (0)
Apr 29, 2-3 AM (0)
Apr 29, 3-4 AM (0)
Apr 29, 4-5 AM (0)
Apr 29, 5-6 AM (0)
Apr 29, 6-7 AM (0)
Apr 29, 7-8 AM (0)
Apr 29, 8-9 AM (0)
Apr 29, 9-10 AM (0)
Apr 29, 10-11 AM (0)
Apr 29, 11-12 PM (0)
Apr 29, 12-1 PM (0)
Apr 29, 1-2 PM (0)
Apr 29, 2-3 PM (1)
Apr 29, 3-4 PM (0)
Apr 29, 4-5 PM (0)
Apr 29, 5-6 PM (0)
Apr 29, 6-7 PM (0)
Apr 29, 7-8 PM (0)
Apr 29, 8-9 PM (0)
Apr 29, 9-10 PM (0)
Apr 29, 10-11 PM (0)
Apr 29, 11-12 AM (0)
Apr 30, 12-1 AM (0)
Apr 30, 1-2 AM (0)
Apr 30, 2-3 AM (0)
Apr 30, 3-4 AM (0)
Apr 30, 4-5 AM (0)
Apr 30, 5-6 AM (0)
Apr 30, 6-7 AM (0)
Apr 30, 7-8 AM (0)
Apr 30, 8-9 AM (0)
Apr 30, 9-10 AM (0)
Apr 30, 10-11 AM (0)
Apr 30, 11-12 PM (0)
Apr 30, 12-1 PM (1)
Apr 30, 1-2 PM (0)
Apr 30, 2-3 PM (68)
Apr 30, 3-4 PM (0)
Apr 30, 4-5 PM (0)
Apr 30, 5-6 PM (0)
Apr 30, 6-7 PM (0)
Apr 30, 7-8 PM (1)
Apr 30, 8-9 PM (0)
Apr 30, 9-10 PM (0)
Apr 30, 10-11 PM (0)
Apr 30, 11-12 AM (0)
May 01, 12-1 AM (0)
May 01, 1-2 AM (0)
May 01, 2-3 AM (0)
May 01, 3-4 AM (0)
May 01, 4-5 AM (0)
May 01, 5-6 AM (0)
May 01, 6-7 AM (0)
May 01, 7-8 AM (0)
May 01, 8-9 AM (0)
May 01, 9-10 AM (0)
May 01, 10-11 AM (0)
May 01, 11-12 PM (0)
May 01, 12-1 PM (0)
May 01, 1-2 PM (0)
May 01, 2-3 PM (0)
May 01, 3-4 PM (0)
May 01, 4-5 PM (0)
May 01, 5-6 PM (0)
May 01, 6-7 PM (0)
May 01, 7-8 PM (0)
May 01, 8-9 PM (0)
May 01, 9-10 PM (0)
May 01, 10-11 PM (0)
May 01, 11-12 AM (0)
May 02, 12-1 AM (0)
May 02, 1-2 AM (0)
May 02, 2-3 AM (0)
May 02, 3-4 AM (0)
May 02, 4-5 AM (0)
May 02, 5-6 AM (0)
May 02, 6-7 AM (0)
May 02, 7-8 AM (0)
May 02, 8-9 AM (0)
May 02, 9-10 AM (0)
May 02, 10-11 AM (0)
May 02, 11-12 PM (0)
May 02, 12-1 PM (0)
May 02, 1-2 PM (0)
May 02, 2-3 PM (0)
May 02, 3-4 PM (0)
May 02, 4-5 PM (0)
May 02, 5-6 PM (0)
May 02, 6-7 PM (0)
May 02, 7-8 PM (0)
May 02, 8-9 PM (0)
May 02, 9-10 PM (0)
May 02, 10-11 PM (0)
May 02, 11-12 AM (0)
May 03, 12-1 AM (0)
May 03, 1-2 AM (0)
May 03, 2-3 AM (0)
May 03, 3-4 AM (0)
May 03, 4-5 AM (0)
May 03, 5-6 AM (0)
May 03, 6-7 AM (0)
May 03, 7-8 AM (0)
May 03, 8-9 AM (0)
May 03, 9-10 AM (0)
May 03, 10-11 AM (0)
May 03, 11-12 PM (0)
May 03, 12-1 PM (0)
May 03, 1-2 PM (0)
May 03, 2-3 PM (0)
May 03, 3-4 PM (0)
May 03, 4-5 PM (0)
May 03, 5-6 PM (0)
May 03, 6-7 PM (0)
May 03, 7-8 PM (0)
May 03, 8-9 PM (0)
May 03, 9-10 PM (0)
May 03, 10-11 PM (0)
May 03, 11-12 AM (0)
May 04, 12-1 AM (0)
May 04, 1-2 AM (0)
May 04, 2-3 AM (0)
May 04, 3-4 AM (0)
May 04, 4-5 AM (0)
May 04, 5-6 AM (0)
May 04, 6-7 AM (0)
May 04, 7-8 AM (0)
May 04, 8-9 AM (0)
May 04, 9-10 AM (0)
May 04, 10-11 AM (0)
May 04, 11-12 PM (0)
May 04, 12-1 PM (0)
May 04, 1-2 PM (0)
May 04, 2-3 PM (0)
May 04, 3-4 PM (0)
May 04, 4-5 PM (0)
May 04, 5-6 PM (0)
May 04, 6-7 PM (0)
May 04, 7-8 PM (0)
May 04, 8-9 PM (0)
May 04, 9-10 PM (0)
May 04, 10-11 PM (0)
May 04, 11-12 AM (0)
May 05, 12-1 AM (0)
May 05, 1-2 AM (0)
May 05, 2-3 AM (0)
May 05, 3-4 AM (0)
May 05, 4-5 AM (0)
May 05, 5-6 AM (0)
May 05, 6-7 AM (0)
May 05, 7-8 AM (0)
May 05, 8-9 AM (1)
May 05, 9-10 AM (0)
May 05, 10-11 AM (4)
May 05, 11-12 PM (1)
May 05, 12-1 PM (1)
May 05, 1-2 PM (1)
May 05, 2-3 PM (1)
May 05, 3-4 PM (1)
May 05, 4-5 PM (0)
May 05, 5-6 PM (0)
81 commits this week Apr 28, 2026 - May 05, 2026
con-rs: add CLAUDE.md, README.md, fill test gaps
CLAUDE.md captures the sans-IO discipline, determinism rules, and
effect-emission patterns that consumers must respect.  README.md
documents the architecture with five Mermaid diagrams (I/O boundary,
Praos/Leios sequence flows, events↔effects graphs, module deps).

Test coverage 68 → 131 (+63):
- praos.rs: 0 → 39 (construction, peer events, self-production,
  validation outcomes, on_block_received paths, all four
  SelectionDecision variants, try_switch_to, hybrid ancestor walks,
  in-flight TTL eviction, orphan classification with ReIntersect).
- leios.rs: 9 → 24 (on_eb_txs_*, votes_*, on_validated_eb,
  retry_eb_tx_fetch, partial/complete bitmap match, expiry telemetry,
  PV-seated quorum telemetry).
- peer_chain.rs: 1 → 9 (append, dedup, cap eviction, rollback known/
  unknown/anchor, clear_entries, origin anchor).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
con-rs: lift Tip; extract LeiosState sans-IO state machine
Two changes bundled, both extending con-rs's coverage to the Leios
side of consensus.

1. `types::Tip` lifted from net-core (data + Display + minicbor codec).
   net-core re-exports via `pub use con_rs::Tip` so existing call
   sites keep working.

2. `leios::LeiosState` mirrors `praos::PraosState`: a sans-IO state
   machine that owns the per-EB election state (via `Elections`), the
   per-EB tx-hash manifest cache, the in-flight Leios-fetch tracking,
   and the local node's voting configuration.  It emits
   `Vec<LeiosEffect>` describing what the I/O layer should do —
   request an EB body, request EB transactions, ask for votes, record
   a manifest, hand a block / votes to the validator, emit a vote,
   raise telemetry.

   The state never builds wire-format vote bodies: when an election
   enters the Voting phase and the local node is eligible (PV via
   `persistent_seats`, NPV via the wfa lottery on the eligibility
   signature), it emits an `EmitVote` effect carrying logical args
   (PV flag, NPV signature) and the I/O layer encodes the body.
   Same principle as `praos`: con-rs is wire-format-agnostic.

   `EbTxMatchOutcome` and the bitmap-helpers move along.  `ValidatedVote`
   is a borrowed view used to feed decoded vote bodies into the state
   machine without copying.

Net-node delta:
- `consensus/leios/mod.rs` becomes a thin wrapper holding `state`,
  `commands`, `validator`, `mempool`, plus the telemetry buffer.
  All event handlers translate `NetworkEvent` / `LedgerOutcome` into
  state-machine calls and dispatch the returned effects.
- `consensus/leios/voting.rs` is deleted; vote-body construction
  (`VoteBody::stub_persistent` / `stub_non_persistent` / `encode`)
  happens in the wrapper's `emit_vote` helper, triggered by
  `LeiosEffect::EmitVote`.
- `bitmap_for_missing_txs` (mempool query for EB-tx bitmap) stays in
  net-node — mempool semantics will get their own trait later.

Comment hygiene: cleaned up consumer-specific phrasing in con-rs doc
comments (no more references to "net-node" or "sim-rs" — con-rs is
generic Cardano consensus, not glue).

Verified: 68 con-rs tests pass (was 58 — +10 new LeiosState tests),
524 net-rs tests pass (was 528 — the 4 voting.rs tests retired in
favour of con-rs equivalents), cargo clippy --all-targets shows 11
warnings in net-rs (baseline) and 0 in con-rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
con-rs: extract PraosState sans-IO state machine
Pulls every consensus-shaped field out of net-node's `PraosConsensus`
into a new `con_rs::praos::PraosState`.  The wrapper that remains in
net-node is purely I/O glue: it translates `NetworkEvent` /
`LedgerOutcome` / wire-type args into logical args, calls into the
state machine, and dispatches the returned `Vec<PraosEffect>` to the
network-command channel and validator actor.

Moved into `PraosState`:
- chain_tree, adopted_tip_hash
- block_cache (with opaque header/body bytes; `CachedBlock` no longer
  carries `WrappedHeader` / `BlockBody`, sidestepping a wire-format
  dep on con-rs)
- validated, self_produced
- peer_chains, orphan_cooldown
- in_flight (fetch tracking), in_flight_validation
- queued_validator_tip, last_validated_tip
- security_param_k, node_id

Algorithms moved with the state:
- walk_ancestors_hybrid, select_chain_once, try_switch_to (pure)
- evaluate_and_fetch, execute_switch, submit_for_validation,
  issue_fetch, retry_select_chain (now emit `PraosEffect`s rather
  than calling I/O directly)
- on_block_received, on_block_applied, on_block_rolled_back,
  on_block_apply_failed, on_tip_advanced, on_peer_*, on_block_fetch_failed
- register_self_produced

`PraosEffect` covers the I/O surface: FetchBlockRange, ReIntersect,
InjectBlock / InjectRollback (chain-store side), ValidatorApply /
ValidatorRollback (ledger side).

To support the simulator's determinism contract, all collections in
con-rs are `BTreeMap` / `BTreeSet` (peer_chains, block_cache,
orphan_cooldown, in_flight, validated, in_flight_validation,
self_produced).  `Point` gains `PartialOrd` / `Ord` so it can key
`BTreeSet<Point>` for `self_produced`.

Time is injected: methods that need wall-clock take `now: Instant`
(orphan-cooldown expiry, in-flight TTL, etc.).  Sim-rs's adapter will
pass synthetic time; net-node passes `Instant::now()` at the call
boundary.

Net-node delta:
- `consensus/praos/mod.rs` shrinks from a 2200-line file backed by
  `selection.rs` / `validation.rs` / `fetching.rs` (~1700 lines
  combined) to a single 340-line wrapper.  Helper files deleted.
- Public API on `PraosConsensus` is unchanged.  Tests retained,
  poking through `consensus.state.<field>`.
- `dispatch(Vec<PraosEffect>)` translates each effect: rehydrates
  `header: Vec<u8>` / `body: Vec<u8>` into `WrappedHeader` /
  `BlockBody` for `InjectBlock` and `ValidatorApply`.

Verified: 58 con-rs tests pass, 154 net-node tests pass (baseline
154 — no regression), 528 net-rs workspace tests pass, cargo clippy
--all-targets shows 11 warnings in net-rs (baseline) and 0 in con-rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
con-rs: lift PeerId and peer_chain in prep for selection extraction
Two small lifts that remove con-rs's last dependencies on net-core:

- `peer::PeerId` (`pub struct PeerId(pub u64)` + Display).  Picks up
  `PartialOrd, Ord` so it can key BTreeMaps in the upcoming PraosState.
  net-core re-exports via `pub use con_rs::PeerId`.
- `peer_chain::{PeerChain, PeerChainEntry, PeerChainAnchor}` — the
  per-peer ordered fragment used by Praos chain selection.  Lifted
  verbatim with visibilities widened from pub(crate)/pub(super) to pub.
  Adds `is_empty` to satisfy clippy.

Net-node's `consensus/praos/peer_chain.rs` is deleted; imports rewrite
to `con_rs::peer_chain::*` in praos/{mod,selection}.rs.  No semantic
change; this is scaffolding for the next commit, which moves the pure
parts of `selection.rs` (walk_ancestors_hybrid, select_chain_once,
try_switch_to) into a `PraosState` struct in con-rs.

Verified: 58 con-rs tests pass (was 57 — +1 peer_chain test moved in),
528 net-rs tests pass (was 529 — same test moved out), cargo clippy
--all-targets shows 11 warnings in net-rs (baseline) and 0 in con-rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
con-rs: lift Point and chain_tree
Adds two modules to con-rs:

- `types::Point` (with Display + minicbor Encode/Decode codec).  Both
  consumers must agree on the on-wire encoding of a chain point, so the
  codec lives with the type.  con-rs picks up `minicbor` as a dep.
- `chain_tree` (`ChainTree`, `ChainTreeEntry`, `is_better_tip`,
  `ChainNode`): the fork-aware tree of block headers used by Praos
  longest-chain selection.  Lifted verbatim; HashMap iterations are
  sort-stabilized at the public API boundary, so internal collection
  choice doesn't leak determinism.  Adds `Default` impl and `is_empty`
  to satisfy clippy.

Net-core now depends on con-rs and re-exports `Point` via `pub use
con_rs::Point`, so all callers continue to import from
`net_core::types`.  Net-core's `Tip`, `MAX_POINTS`, `decode_points` /
`encode_points`, and the existing tests stay where they are — they're
wire-protocol concerns.

Net-node's `chain_tree.rs` is deleted; imports in telemetry, consensus
mod, and the praos sub-layer are rewritten to `con_rs::chain_tree::*`.

Verified: 57 con-rs tests pass (was 42 — +4 Point tests, +15 chain_tree
tests now run in con-rs), 529 net-rs workspace tests pass (was 544 —
the 15 chain_tree tests moved out), cargo clippy --all-targets shows 11
warnings in net-rs (baseline) and 0 in con-rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
con-rs: extract Elections sans-IO state machine; net-node delegates
Introduces `con_rs::elections::Elections`: a sans-IO state machine that
owns per-EB election state and the committee/stake context needed to
attribute incoming votes.  Exposes:

- `announce(eb_slot, eb_hash) -> bool`
- `on_slot(slot) -> Vec<SlotEffect>` returning deterministically-ordered
  `EligibleToVote` and `Expired` effects
- `mark_voted`, `record_vote`, `weight_for`
- queries: `phase`, `voted`, `quorum`, `voter_count`, `count`,
  `has_certified_eb`, `certified_eb_slot`

To support sim-rs's determinism contract, `EbElection.voter_weights` and
the internal elections map are `BTreeMap`s; `aggregation::record_vote`
takes `&mut BTreeMap<[u8;32], EbElection>`.  All iteration in con-rs is
now over `BTreeMap`, so given deterministic input ordering at the
adapter, effect dispatch order is reproducible from a seed.

Net-node's `LeiosConsensus` sheds six fields (committee_selection,
persistent_committee, stake_registry, total_stake, quorum_weight_fraction,
expected_committee_size) and current_slot in favour of an `Elections`
instance.  The leios layer is now a thin I/O bridge: `on_slot` iterates
`SlotEffect`s, calling `voting::try_vote_on_eb` on `EligibleToVote` and
emitting telemetry on `Expired`; `on_validated_eb` and `on_validated_votes`
delegate.  voting.rs is unchanged.

Verified: 38 con-rs tests pass (28 prior + 10 new election tests),
544 net-rs workspace tests pass, cargo clippy --all-targets shows 11
warnings (baseline — no regression).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
con-rs: extract shared consensus primitives from net-node
Introduce a top-level `con-rs` crate as a peer of `net-rs` and `sim-rs`,
holding the protocol pieces both implementations should agree on. This
is step 1 of a planned extraction: lift the pure modules verbatim, no
API redesign yet.

Lifted from net-node/src/consensus/leios:
- pipeline.rs: PipelinePhase, EbElection, PipelineConfig (CIP-0164 phase
  math).  Drops EbElection.eb_point — only used for tracing — to avoid
  pulling minicbor into con-rs.  Aggregation logging now formats slot +
  hash-prefix directly.
- wfa.rs: persistent committee allocation, NPV eligibility signature +
  Bernoulli win count, build_committee, expected_committee_size.
- aggregation.rs: record_vote / QuorumFormed.

Lifted from net-node/src/config.rs:
- CommitteeSelection (WfaLs / EveryoneVotes / StakeCentile) and its
  serde defaults; StakeEntry.  net-node still imports these from
  `crate::config` via `pub use con_rs::*`.

con-rs is sans-IO: deps are just serde / rand / blake2b_simd / tracing.
No tokio, no net-core.  Both consumers will reference it via path; sim-rs
adoption follows in a later commit.

Net-node delta: add con-rs path dep, replace the four module declarations
with `use con_rs::*`, drop the eb_point construction site.  voting.rs is
unchanged — `super::wfa` resolves through mod.rs's re-import — and is
deliberately left in net-node for now since it sends NetworkCommand and
belongs in step 2's sans-IO redesign.

Verified: cargo build, cargo test (28 con-rs + 544 net-rs workspace),
cargo clippy --all-targets (11 warnings, all pre-existing — no regression).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1500-node sweep: final 10 Plutus runs (sweep complete)
Completes the topology-v2-1500 sweep: everyone × 4 (Plutus 5000-50000)
plus top-stake-fraction × 6 (Plutus 1000-50000). The full sweep finished
2026-05-01 at 07:24:33 BST, totalling 33 runs (5 NA × 3 modes + 6 Plutus
× 3 modes), all completing 100% TX finalization except the 50000 Gstep/EB
collapse case which is the expected pathological behaviour at that
Plutus level.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Allow topology selection in voting benchmark script
Accept topology as leafname (resolved in data/simulation/pseudo-mainnet/),
relative path, or absolute path. Defaults to topology-v2-cip.yaml.
Auto-compute vote thresholds from topology stake distribution at
configurable quorum fraction (QUORUM_FRACTION, default 60%).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add no-caps parameter file and baseline voting results
parameters/no-caps.yaml disables all three memory caps for diagnostic
experiments (peer backlog, generated backlog, TX max age).

voting_results.csv captures the full 4-way matrix at 0.200/wfa-ls:
{turbo,sequential} × {caps,nocaps} × seeds 0-4. Key findings:

- Seed 4 is the stress seed: caps cause 40% uncertified (seq) vs 17%
  without caps. Root cause is a race in propagate_tx where
  acknowledge_tx consumes the one-shot missing_txs trigger before
  PeerBacklogFull drops the TX.
- Seeds 1,3 are cap-insensitive (well-spaced RBs).
- No-caps converges all seeds to 16-22% uncertified.
- Stale rows (pre-rayon-fix, pre-seed-wiring) labelled as such.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Set wfa-ls VRF trials to 600 (480 persistent + 120 non-persistent)
The experiment config's vote-generation-probability: 600 was being
ignored because the simulation uses persistent + non-persistent
probabilities directly (defaulting to 400 + 100 = 500). Override both
in the wfa-ls mode to get the intended 600 total with 80:20 split,
giving a 75% quorum at the existing threshold of 450.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Convert HashMap/HashSet to BTreeMap/BTreeSet in linear_leios node state
Eliminates non-deterministic iteration order in NodeLeiosState,
LedgerState, and LinearLeiosNode.txs. All key types already implement
Ord. At typical map sizes (5-50 entries for leios state, 100s-1000s
for txs) BTreeMap has negligible CPU overhead and slightly lower
memory usage than HashMap.

The praos state (NodePraosState) was already using BTreeMap; this
brings the leios state into line. Remaining HashSet usages are pure
membership tests (contains/insert) that do not affect determinism.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sort event stream by (timestamp, node_id) for deterministic jsonl output
Multi-shard execution emits tracking events from concurrent shard
threads via an mpsc channel, so events at the same virtual timestamp
can arrive in arbitrary order.  Buffer events in timestamp buckets
(BTreeMap) with a 1-second flush window, sorting each bucket by
originating node ID before writing.  This makes the jsonl event
stream byte-identical across runs without affecting simulation logic.

Also adds Event::node_id() to extract the originating node from each
event variant for sorting purposes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Retain EB-critical TXs on peer backlog overflow
Problem
-------
When a node's peer TX backlog hits its cap (e.g. 10,000), incoming TXs
are silently dropped from self.txs.  If a dropped TX is referenced by a
pending Endorser Block, the EB's validation scan (try_validating_eb)
finds has_tx() = false and the EB is never marked all_txs_seen.  The EB
then misses its vote window and is orphaned by the next Ranking Block
(WrongEB).  Because the TX is never re-offered by peers, the one-shot
missing_txs trigger — already consumed by acknowledge_tx — cannot
re-fire, leaving the EB permanently stuck.

Under Poisson-clustered RB production (e.g. seed 4 at 0.200 MB/s), this
cascade produced 48 EBs with 19 uncertified (40%), 23M peer TX drops,
and a mean of only 348 votes/EB (well below the 450 quorum).

Fix
---
Two changes in propagate_tx():

1. Move the mempool insertion check (try_add_to_mempool) BEFORE
   acknowledge_tx, so that missing_txs has not yet been consumed at the
   point where we decide whether to drop.

2. When PeerBacklogFull fires, check whether the TX is referenced by a
   pending EB (self.leios.missing_txs.contains_key).  If yes, keep the
   TX in self.txs (skip the backlog, but preserve has_tx = true) and
   fall through to acknowledge_tx normally.  If no, drop as before.

This retains only EB-critical TXs — bounded by (pending_EBs × EB_size),
typically a few thousand entries and ~3 MB of HashMap overhead per node.
Non-critical TXs are still dropped, preserving the memory cap's purpose.

Effect on seed 4 sequential 0.200/wfa-ls (worst-case seed)
-----------------------------------------------------------
                  EBs  uncert  mean   WrongEB  drops   peak RSS
caps (before):    48   19      348    1138     23.2M   ~20 GB
caps-retain:      45    8      470    1330      5.9M   ~24 GB
nocaps (ref):     46    8      473    1516      0      ~35 GB

Uncertified EBs:  19 → 8  (40% → 18%)
Mean votes/EB:    348 → 470  (near nocaps 473)
Peer TX drops:    23.2M → 5.9M  (−74%)
Peak RSS:         ~20 → ~24 GB  (+20%, well below nocaps ~35 GB)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Allow filtering by throughput and committee mode in benchmark script
Accept optional second and third args for comma-separated throughput
and mode filters (use "-" for all). Append to existing results CSV
when filtering instead of overwriting.

Examples:
  ./scripts/cip-voting-options.sh - 0.250 everyone
  ./scripts/cip-voting-options.sh - 0.250,0.300 wfa-ls,everyone

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add network queue stats instrumentation
Expose per-shard connection queue statistics (total/active connections,
queued messages, queued bytes) via a shared NetworkStatsCollector.
Each shard's sequential engine updates its counters at slot boundaries;
the node's existing log_memory_stats reads the aggregate.

Output appears every 60 slots alongside Memory stats, covering all
shards.  Initial profiling showed zero queued messages in turbo mode
(zero-latency clusters bypass bandwidth queues), ruling out network
queues as the cause of the ~40 GB RSS vs ~20 GB tracked-state gap.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
De-RNG Linear Leios completely: withhold attacker + TxGeneratorCore
Migrate every remaining stateful-RNG use reachable from Linear Leios:

- linear_leios.rs generate_withheld_txs: `self.rng.random_bool(p)` is
  replaced with `rng.draw_bool(node, slot, DrawSite::WithholdDecision,
  p)`. The distribution sample for `txs_to_generate` and the per-tx
  `new_tx` body generation use `Rng::seeded_chacha(node, slot, site)`
  to produce one-shot ChaChaRngs seeded from context — this keeps the
  rand_distr / `new_tx` machinery unchanged while removing the
  cross-call stateful coupling.

- tx.rs TxGeneratorCore: replaces its `ChaChaRng` with the stateless
  `SimRng` plus a monotonic `next_tx_idx: u64`. Each TX is generated
  from a one-shot ChaChaRng seeded from
  `("tx_generator", tx_idx)` — so the generated TX stream is a pure
  function of the master seed regardless of per-node or network-timing
  behaviour. Propagates the `SimRng` type through TransactionProducer
  and its callers in sim/sequential.rs and sharding/shard.rs; the
  master-RNG `.next_u64()` consumption is preserved to keep any
  remaining downstream draws on stracciatella/leios variants seeded
  the same way they were.

- Drops `rng: ChaChaRng` field from `LinearLeiosNode`. The NodeImpl
  trait signature still takes a `ChaChaRng` for the other variants, so
  LinearLeiosNode::new accepts it as `_rng` and discards.

New Rng methods: `seeded_chacha(node, slot, site)` for context-tied
one-shot ChaChaRng seeding, and `seeded_chacha_from<K: Hash>(&K)` for
sim-wide (non-node-tied) draws like the TX generator.

All 54 sim-core tests pass; clippy clean for Linear Leios and
TxGeneratorCore.

Stracciatella and full-Leios variants retain their stateful `self.rng`
for now — they build fine but are out of scope for the current
determinism investigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add stateless context-derived RNG primitive; migrate VRF/lottery
The simulator's stateful ChaChaRng-per-node design is fragile: RNG
consumption count per node depends on control flow (e.g., "did this
node receive an EB in time to vote"), which depends on network timing.
Any microsecond-scale timing drift changes the number of RNG draws on a
node, desynchronising its RNG state, and every downstream random
decision on that node diverges — a macro-amplifier that turns upstream
timing blips into EB-scale outcome drift.

It's also unrealistic. Cardano's real VRF is stateless per slot:
vrf_output = f(key, nonce || slot) is a pure function that doesn't
"advance" with each use.

Introduce a stateless oracle: every random draw becomes a pure function
of (global_seed, context). The new `sim-core/src/rng` module provides:

- DrawSite enum naming every call site (RbLottery, VoteVrf, MempoolSwap,
  TxGen{Node,Body,Frequency}, TxConflict, Withhold*, test/lottery site
  variants). Discriminant plus variant fields are hashed into the
  context, so distinct call sites never collide.
- Rng::draw_{u64,range,f64_01,bool}, all pure functions of
  (seed, node, slot, site).
- SplitMixHasher — portable deterministic hasher: endian-pinned writes
  (to_le_bytes in every write_uNN), splitmix64-style mixing, splitmix
  finalizer. Not cryptographic; fine for a sim (no adversarial inputs)
  and ~ns per draw.

Ten unit tests in rng::tests cover: determinism, different-seed
differentiation, 500-context collision check, 600-trial-index
distinctness, site-variant-on-same-(node,slot) distinctness, range/
probability sanity, endian-independence, and golden vectors pinning the
hash output (tested to catch accidental hash-function changes).

Migrate the VRF/lottery call paths for all three node variants:

- sim/lottery.rs: LotteryConfig::run signature changes from
  `(kind, success_rate, &mut ChaChaRng)` to
  `(kind, success_rate, &Rng, NodeId, slot, DrawSite)`. MockLotteryResults
  (tests) unchanged: still keyed by LotteryKind.
- sim/linear_leios.rs: run_vrf threads slot+site through; RB lottery
  uses DrawSite::RbLottery; vote VRF enumerates its (up to) 600 trials
  as DrawSite::VoteVrf { eb_id, trial }.
- sim/stracciatella.rs: inline run_vrf (bypasses LotteryConfig) migrated
  similarly. DrawSites: RbLottery, EbLottery{pipeline, trial},
  VoteVrfPipeline{pipeline, trial}.
- sim/leios.rs: inline run_vrf migrated. DrawSites: IbLottery, EbLottery,
  VoteVrfPipeline, RbLottery.

Nodes still hold a ChaChaRng for mempool shuffle, withhold-TX attack,
TxGeneratorCore, and new_tx body randomness. These are migrated in
follow-up phases. The critical VRF path — the macro-amplifier that
cascades network-timing non-determinism into per-node RNG-state
desynchronisation — is now structurally deterministic by construction.

All 51 sim-core tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Convert cip-voting-options.sh to named params, add engine selector
Positional args had grown unwieldy. Rewrite with flag parsing:
  -t/--topology, -T/--throughput, -m/--mode, -e/--engine,
  -s/--slots, --quorum-fraction, --stake-fraction.

Add an `--engine` selector that writes an on-the-fly override file:
  actor       — default (tokio async), single-shard, non-deterministic
  sequential  — single-shard sequential DES (deterministic)
  turbo       — sequential DES with 6 shards (non-deterministic, fast)

Add `engine` as a CSV column so runs from different engines can live
in the same file and be pivoted cleanly.

Add determinism-run.sh / determinism-check.sh as a simple 3-run
harness for spot-checking single-shard-sequential determinism against
the 0.200/wfa-ls scenario. determinism-run.sh runs the benchmark 3×
and writes progress to /tmp/det-run-state; determinism-check.sh prints
a concise status summary (safe to poll from /loop or cron).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>