Performance / load-testing scenarios for ValiChord, built on
holochain/wind-tunnel
(holochain_wind_tunnel_runner 0.7.1, targeting Holochain 0.6.3 / Kitsune2 0.4.1 per
upstream's version-compatibility table; we run it against our 0.6.2 conductor — a patch-level
difference on the same line).
0.7.1 is the last release usable on our stack. Upstream main moved to Holochain
0.7.0-rc.1 on 2026-07-24, so every release after v0.7.1 is on the 0.7 line and is gated behind
our own 0.7 migration. Hold here until then.
A scenario applies user-defined load with N agents and reports metrics. Each
agent is one thread of execution that repeatedly runs a behaviour. Wind-Tunnel
auto-captures per-call_zome latency; scenarios add their own custom metrics on
top.
This is a separate Cargo workspace — intentionally not a member of
valichord/Cargo.toml. The runner pulls inholochain(the native conductor), which cannot compile towasm32-unknown-unknownand would break the main WASM build. Same isolation pattern assweettest_integration/.
All Holochain scenarios need the packed hApp. Build the WASMs and pack first:
cd /workspaces/ValiChord/valichord
cargo build --target wasm32-unknown-unknown --release
hc dna pack dnas/attestation -o workdir/attestation.dna
hc dna pack dnas/researcher_repository -o workdir/researcher_repository.dna
hc dna pack dnas/validator_workspace -o workdir/validator_workspace.dna
hc dna pack dnas/governance -o workdir/governance.dna
hc app pack . -o workdir/valichord.happOverride the hApp path with VALICHORD_HAPP_PATH=/path/to/valichord.happ.
Always kill stale conductors before a run:
pkill -f holochain; pkill -f lair-keystore; sleep 2The Kitsune scenario (kitsune_dht_propagation) does not use the hApp.
It needs a running Kitsune2 bootstrap server and an Iroh relay server
instead (see its section below).
Two things the scenario args alone don't cover:
WT_METRICS_DIRmust be set (a writable dir for conductor telemetry) or every agent panics at startup — this arrived in runner 0.7.0.- Custom scenario metrics (
sync_lag,sent_count,recv_count, …) are only written when you pass--reporter=influx-file. They land in$WT_METRICS_DIR/<scenario>-<timestamp>.influx, each prefixedwt.custom.<name>(e.g.wt.custom.sync_lag). Without the flag you still get the printed "Summary of operations" (per-call latencies) but not the custom metrics.
export WT_METRICS_DIR=/tmp/wt_metrics && mkdir -p "$WT_METRICS_DIR"
# …then any command below, adding --reporter=influx-file to capture wt.custom.* metricsThe credential-gated attestation DNA is installed with a dev-mode
membrane-proof bypass automatically — the shared valichord_wt_common crate
ports the bypass from valichord-ui's dev-setup.mjs (empty issuer + 64×0x42
proof) and installs against the attestation role. No manual step.
CI (.github/workflows/wind-tunnel-smoke.yml) builds all scenarios + runs the
unit tests — a deterministic check that catches compile / dependency /
install-path regressions. It does not run a live scenario: a live
multi-conductor run was tried in CI twice and both failed for environment (not
code) reasons on a standard 2-core/7 GB GitHub runner — the per-agent conductors
either couldn't peer (the runner uses the default public bootstrap, no
local-bootstrap knob) or couldn't all start before a setup timeout. Live runs
are a local / well-resourced-machine activity (they work — see the dht_sync_lag
result below); CI gates on build + unit tests only.
There are five scenarios. The first three measure protocol throughput / latency; the last two form a propagation-latency ladder, from the raw network substrate up to a real ValiChord entry crossing between agents.
| Scenario | Measures | Default run | Key metrics |
|---|---|---|---|
validation_request_throughput |
Concurrent commit-phase write throughput — each agent loops submit_validation_request + notify_commitment_sealed |
--agents 4 --duration 60 |
commits_sent |
phase_observation_latency |
Time from a CommitmentAnchor write to the PhaseMarker(RevealOpen) becoming observable (single agent; num_validators_required=1) |
--agents 2 --duration 60 |
phase_observation_ms, poll_count, phase_timeout_count |
concurrent_reveal_throughput |
Full commit→reveal round under N-agent concurrent load (exercises ChainTopOrdering::Relaxed over 3 back-to-back source-chain writes) |
--agents 4 --duration 90 |
round_total_ms, reveal_count, reveal_timeout_count |
cargo run -p validation_request_throughput -- --agents 4 --duration 60
cargo run -p phase_observation_latency -- --agents 2 --duration 60
cargo run -p concurrent_reveal_throughput -- --agents 4 --duration 90These answer the question the DepMissingFromDht "transient gossip lag" noise
raises: how fast is propagation, and is the cost in the network or in our DNA
logic? Reading down the ladder isolates each layer.
How long after a write agent authors a ValidationRequest does it become
visible to record_lag reader agents. Readers discover requests with no prior
knowledge via the global pending anchor (get_pending_request_refs) and emit
sync_lag = observed_time − the record's Action timestamp.
Zero DNA changes — reuses existing zome functions and takes the send-time
from each record's built-in Action timestamp (no created_at field, no
integrity change, no DNA-hash change). Runs against the existing valichord.happ.
Run with one writer and N readers:
cargo run -p dht_sync_lag -- --agents 3 --duration 60 \
--behaviour=write:1 --behaviour=record_lag:2 --reporter=influx-fileKey metrics: sync_lag (per request, seconds), sent_count, recv_count.
Single-host assumption: all agents share a wall clock, so now − authored_at
needs no clock-skew correction.
Verified live (2026-06-12, 3 agents, 45s): the writer submitted 288
requests; two readers on separate conductors observed them propagate
(287/288 and 232/288), recording 525 sync_lag samples — median ≈ 185 ms,
p90 ≈ 8.7 s (the gossip-under-load tail). This was the first live run of any
ValiChord wind-tunnel scenario; it's what surfaced the membrane-proof install
fix now shared across all four Holochain scenarios.
Benchmarks peer-to-peer message propagation at the Kitsune2 networking layer,
beneath ValiChord's DHT/DNA. Agents create an instrumented "chatter" instance,
join a shared network, and broadcast timestamped messages. Runs no ValiChord
code — it's the network baseline you subtract from dht_sync_lag.
Needs a Kitsune2 bootstrap server and an Iroh relay (the iroh/QUIC transport
uses the relay for NAT traversal / peer discovery). Confirm flags with --help:
cargo run -p kitsune_dht_propagation -- \
--bootstrap-server-url http://127.0.0.1:30000 \
--relay-url <iroh-relay-url> \
--agents 2 --duration 30NUM_MESSAGES (env, default 3) sets messages per interval.
The separate-relay requirement is on its way out — but not yet. Both
--bootstrap-server-urland--relay-urlare required args (they are non-optionalStrings inbindings/kitsune_runner/src/cli.rs), which is why this scenario has never had a live run here. kitsune2 merged3746be1— "stabilize authenticated iroh relay hosted in the bootstrap server" tomainon 2026-07-27, so one binary will eventually serve both flags. It is not usable yet: it landed afterv0.5.0-dev.6so it is in no published kitsune2 release, and the 0.5 line belongs to Holochain 0.7 while we pin 0.4.1. Re-check when we migrate to 0.7. (polite-shrink's Stage-2 harness solved the same problem locally by building its own combinedbootstrap_relaybinary — that remains the fallback if a live run is wanted sooner.)
Dependency pin — do not remove. This scenario's transitive iroh stack pulls
ed25519-dalek 3.0.0-pre.1, which only builds against the release-candidatepkcs8/ed25519crates (the final 0.11.0 / 3.0.0 releases changedpkcs8::Error::KeyMalformedto a tuple variant and break the build). The exact RC versions are pinned inkitsune_dht_propagation/Cargo.toml(matching upstream's own lockfile). An unconstrainedcargo updatewill try to undo this — if the Kitsune build breaks after a dep update, re-check those pins first. The Holochain scenarios are unaffected (they useed25519-dalek 2.x).
Default output is human-readable. For machine-readable metrics, append a reporter, e.g.:
cargo run -p concurrent_reveal_throughput -- --agents 8 --duration 120 --reporter=influx-file- Create
scenarios/<name>/{Cargo.toml,src/main.rs}(copy the closest existing scenario —validation_request_throughputfor a Holochain write scenario,dht_sync_lagfor a writer/reader split,kitsune_dht_propagationfor a Kitsune substrate test). - Register it under
membersinwind-tunnel/Cargo.toml. cargo build -p <name>to verify. Pure-logic helpers can have inline#[cfg(test)]unit tests (no conductor needed); live runs need the packed hApp + a conductor and are best left to CI / a beefy machine.