On this page
Local multi-validator network
You can run a real BFT network of several validators on your own machine, either as plain processes or in Docker with chaos tooling. This is how Phase 2 consensus is developed and tested.
Built (Phase 2) · Static validator set from the genesis file · Dev and test use only
Four validators as processes#
cargo build
# Keys, one shared genesis.json and node0..node3/config.toml
./target/debug/xdevs-cli testnet init --validators 4 --out ./testnet
# One terminal per validator
./target/debug/xdevs run --config ./testnet/node0/config.toml
./target/debug/xdevs run --config ./testnet/node1/config.toml
./target/debug/xdevs run --config ./testnet/node2/config.toml
./target/debug/xdevs run --config ./testnet/node3/config.toml
cast block-number --rpc-url http://127.0.0.1:30332All four validators must be up before the first block: one validator holds only 1/4 of the voting power.
Ports#
Node k uses ports base + 10k (default base 30300):
| Offset | Use | node0 | node1 | node2 | node3 |
|---|---|---|---|---|---|
| +0 | Consensus P2P | 30300 | 30310 | 30320 | 30330 |
| +1 | Transaction gossip | 30301 | 30311 | 30321 | 30331 |
| +2 | JSON-RPC (HTTP + WS) | 30302 | 30312 | 30322 | 30332 |
| +3 | Metrics ([metrics] listen) | 30303 | 30313 | 30323 | 30333 |
testnet init options#
| Option | Default | Meaning |
|---|---|---|
--validators N | required | Number of validators (equal voting power) |
--out DIR | required | Output directory (must be empty unless --force) |
--chain-id ID | 7117 | Chain id |
--block-time D | 2s | Target block time |
--base-port P | 30300 | First port; node k uses P + 10k … P + 10k + 3 |
--host H | 127.0.0.1 | Host the nodes listen on and reach each other at |
--full-nodes M | 0 | Extra full nodes (no consensus key) |
--password / --password-file FILE | none | Encrypt the consensus keys; configs point [validator] password_file at the file |
--light-kdf | off | Cheap scrypt parameters for encrypted keys |
--force | off | Replace an existing testnet in --out |
Each node directory contains consensus_key.hex (or consensus_key.json when encrypted), node_key.json (libp2p identity, same peer id on both networks), config.toml and, once running, data/. Every node lists every other node as a consensus peer and a gossip bootnode.
Single keys can be generated with:
xdevs-cli keygen --consensus --out consensus_key.json # Ed25519 validator key (encrypted keystore)
xdevs-cli keygen --node-key --out node_key.json # libp2p node identityWhat the network guarantees#
With four equal validators:
| Situation | Voting power online | Expected |
|---|---|---|
| All 4 up | 4/4 | Progress |
| 1 stopped, killed, paused or isolated | 3/4 > 2/3 | Progress; the node catches up when it returns |
| 2 stopped, or a 2–2 partition | 2/4 < 2/3 | Halt, then resume after recovery |
| Delay or loss on some links | 4/4 | Progress, possibly with more rounds |
| Any situation | Never a fork: same block hash and state root at every height on every node |
A new full node started late syncs from genesis and follows the head. Each validator keeps a persistent double-sign guard, so a restarted validator never signs two conflicting votes.
Docker localnet#
The Docker setup runs 4 validators, an optional late-joining full node and the load generator, with scripts to inject faults. It needs Linux or macOS with Docker Engine 25+ (on Windows, WSL 2 with Docker Desktop), bash 4+ and jq.
scripts/localnet-up.sh --build # build the image, start node0-3, wait for healthy
scripts/check.sh --wait-height 10 # all nodes at >= 10, same hashes and state roots
cast block-number --rpc-url http://127.0.0.1:8545 # node0
scripts/chaos.sh start node4 # late joiner, syncs from genesis
scripts/chaos.sh kill node1 # crash a validator
scripts/chaos.sh partition node0,node1 node2,node3 # the chain halts
scripts/chaos.sh heal # and resumes
scripts/localnet-down.sh # stop and wipeThe first image build takes 15–30 minutes and about 5 GB of disk. To skip it, use the image CI publishes: export XDEVS_IMAGE=ghcr.io/jacell100/xdevs-chain:main. In Docker, node RPCs are published on 127.0.0.1:8545, :8555, :8565, :8575 (and :8585 for the joiner).
CI runs the same scenarios on every pull request: stop one validator (chain continues), stop two (chain halts), restart (chain resumes), kill -9, pause, isolate, partition, network delay and loss, and a late joiner, followed by a deep fork scan. A separate soak workflow runs the network under load for up to 5.5 hours.
Full instructions: localnet runbook · soak runbook.
Limits today#
- The validator set is fixed in
genesis.json. Staking-driven validator changes, rewards and slashing are Phase 3. - Peers are static (
persistent_peers,bootnodes); Kademlia discovery is off. - P2P runs over TCP; QUIC is planned after a Linux soak.