Skip to content

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

Dev keys

testnet init writes unencrypted consensus keys by default, and the genesis funds the public Anvil dev accounts. Use these networks for development and testing only.

Four validators as processes ​

bash
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:30332

All 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):

OffsetUsenode0node1node2node3
+0Consensus P2P30300303103032030330
+1Transaction gossip30301303113032130331
+2JSON-RPC (HTTP + WS)30302303123032230332
+3Metrics ([metrics] listen)30303303133032330333

testnet init options ​

OptionDefaultMeaning
--validators NrequiredNumber of validators (equal voting power)
--out DIRrequiredOutput directory (must be empty unless --force)
--chain-id ID7117Chain id
--block-time D2sTarget block time
--base-port P30300First port; node k uses P + 10k … P + 10k + 3
--host H127.0.0.1Host the nodes listen on and reach each other at
--full-nodes M0Extra full nodes (no consensus key)
--password / --password-file FILEnoneEncrypt the consensus keys; configs point [validator] password_file at the file
--light-kdfoffCheap scrypt parameters for encrypted keys
--forceoffReplace 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:

bash
xdevs-cli keygen --consensus --out consensus_key.json   # Ed25519 validator key (encrypted keystore)
xdevs-cli keygen --node-key --out node_key.json         # libp2p node identity

What the network guarantees ​

With four equal validators:

SituationVoting power onlineExpected
All 4 up4/4Progress
1 stopped, killed, paused or isolated3/4 > 2/3Progress; the node catches up when it returns
2 stopped, or a 2–2 partition2/4 < 2/3Halt, then resume after recovery
Delay or loss on some links4/4Progress, possibly with more rounds
Any situationNever 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.

bash
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 wipe

The 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.

Built by THE xDEVS · Apache-2.0 · Testnet tokens (tXDS) have no monetary value.