Deterministic simulation, fault injection, and versioned traces for consensus protocol research in Go.


Keywords
consensus, deterministic-simulation, distributed-systems, fault-injection, golang, raft, reproducibility, research
License
Apache-2.0
Install
go get github.com/aminkbi/d-raft

Documentation

d-raft

CI Go Reference License

Deterministic Raft experiments with durable, replayable decisions.

d-raft is a research platform for turning distributed-systems failures into small, independently checkable execution artifacts. It combines a pure Raft reference state machine with virtual time, a faultable network, explicit persistence acknowledgements, crash/restart simulation, semantic decision recording, and package-separated safety checks with structured witnesses.

The project is created and maintained by Mohammadamin Khanbabaei (aminkbi).

Research status: the deterministic kernel, durable reference Raft model, cluster harness, safety checker, observational trace decoder, and exact semantic decision replay are implemented. Self-describing run artifacts and the research CLI are also usable. Bounded prefix exploration and fingerprint-preserving semantic minimization are implemented. Durable snapshots, safe log compaction, and snapshot-bearing run artifacts are also implemented. Joint-consensus membership changes and learners are implemented, including durable recovery and snapshot-aware configuration state. An experimental adapter for the production-used go.etcd.io/raft/v3 core is implemented for a declared fixed-membership capability subset. A versioned, portable binary KV application oracle now produces independently checkable state/history commitments in both adapters, including reference snapshot recovery. A versioned six-fault corpus and isolated, repository-pinned runner are implemented with a clean Go 1.26.6 result: three checker-backed safety kills and three separately reported conformance kills. Strict adapter-neutral semantic plans, bilateral capability preflight, projection accounting, normalized outcomes/comparisons, source-provenance verification, and a two-adapter research CLI are implemented. Immutable, CI-verified cross-adapter control and faulted-workload cases are published. A clean-provenance 21-trial bounded harness/accounting study, raw trial observations, paired cache contrast, and pinned related-work matrix are also published; real-bug effectiveness and diagnosis-time claims remain outside the measured evidence.

Why d-raft?

A seed says how to repeat one pseudorandom run in one implementation. A useful counterexample should say which semantic choices mattered, retain evidence of the violated invariant, survive irrelevant changes in random-number consumption, and be portable to another implementation.

d-raft's working research direction is therefore:

Portable, minimized, independently checkable semantic Raft counterexamples that replay across implementations and versions.

Deterministic simulation, pure Step APIs, seeded replay, and trace reduction all have substantial prior art. d-raft does not present those techniques alone as novel; RESEARCH.md defines the narrower thesis and evaluation plan.

What works today

Package Role
sim Protocol-neutral virtual-time scheduler, stable RNG streams, typed network, partitions, and observational JSONL traces
raft Pure deterministic Raft reference state machine with elections, replication, current-term commit, snapshots, compaction, joint consensus, learners, and leader no-op entries
raftsim Durable storage, timers, network delivery, partitions, crash/restart, snapshot installation, membership actions, process incarnations, and persistence barriers
check Package-separated election, voting, term, log, commit, apply, snapshot, and membership-transition witnesses with stable fingerprints
decision Versioned semantic choices, seeded selection, recording, exact tape replay, and domain-drift detection
trace Bounded, line-aware, payload-lossless decoder for known d-raft.trace/v1 fields
artifact Strict, self-describing d-raft.run/v3 artifacts with scenarios, voter/learner roles, configuration actions, environment, tape, outcome, digest, and witnesses; legacy v1/v2 decoding remains available
apporacle Strict binary KV commands, canonical checkpoints, known-answer vectors, and adapter-neutral state/history commitments
semanticplan Strict portable plan/capability/execution schemas, plan-aware projection proof checks, negotiated invariant universes, normalized outcomes, and outcome-bound comparisons
experiment Clean-run execution of versioned and named canonical scenarios with proposal, snapshot, membership, process, and network actions
evaluation Strict balanced-trial evaluator with raw accounting, paired cache contrasts, machine/build provenance, and publication validation
cmd/draft run, canonical, explore, replay, minimize, and inspect research workflow
cmd/draft-eval Linux research-evaluation runner with clean-build provenance gating and durable, private, no-clobber publication
explore Clean-rerun bounded DFS with deterministic suffix completion and collision-safe canonical-state pruning
minimize Scenario ddmin, sparse semantic guidance reduction, and domain-aware selection shrinking
mutant Strict pinned mutant manifests, isolated worktree execution, bounded evidence, and closed outcome classification
cmd/draft-mutants Seeded-mutant corpus runner with atomic, no-overwrite JSON result publication
adapters/etcdraft Experimental go.etcd.io/raft/v3 v3.7.0 production-core adapter with conservative checking and adapter-local exact replay
adapters/etcdraft/cmd/draft-cross Replay-verified plan derivation plus private, no-clobber, manifest-committed comparative bundles and end-to-end verification

The root module is dependency-free and uses no wall-clock sleeps or background goroutines. The isolated nested etcd/raft adapter carries its production-core dependency. Both target Go 1.26 and declare the current Go 1.26.6 toolchain.

See MUTANTS.md for the seeded-fault evaluation contract and runner trust boundary. See SEMANTIC_PLANS.md for the cross-adapter portability and comparison contract. See EVALUATION.md for the bounded study, RELATED_WORK.md for pinned positioning, and REPRODUCIBILITY.md for verification and archival steps. Release-level changes are summarized in CHANGELOG.md.

Architecture

flowchart LR
    Scenario[Scenario / faults / proposals]
    Decisions[Semantic decider]
    Harness[Durable Raft harness]
    Model[Pure Raft model]
    Runtime[Virtual time + network]
    Checker[Package-separated checker]
    Artifact[Decision tape + observations]

    Scenario --> Harness
    Decisions --> Harness
    Harness <--> Model
    Harness <--> Runtime
    Harness --> Checker
    Decisions --> Artifact
    Runtime --> Artifact
    Checker --> Artifact
Loading

The persistence boundary is explicit:

Step(input)
  -> Persist(token, state)
  -> simulated durable completion
  -> Step(Persisted(token))
  -> dependent messages, timers, apply, and snapshot-install effects

A crash destroys volatile raft.Node state. Restart creates a fresh node only from the durable store. This lets tests distinguish a crash immediately before persistence from one immediately after it.

Quick start

config := raftsim.DefaultConfig("a", "b", "c")
config.Seed = 42

cluster, err := raftsim.New(config)
if err != nil {
    log.Fatal(err)
}
if _, err := cluster.RunUntil(2 * time.Second); err != nil {
    log.Fatal(err)
}

leader, ok := cluster.Leader()
if !ok {
    log.Fatal("no unique leader")
}
if err := cluster.ProposeTo(leader, []byte("set x=1")); err != nil {
    log.Fatal(err)
}

Partitions, scheduled crashes, restarts, and crash-after-persist boundaries are available directly on raftsim.Cluster. Every semantic step is checked; when StopOnViolation is enabled, a run stops at the first safety violation.

Replay semantic decisions

Recording is separate from the observational trace. The choice tape describes election timeouts, network loss and latency, and storage completion latency by stable semantic identity.

recorder := decision.NewRecorder(decision.NewSeedDecider(42))
config.Decider = recorder
original, _ := raftsim.New(config)
_, _ = original.RunUntil(2 * time.Second)

tape := recorder.Tape()
replay, _ := decision.NewTapeDecider(tape)
config.Seed = 999 // infrastructure RNG no longer controls these choices
config.Decider = replay
replayed, _ := raftsim.New(config)
_, _ = replayed.RunUntil(2 * time.Second)
if err := replay.Finish(); err != nil {
    log.Fatal(err)
}

TapeDecider stops at the first choice ID, kind, domain, or selection mismatch. This makes replay drift explicit instead of silently producing a different run. Exact execution also requires the same scenario version, external actions, and run horizon; d-raft.run/v3 bundles those inputs with the tape.

Command-line workflow

Build the research CLI and create a self-contained run artifact:

go build -o draft ./cmd/draft
./draft run --seed 42 --duration 2s --out run.json
./draft canonical --seed 1 --out portable-faults.json portable-faults-v1
./draft inspect run.json
./draft replay run.json
./draft explore --depth 6 --max-runs 1000
./draft explore --cache=false --depth 6 --max-runs 1000 # matched baseline
./draft minimize --out minimized.json failing.json

go build -buildvcs=true -o draft-eval ./cmd/draft-eval
./draft-eval --trials 21 --out d-raft-evaluation.json
./draft-eval --verify d-raft-evaluation.json

draft replay starts from a clean cluster, consumes the stored choice tape, rejects any semantic drift, and verifies the recorded outcome status, step count, virtual end time, violation fingerprints, and versioned canonical observation digest. Artifact writes use a temporary file and atomic no-clobber publication, so an encoding or filesystem failure does not leave a plausible partial result. Artifacts remain private (0600) by default.

draft run and draft explore generate a steady all-voter scenario. The named portable-faults-v1 canonical scenario generates four portable KV proposals around a minority partition/heal and a crash/restart, followed by a 2.6-second quiet convergence tail. Its version fixes the three-node timing, 2% network loss, command bytes, and seed 1; a different semantic stream requires a new canonical version rather than silently changing the published experiment. The v3 schema and Go APIs can also execute scheduled proposals, snapshots, joint membership transitions, partitions, healing, crashes, and restarts, including the crash-after-persistence-before-acknowledgement boundary. Role changes are limited to a pre-provisioned Members universe; they do not perform dynamic discovery or process creation. Additional named fault suites and broader production adapters are later milestones. The experimental etcd/raft production-core adapter and separate draft-etcd CLI are documented in ADAPTERS.md. Canonical reference-state caching is enabled by default for draft explore and bounded with --cache-entries and --cache-bytes. See ARTIFACTS.md, MEMBERSHIP.md, SNAPSHOTS.md, EXPLORATION.md, CANONICAL_STATE.md, and MINIMIZATION.md. The opt-in cross-adapter application profile is specified in APPLICATION_ORACLE.md.

draft-eval runs the fixed balanced study only from a clean, VCS-stamped Linux build. It records all raw trials, machine and schema identity, status-separated terminal outcomes, processed event attempts, and the paired cache-on minus cache-off contrast. Its common runner-invocation ceiling does not make random full runs and DFS frontier probes equal computational work. If the workload has no cache hits or state pruning, the result characterizes cache overhead with no repeated exact cache identity; it is not evidence of pruning efficacy or real-bug detection probability. The output path is preflighted before the run and published as a durable, 0600, no-clobber artifact.

Observational traces

sim.JSONLRecorder emits globally ordered d-raft.trace/v1 JSON Lines. trace.Decoder enforces schema and sequence rules, supports compatible and strict validation, bounds record size, and preserves protocol payloads as json.RawMessage so 64-bit terms and indexes are never routed through float64.

The observational trace and semantic decision tape are deliberately distinct: the former explains what happened; the latter drives an execution.

Determinism boundary

For a fixed d-raft version, initial state, API-call sequence, decision tape or seed, and deterministic callbacks, d-raft reproduces virtual time, event and packet order, protocol state, durable state, and trace output. Unordered map iteration, wall-clock reads, external I/O, concurrent callbacks, and mutation after an inadequately cloned send are outside that guarantee. See COMPATIBILITY.md.

Development

go test ./...
go vet ./...
go test -race ./...

cd adapters/etcdraft
go test ./...
go vet ./...
go test -race ./...

Contributions should include a deterministic regression test and, for protocol changes, a crash-boundary test where persistence matters. See CONTRIBUTING.md.

Prior art and positioning

d-raft builds on ideas demonstrated by etcd/raft's deterministic core and TLA+ trace validation, FoundationDB simulation, DEMi, SAMC, MadSim/MadRaft, Oddity, Coyote, Turmoil, VOPR, and commercial deterministic-testing systems such as Antithesis. The research target is a portable semantic counterexample format and reduction/evaluation workflow, not another claim that seeded simulation itself is new.

Citation and license

If d-raft supports published work, cite the repository using CITATION.cff and record the release or commit, Go toolchain, scenario version, adapter version, and semantic tape schema.

Licensed under Apache 2.0.