Author: Lucas Luhur Last updated: 2026-07-07


Open question:


1. Summary

The experiments framework is the composition layer that turns the validated modules (consensus → broadcast network → anonymity → adversary → measures) into swept, reproducible runs. This also delivers the first real anonymisation layer, tor_fifo .

It treats every pipeline stage as a component plugged by name from a registry: change one string in a config and the layer / attack / measure swaps out, with the framework core untouched.

Parameters used

Parameter Symbol Meaning Value
Mix hops $k$ relay hops on the shared path 3
Mixing delay $μ$ mean per-hop hold, slots 1
Cover per slot $ S $
Cover policy peer selection: flat / stake_weighted flat
Seed / reps master seed + repetitions per cell (reproducibility) 0 / 3

Inherited from the consensus modules: $N = 1000, T = 388,800, f = 1/30, Pareto\ k = 1.33$


2. The framework core

run_once composes one experiment cell end to end, each stage selected by name from its registry.

def run_once(cfg, rng=None):
    rng = np.random.default_rng(rng)
    validate_pairing(cfg.attack, cfg.measures, ATTACKS, MEASURES)   # guess-type wall

    # consensus (the system) -> real winner events
    alpha = sample_relative_stakes(cfg.N, cfg.shape, rng=rng)
    slots, nodes = simulate_events(alpha, f=cfg.f, T=cfg.T, rng=rng)

    # traffic-level cover -> augmented, tagged, batched stream
    slots, nodes, is_dummy, group = inject_dummies(
        slots, nodes, cfg.N, params=cfg.dummy, alpha=alpha, T=cfg.T, rng=rng)

    # defence (plug by name) -> the Trace
    trace = LAYERS[cfg.layer](slots, nodes, is_dummy, group,
                              params=cfg.layer_params, latency_oracle=None, rng=rng)

    # attacker + scorer (plug by name, type-matched)
    guess = ATTACKS[cfg.attack].run(trace, params=cfg.attack_params, rng=rng)
    return {m: MEASURES[m].score(guess, ...) for m in cfg.measures}

The fixed data contract, left to right:

$$ \text{consensus} \to \texttt{inject\_dummies} \to \texttt{LAYERS}[\text{layer}] \to \texttt{ATTACKS}[\text{attack}] \to \{\texttt{MEASURES}[m]\} $$

Each run has an independent, decorrelated RNG (SeedSequence(seed).spawn) so every run is bit-reproducible.


3. The Trace — the one load-bearing contract

Every layer emits the same schema, and the downstream attack consumes only it (never the ground truth). This makes layers interchangeable: tor-like/mixnet/dandelion++ will follow the same pipeline.