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]>