Introduction
ZombieNet SDK is a Rust testing framework for Polkadot SDK-based blockchains. It spawns ephemeral blockchain networks programmatically for testing and development.
The SDK succeeds the original ZombieNet (TypeScript), offering a type-safe, composable Rust API.
Migrating from TypeScript ZombieNet? The TOML configuration format is largely compatible. See Network Definition Spec for the full reference.
Key Features
- Programmatic Network Spawning - Define and spawn relay chains and parachains from Rust code
- Fluent Builder API - Compose networks using a chainable builder pattern
- Multiple Providers - Run on Kubernetes, Podman, Docker, or natively
- Metrics & Assertions - Query Prometheus metrics and validate network behavior
- Subxt Integration - Interact with networks using subxt
Use Cases
- Integration Testing - Test cross-chain functionality with complete networks
- CI/CD Pipelines - Automate network deployment and testing
- Development - Spin up local networks for development and debugging
- Parachain Testing - Test registration, block production etc.
Next
See Getting Started to spawn your first network.
Getting Started
This guide walks you through setting up ZombieNet SDK and spawning your first network.
- Setup - Prerequisites and installation
- Your First Network - Spawn a relay chain and parachain
Next
- Network Definition Spec - Configuration reference
- Providers - Native, Docker, Kubernetes
- CLI Usage - CLI reference
- Examples - More configurations and use cases
Setup
Prerequisites
- Rust - Install via rustup
- A provider (at least one):
- Native: Binaries used by your network in PATH (or absolute paths in config), e.g.,
polkadot,polkadot-parachain - Docker/Podman: Container runtime installed and running
- Kubernetes: Cluster access with
kubectlconfigured
- Native: Binaries used by your network in PATH (or absolute paths in config), e.g.,
Installation
CLI
Download pre-built binaries from GitHub Releases, or install via Cargo:
cargo install zombie-cli
Or build from source:
git clone https://github.com/paritytech/zombienet-sdk
cd zombienet-sdk
cargo build --release -p zombie-cli
# Binary will be at target/release/zombie-cli
As a Dependency
Add to your Cargo.toml:
[dependencies]
zombienet-sdk = "0.4"
Your First Network
Using TOML
Create network.toml:
[relaychain]
chain = "rococo-local"
default_command = "polkadot"
[[relaychain.nodes]]
name = "alice"
[[relaychain.nodes]]
name = "bob"
Spawn with the CLI:
zombie-cli spawn network.toml --provider native
# or: --provider docker
Programmatically
The same network in Rust:
use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let network = NetworkConfigBuilder::new() .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_validator(|v| v.with_name("alice")) .with_validator(|v| v.with_name("bob")) }) .build()? .spawn_native() .await?; let alice = network.get_node("alice")?; println!("Alice WS: {}", alice.ws_uri()); tokio::signal::ctrl_c().await?; Ok(()) }
Adding a Parachain
TOML
Extend network.toml:
[relaychain]
chain = "rococo-local"
default_command = "polkadot"
[[relaychain.nodes]]
name = "alice"
[[relaychain.nodes]]
name = "bob"
[[parachains]]
id = 1000
[[parachains.collators]]
name = "collator01"
command = "polkadot-parachain"
Programmatically
use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let network = NetworkConfigBuilder::new() .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_validator(|v| v.with_name("alice")) .with_validator(|v| v.with_name("bob")) }) .with_parachain(|p| { p.with_id(1000) .with_collator(|c| { c.with_name("collator01") .with_command("polkadot-parachain") }) }) .build()? .spawn_native() .await?; let collator = network.get_node("collator01")?; println!("Collator WS: {}", collator.ws_uri()); tokio::signal::ctrl_c().await?; Ok(()) }
How It Works
This document covers the two core aspects of what ZombieNet does under the hood: chain spec customization and command-line argument generation.
Chain Spec Customization
When spawning a network, ZombieNet modifies chain specifications to configure the test environment. These customizations happen automatically based on your network configuration.
Relay Chain Customizations
The framework applies these modifications to relay chain specs:
| Customization | Description |
|---|---|
| Runtime Genesis Patch | Merges user-provided genesis_overrides JSON (applied first) |
| Clear Authorities | Removes existing authorities from session/keys, aura/authorities, grandpa/authorities, collatorSelection/invulnerables, and staking/invulnerables + staking/stakers (relay only). Also sets validatorCount to 0 unless devStakers is configured. |
| Add Balances | Adds balances for each node's sr and sr_stash accounts. Balance is max(initial_balance, staking_min * 2). Only nodes with initial_balance > 0 receive balances. |
| Add Zombie Account | Adds //Zombie account with 1000 units (in chain denomination, i.e., 1000 * 10^token_decimals planck) for internal operations |
| Add Staking | Configures staking for validators with minimum stake |
| Add Session Authorities | Adds validators as session authorities (if session pallet exists) |
| Add Aura/Grandpa Authorities | For chains without session pallet |
| Add HRMP Channels | Configures cross-chain messaging channels |
| Register Parachains | Adds InGenesis parachains to genesis |
Parachain Customizations
For cumulus-based parachains:
| Customization | Description |
|---|---|
| Override para_id/paraId | Sets correct parachain ID in chain spec root (both para_id and paraId variants) |
| Override relay_chain | Sets relay chain ID in chain spec root |
| Apply Genesis Overrides | Merges user-provided JSON overrides |
| Clear/Add Authorities | Clears existing authorities from session/keys, aura/authorities, grandpa/authorities, collatorSelection/invulnerables. Then adds collator session keys (if session pallet exists) or aura authorities. |
| Add Collator Selection | Adds invulnerable collators to collatorSelection/invulnerables |
| Override parachainInfo | Sets correct parachainId in /parachainInfo/parachainId |
| Add Balances | Extracts accounts from assets pallet metadata and ensures they have native token balances. Adds balances for collator sr accounts. |
For EVM-based parachains (is_evm_based = true), Ethereum session keys are generated and used instead of standard keys.
Chain Spec Key Types
ZombieNet supports customizing session key types and their cryptographic schemes via the chain_spec_key_types configuration option.
Cryptographic Schemes
| Scheme | Suffix | Description |
|---|---|---|
| SR25519 | _sr | Schnorr signatures, used by most runtime keys |
| ED25519 | _ed | Edwards curve signatures, used by grandpa |
| ECDSA | _ec | Elliptic curve, used by beefy and Ethereum compatibility |
Predefined Key Types
These key types have predefined default schemes:
| Key Type | Default Scheme | Notes |
|---|---|---|
aura | SR25519 | ED25519 on asset-hub-polkadot |
babe | SR25519 | |
grandpa | ED25519 | |
beefy | ECDSA | |
im_online | SR25519 | |
authority_discovery | SR25519 | |
para_validator | SR25519 | |
para_assignment | SR25519 | |
parachain_validator | SR25519 | |
nimbus | SR25519 | |
vrf | SR25519 |
Syntax
Two formats are supported:
- Short form:
aura— uses the predefined default scheme - Long form:
aura_ed— explicitly specifies the scheme
Unknown key types default to SR25519 when using short form.
Example
[relaychain]
chain_spec_key_types = ["aura", "grandpa", "beefy"] # Uses defaults
# Or with explicit schemes:
chain_spec_key_types = ["aura_ed", "grandpa_sr", "custom_key_ec"]
Optional Overrides
Two additional overrides can be applied after the raw chain spec is generated:
- WASM Override (
wasm_override): Replaces runtime code (:codestorage key) with custom WASM - Raw Spec Override (
raw_spec_override): Applies JSON patch merge to override any part of the raw spec
Command-Line Arguments
ZombieNet generates command-line arguments for each spawned node. Understanding what gets added helps debug issues and customize node behavior.
Framework-Managed Arguments
These arguments are always set by the framework and filtered out if provided by user configuration:
| Argument | Value |
|---|---|
--chain | Generated chain spec path |
--name | Node name from config |
--rpc-cors | all |
--rpc-methods | unsafe |
--parachain-id | Parachain ID (relay chain nodes only, when para_id is specified; NOT used for cumulus collators) |
--node-key | Deterministic key derived from node name |
Additionally, these flags are managed by the framework:
| Flag | When Added |
|---|---|
--no-telemetry | Relay chain nodes only (not cumulus collators) |
--collator | Cumulus collators with is_validator: true |
--execution wasm | Embedded relay chain full node (after -- separator) |
Arguments Added Conditionally
| Argument | Condition |
|---|---|
--validator | Relay chain nodes with is_validator: true |
--insecure-validator-i-know-what-i-do | Validators, if the binary supports it |
--prometheus-external | Unless already in user args |
--unsafe-rpc-external | Only for Docker/Kubernetes providers (not native) |
--bootnodes | When bootnode addresses are available |
Port Arguments
The framework injects port arguments based on assigned ports:
| Argument | Description |
|---|---|
--prometheus-port | Prometheus metrics port |
--rpc-port | RPC/WebSocket port |
--listen-addr | P2P listen address (format: /ip4/0.0.0.0/tcp/{port}/ws) |
--base-path | Data directory path |
Cumulus Collator Arguments
Cumulus-based collators receive two sets of arguments separated by --:
Before -- (collator arguments):
--chain(parachain spec)--collator--prometheus-port,--rpc-port,--listen-addr--base-path- User-provided collator args
After -- (embedded relay chain full node):
--base-path(separate relay data directory)--chain(relay chain spec)--execution wasm(hardcoded, always WASM execution)--port(full node P2P port, injected by framework when assigned port differs from default 30333)--prometheus-port(full node metrics port, injected by framework when assigned port differs from default 9616)- User-provided full node args
Removing Framework Arguments
Use the -: prefix to remove arguments that the framework adds:
[[relaychain.nodes]]
name = "bob"
args = ["-:--insecure-validator-i-know-what-i-do"]
This removes --insecure-validator-i-know-what-i-do from bob's command line.
The removal syntax:
-:--flag-nameremoves--flag-name-:flag-namealso removes--flag-name(normalized automatically)- Works for both flags and options (the option's value is also removed)
Example: Generated Relay Chain Command
polkadot \
--chain /cfg/rococo-local.json \
--name alice \
--rpc-cors all \
--rpc-methods unsafe \
--unsafe-rpc-external \ # omitted for native provider
--node-key <deterministic_key> \
--prometheus-external \
--validator \
--insecure-validator-i-know-what-i-do \ # if binary supports it
--bootnodes <bootnode_multiaddrs> \
--prometheus-port 9615 \
--rpc-port 9944 \
--listen-addr /ip4/0.0.0.0/tcp/30333/ws \
--base-path /data \
--no-telemetry \
[user_args...]
Example: Generated Cumulus Collator Command
polkadot-parachain \
--chain /cfg/1000.json \
--name collator01 \
--rpc-cors all \
--rpc-methods unsafe \
--unsafe-rpc-external \ # omitted for native provider
--node-key <deterministic_key> \
--prometheus-external \
--collator \ # if is_validator: true
--bootnodes <para_bootnode_multiaddrs> \
--prometheus-port 9615 \
--rpc-port 9944 \
--listen-addr /ip4/0.0.0.0/tcp/30333/ws \
--base-path /data \
[user_collator_args...] \
-- \
--base-path /relay-data \
--chain /cfg/rococo-local.json \
--execution wasm \
--port 30334 \ # if assigned port differs from default
--prometheus-port 9616 \ # if assigned port differs from default
[user_full_node_args...]
Network Definition Spec
Configuration via TOML files or the builder API.
- Global Settings - Timeouts, concurrency, base directory
- Relaychain - Relay chain configuration
- Parachain - Parachain and collator configuration
- Node - Node, node groups, and resources
- HRMP Channels - Cross-chain messaging
- Custom Processes - Custom process to spawn
- Chain Spec - Chain spec generation options
- Examples - Complete configuration examples
Global Settings
TOML
[settings]
timeout = 3600
node_spawn_timeout = 600
local_ip = "127.0.0.1"
base_dir = "/tmp/zombienet"
spawn_concurrency = 4
tear_down_on_failure = true
bootnodes_addresses = ["/ip4/127.0.0.1/tcp/30333/p2p/12D3KooW..."]
Builder
#![allow(unused)] fn main() { use zombienet_sdk::NetworkConfigBuilder; let config = NetworkConfigBuilder::new() .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_validator(|v| v.with_name("alice")) }) .with_global_settings(|gs| { gs.with_network_spawn_timeout(3600) .with_node_spawn_timeout(600) .with_local_ip("127.0.0.1") .with_base_dir("/tmp/zombienet") .with_spawn_concurrency(4) }) .build() .unwrap(); }
Reference
| Option | Type | Default | Description |
|---|---|---|---|
timeout | Number | 3600 | Global timeout (seconds) for network spawn |
node_spawn_timeout | Number | 600 | Individual node spawn timeout (seconds). Override with ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS env var |
local_ip | String | 127.0.0.1 | Local IP for exposed services |
base_dir | String | Random temp dir | Base directory for network artifacts |
spawn_concurrency | Number | — | Number of concurrent spawn processes. Override with ZOMBIE_SPAWN_CONCURRENCY env var |
tear_down_on_failure | Boolean | true | Tear down network if nodes become unresponsive |
bootnodes_addresses | Array | — | External bootnode multiaddresses |
Relaychain
TOML
[relaychain]
chain = "rococo-local"
default_command = "polkadot"
default_image = "parity/polkadot:latest"
default_args = ["-lruntime=debug"]
# Optional: Use a pre-existing chain spec
# chain_spec_path = "/path/to/chain-spec.json"
# Optional: Generate chain spec with a command
# chain_spec_command = "polkadot build-spec --chain {chain}"
# Optional: Runtime for chain spec generation
# chain_spec_runtime.src = "/path/to/runtime.wasm"
# chain_spec_runtime.preset = "development"
# Genesis configuration
random_nominators_count = 4
max_nominations = 16
# Genesis patch (applied to chain spec)
[relaychain.genesis.balances]
balances = [["5GrwvaEF...", 1000000000000]]
[[relaychain.nodes]]
name = "alice"
invulnerable = true
[[relaychain.nodes]]
name = "bob"
args = ["-lparachain=trace"]
Builder
#![allow(unused)] fn main() { use zombienet_sdk::NetworkConfigBuilder; let config = NetworkConfigBuilder::new() .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_default_image("parity/polkadot:latest") .with_validator(|v| v.with_name("alice").invulnerable(true)) .with_validator(|v| v.with_name("bob")) }) .build() .unwrap(); }
Reference
| Option | Type | Default | Description |
|---|---|---|---|
chain | String | rococo-local | Chain name (e.g., polkadot, rococo-local, westend) |
default_command | String | polkadot | Default binary command for nodes |
default_image | String | — | Default container image (Docker/Podman/K8s) |
default_args | Array | — | Default CLI arguments for all nodes |
default_resources | Object | — | Default resource limits (see Resources) |
default_db_snapshot | String | — | Default database snapshot path/URL |
chain_spec_path | String | — | Path or URL to pre-existing chain spec |
chain_spec_command | String | — | Command template to generate chain spec |
chain_spec_command_is_local | Boolean | false | Run chain spec command locally |
chain_spec_command_output_path | String | /dev/stdout | Output path for chain spec command |
chain_spec_runtime | Object | — | Runtime WASM for chain spec generation (see Chain Spec) |
random_nominators_count | Number | — | Number of random nominators for staking |
max_nominations | Number | — | Maximum nominations per nominator |
genesis | Object | — | Genesis overrides as JSON (alias: runtime_genesis_patch) |
wasm_override | String | — | WASM runtime override path/URL |
raw_spec_override | String/Object | — | Raw chain spec override (inline JSON or file path) |
Parachain
TOML
[[parachains]]
id = 1000
chain = "asset-hub-rococo-local"
onboard_as_parachain = true
default_command = "polkadot-parachain"
balance = 2000000000000
# Registration strategy (pick one):
# add_to_genesis = true # InGenesis (default, fastest)
# register_para = true # UsingExtrinsic (via extrinsic after spawn)
# (neither = Manual, no automatic registration)
# Genesis configuration
# genesis_wasm_path = "/path/to/para-wasm"
# genesis_state_path = "/path/to/para-genesis-state"
# Or generate genesis artifacts
# genesis_wasm_generator = "polkadot-parachain export-genesis-wasm"
# genesis_state_generator = ["polkadot-parachain", "export-genesis-state"]
# Genesis patch
[parachains.genesis.balances]
balances = [["5GrwvaEF...", 1000000000000]]
[[parachains.collators]]
name = "collator01"
command = "polkadot-parachain"
[[parachains.collators]]
name = "collator02"
args = ["-lparachain=trace"]
Builder
#![allow(unused)] fn main() { use zombienet_sdk::{NetworkConfigBuilder, RegistrationStrategy}; let config = NetworkConfigBuilder::new() .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_validator(|v| v.with_name("alice")) .with_validator(|v| v.with_name("bob")) }) .with_parachain(|p| { p.with_id(1000) .with_collator(|c| c.with_name("collator01").with_command("polkadot-parachain")) }) .build() .unwrap(); }
Reference
| Option | Type | Default | Description |
|---|---|---|---|
id | Number | — | Required. Unique parachain ID |
chain | String | — | Chain name for the parachain |
cumulus_based | Boolean | true | Whether parachain uses Cumulus |
evm_based | Boolean | false | Whether parachain is EVM-based (e.g., Frontier) |
onboard_as_parachain | Boolean | true | Register as parachain (vs parathread) |
add_to_genesis | Boolean | — | Register in genesis (InGenesis strategy). Mutually exclusive with register_para |
register_para | Boolean | — | Register via extrinsic (UsingExtrinsic strategy). Mutually exclusive with add_to_genesis |
balance | Number | 2000000000000 | Initial balance for parachain account |
default_command | String | — | Default collator command |
default_image | String | — | Default container image |
default_args | Array | — | Default CLI arguments |
default_resources | Object | — | Default resource limits (see Resources) |
default_db_snapshot | String | — | Default database snapshot path/URL |
genesis_wasm_path | String | — | Path/URL to genesis WASM blob |
genesis_wasm_generator | String | — | Command to generate genesis WASM |
genesis_state_path | String | — | Path/URL to genesis state |
genesis_state_generator | String/Array | — | Command with args to generate genesis state |
genesis | Object | — | Genesis overrides as JSON (alias: runtime_genesis_patch) |
chain_spec_path | String | — | Path/URL to parachain chain spec |
chain_spec_command | String | — | Command template to generate chain spec |
chain_spec_command_is_local | Boolean | false | Run chain spec command locally |
chain_spec_command_output_path | String | /dev/stdout | Output path for chain spec command |
chain_spec_runtime | Object | — | Runtime WASM for chain spec generation |
wasm_override | String | — | WASM runtime override path/URL |
raw_spec_override | String/Object | — | Raw chain spec override (inline JSON or file path) |
bootnodes_addresses | Array | — | Bootnode multiaddresses |
no_default_bootnodes | Boolean | false | Don't auto-assign bootnode role if none specified |
Registration Strategies
- InGenesis - In relay chain genesis (fastest)
- UsingExtrinsic - Via extrinsic after spawn
- Manual - No automatic registration
Collator Groups
[[parachains.collator_groups]]
name = "collator"
count = 3
command = "polkadot-parachain"
#![allow(unused)] fn main() { .with_parachain(|p| { p.with_id(1000) .with_collator_group(|g| { g.with_count(3) .with_base_node(|n| n.with_name("collator").with_command("polkadot-parachain")) }) }) }
Node
Validators, full nodes, and collators share this configuration.
TOML
[[relaychain.nodes]]
name = "alice"
command = "polkadot"
# subcommand = "node" # Optional: for nested CLI commands
image = "parity/polkadot:latest"
invulnerable = true
bootnode = false
initial_balance = 5000000000000
args = ["-lruntime=debug"]
env = [
{ name = "RUST_LOG", value = "info" }
]
# Networking
rpc_port = 9933
prometheus_port = 9615
p2p_port = 30333
# p2p_cert_hash = "..." # Optional: libp2p WebRTC cert hash
bootnodes_addresses = ["/ip4/127.0.0.1/tcp/30333/p2p/12D3KooW..."]
# Storage & Keys
db_snapshot = "/path/to/snapshot.tar.gz"
keystore_path = "/path/to/keystore"
keystore_key_types = ["aura", "gran"]
# override_eth_key = "0x..." # Optional: override EVM session key
node_log_path = "/tmp/alice.log"
# Resources (for container providers)
[relaychain.nodes.resources]
request_memory = "1Gi"
request_cpu = "500m"
limit_memory = "2Gi"
limit_cpu = "1000m"
Builder
#![allow(unused)] fn main() { .with_validator(|node| { node.with_name("alice") .with_command("polkadot") .with_image("parity/polkadot:latest") .invulnerable(true) .with_args(vec!["-lruntime=debug".into()]) .with_env(vec![("RUST_LOG".into(), "info".into())]) .with_resources(|r| { r.with_request_memory("1Gi").with_limit_memory("2Gi") }) }) }
Reference
| Option | Type | Default | Description |
|---|---|---|---|
name | String | — | Required. Unique node name |
command | String | Inherited | Binary command to execute |
subcommand | String | — | Optional subcommand (e.g., for nested CLI) |
image | String | Inherited | Container image |
args | Array | — | CLI arguments |
env | Array | — | Environment variables as {name, value} pairs |
validator | Boolean | true | Whether node is a validator/authority |
invulnerable | Boolean | true | Add to invulnerables set in genesis |
bootnode | Boolean | false | Whether node acts as a bootnode |
initial_balance | Number | 2000000000000 | Initial account balance (alias: balance) |
rpc_port | Number | Auto | RPC port |
prometheus_port | Number | Auto | Prometheus metrics port |
p2p_port | Number | Auto | P2P networking port |
p2p_cert_hash | String | — | libp2p WebRTC certificate hash |
bootnodes_addresses | Array | — | Additional bootnode addresses |
db_snapshot | String | — | Database snapshot path/URL |
keystore_path | String | — | Custom keystore directory |
keystore_key_types | Array | — | Key types to generate in keystore (e.g., aura, gran, babe) |
chain_spec_key_types | Array | — | Session key types to inject into chain spec genesis |
override_eth_key | String | — | Override auto-generated EVM session key |
node_log_path | String | — | Path for node log file |
resources | Object | — | Resource limits (see below) |
Node Groups
Define multiple nodes with the same configuration. Creates validator-0, validator-1, etc.
TOML
[[relaychain.node_groups]]
name = "validator"
count = 5
command = "polkadot"
Builder
#![allow(unused)] fn main() { .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_node_group(|g| { g.with_base_node(|node| { node.with_name("validator") .with_args(vec!["-lruntime=debug".into()]) }) .with_count(5) }) }) }
Resources
For container providers (Docker, Podman, Kubernetes).
[relaychain.default_resources]
request_memory = "1Gi"
request_cpu = "500m"
limit_memory = "2Gi"
limit_cpu = "1000m"
Reference
| Option | Type | Description |
|---|---|---|
request_memory | String | Requested memory (e.g., 512Mi, 1Gi) |
request_cpu | String | Requested CPU (e.g., 250m, 1) |
limit_memory | String | Memory limit |
limit_cpu | String | CPU limit |
HRMP Channels
Cross-chain messaging between parachains.
TOML
[[hrmp_channels]]
sender = 1000
recipient = 2000
max_capacity = 8
max_message_size = 512
[[hrmp_channels]]
sender = 2000
recipient = 1000
max_capacity = 8
max_message_size = 512
Builder
#![allow(unused)] fn main() { let config = NetworkConfigBuilder::new() .with_relaychain(|r| { /* ... */ }) .with_parachain(|p| p.with_id(1000).with_collator(|c| c.with_name("col1"))) .with_parachain(|p| p.with_id(2000).with_collator(|c| c.with_name("col2"))) .with_hrmp_channel(|h| h.with_sender(1000).with_recipient(2000)) .with_hrmp_channel(|h| h.with_sender(2000).with_recipient(1000)) .build() .unwrap(); }
Reference
| Option | Type | Default | Description |
|---|---|---|---|
sender | Number | — | Required. Sending parachain ID |
recipient | Number | — | Required. Receiving parachain ID |
max_capacity | Number | 8 | Maximum messages in channel |
max_message_size | Number | 512 | Maximum message size in bytes |
Custom Processes
Allow to set custom processes to spawn after the network is up.
TOML
[[custom_processes]]
name = "eth-rpc"
command = "eth-rpc"
args = [ "--flag", "--other=1" ]
env = [
{ name = "RUST_LOG", value = "info" }
]
[[custom_processes]]
name = "other"
command = "some-binary"
args = [ "--flag", "--other-flag" ]
Builder
#![allow(unused)] fn main() { let config = NetworkConfigBuilder::new() .with_relaychain(|r| { /* ... */ }) .with_custom_process(|c| c.with_name("eth-rpc").with_command("eth-rpc")) }
#![allow(unused)] fn main() { let cp = CustomProcessBuilder::new() .with_name("eth-rpc") .with_command("eth-rpc") .with_args(vec!["--flag".into()]) .with_env(vec![("Key", "Value")]) .build() .unwrap(); }
Reference
| Option | Type | Default | Description |
|---|---|---|---|
name | String | — | Required. Name of the process |
command | String | — | Required. Command to execute |
image | String | - | Container image |
args | Array | — | CLI arguments |
env | Array | — | Environment variables as {name, value} pairs |
Chain Spec Generation
Priority: chain_spec_path > chain_spec_runtime > chain_spec_command > auto-generation.
Pre-existing
chain_spec_path = "/path/to/chain-spec.json" # or URL
Via Command
chain_spec_command = "polkadot build-spec --chain {chain} --disable-default-bootnode"
Via Runtime
[relaychain.chain_spec_runtime]
src = "/path/to/runtime.wasm"
preset = "development" # optional
Complete Example
Relay chain with two parachains and HRMP channels.
TOML
[settings]
timeout = 600
[relaychain]
chain = "rococo-local"
default_command = "polkadot"
default_args = ["-lruntime=debug"]
[[relaychain.nodes]]
name = "alice"
[[relaychain.nodes]]
name = "bob"
[[parachains]]
id = 1000
[[parachains.collators]]
name = "collator-1000"
command = "polkadot-parachain"
[[parachains]]
id = 2000
[[parachains.collators]]
name = "collator-2000"
command = "polkadot-parachain"
[[hrmp_channels]]
sender = 1000
recipient = 2000
[[hrmp_channels]]
sender = 2000
recipient = 1000
Builder
use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let network = NetworkConfigBuilder::new() .with_relaychain(|r| { r.with_chain("rococo-local") .with_default_command("polkadot") .with_validator(|v| v.with_name("alice")) .with_validator(|v| v.with_name("bob")) }) .with_parachain(|p| { p.with_id(1000) .with_collator(|c| c.with_name("collator-1000").with_command("polkadot-parachain")) }) .with_parachain(|p| { p.with_id(2000) .with_collator(|c| c.with_name("collator-2000").with_command("polkadot-parachain")) }) .with_hrmp_channel(|h| h.with_sender(1000).with_recipient(2000)) .with_hrmp_channel(|h| h.with_sender(2000).with_recipient(1000)) .build() .unwrap() .spawn_native() .await?; Ok(()) }
Providers
Providers determine how ZombieNet spawns and manages nodes. Choose based on your environment and requirements.
Quick Comparison
| Provider | Runs As | Image Required | Resource Limits |
|---|---|---|---|
| Native | Local processes | No | No |
| Docker | Containers | Yes | No |
| Kubernetes | K8s pods | Yes | Yes |
Provider Selection
Set the default provider via environment variable:
export ZOMBIE_PROVIDER=native # or: docker, k8s
Or specify per-command with --provider flag. Default is docker.
Native
Runs nodes directly on your machine as local processes.
Requirements
- Node binaries in PATH (e.g.,
polkadot,polkadot-parachain) - Or specify absolute paths in configuration
Usage
CLI:
zombie-cli spawn network.toml --provider native
Programmatically:
#![allow(unused)] fn main() { let network = config.spawn_native().await?; }
Notes
- Fastest startup, no container overhead
- No container images needed
- Ports are assigned randomly to avoid conflicts
- Files stored in a temp directory (or
base_dirif configured)
Docker
Runs nodes in Docker containers.
Requirements
- Docker or Podman installed and running
- Container images with node binaries
Usage
CLI:
zombie-cli spawn network.toml --provider docker
Programmatically:
#![allow(unused)] fn main() { let network = config.spawn_docker().await?; }
Default Images
Override via environment variables:
| Variable | Default | Description |
|---|---|---|
POLKADOT_IMAGE | docker.io/parity/polkadot:latest | Relay chain nodes |
CUMULUS_IMAGE | docker.io/parity/polkadot-parachain:latest | Parachain collators |
MALUS_IMAGE | docker.io/paritypr/malus:latest | Malus (malicious node) |
COL_IMAGE | docker.io/paritypr/colander:latest | Colander |
Kubernetes
Deploys nodes as Kubernetes pods.
Requirements
- Kubernetes cluster access
kubectlconfigured with valid kubeconfig- Container images accessible to the cluster
Usage
CLI:
zombie-cli spawn network.toml --provider k8s
Programmatically:
#![allow(unused)] fn main() { let network = config.spawn_k8s().await?; }
Attaching to Running Networks
Reconnect to a previously spawned network, currently running network using the zombie.json state file:
#![allow(unused)] fn main() { use zombienet_sdk::{AttachToLive, AttachToLiveNetwork}; // Native let network = AttachToLiveNetwork::attach_native("/tmp/zombie-1/zombie.json".into()).await?; // Docker let network = AttachToLiveNetwork::attach_docker("/tmp/zombie-1/zombie.json".into()).await?; // Kubernetes let network = AttachToLiveNetwork::attach_k8s("/tmp/zombie-1/zombie.json".into()).await?; }
The zombie.json file is written to the network's base directory after spawn completes.
CLI Usage
The zombie-cli command-line tool spawns and manages ZombieNet networks.
Installation
Pre-built binaries:
Download from GitHub Releases.
From source:
cargo install zombie-cli
spawn
Spawn a network from a configuration file.
zombie-cli spawn <CONFIG> [OPTIONS]
Arguments
| Argument | Required | Description |
|---|---|---|
CONFIG | Yes | Path to TOML configuration file |
Options
| Option | Short | Default | Description |
|---|---|---|---|
--provider | -p | docker | Provider: native, docker, or k8s |
--dir | -d | Random temp | Base directory for network files |
--spawn-concurrency | -c | 100 | Concurrent node spawn processes |
--node-verifier | -v | metric | Readiness check: metric or none |
Examples
# Spawn with native provider
zombie-cli spawn network.toml --provider native
# Spawn with custom directory
zombie-cli spawn network.toml --dir /tmp/my-network
# Spawn with Docker (default)
zombie-cli spawn network.toml
# Spawn on Kubernetes
zombie-cli spawn network.toml --provider k8s
reproduce
Reproduce CI test runs locally in Docker. Downloads nextest archives from GitHub Actions and runs them in a containerized environment matching CI.
zombie-cli reproduce <REPO> <RUN_ID> [OPTIONS]
zombie-cli reproduce --archive <PATH> [OPTIONS]
Arguments
| Argument | Required | Description |
|---|---|---|
REPO | Yes* | Repository name (e.g., zombienet-sdk) |
RUN_ID | Yes* | GitHub Actions run ID (find in the URL of a workflow run) |
*Not required when using --archive.
Options
| Option | Short | Description |
|---|---|---|
--archive | -a | Path to local nextest archive (.tar.zst) |
--artifact-pattern | -p | Filter artifact downloads by pattern (default: *zombienet-artifacts*) |
--skip | -s | Skip tests matching pattern (repeatable) |
[TEST_FILTER] | Additional arguments passed to nextest (use -- separator) |
Requirements
- Docker installed and running
- GitHub CLI (
gh) installed and authenticated (for downloading artifacts) POLKADOT_SDK_PATHenvironment variable pointing to your polkadot-sdk workspace
How It Works
- Downloads nextest test archives (
.tar.zst) from GitHub Actions - Optionally downloads binary artifacts matching the pattern
- Runs tests inside a Docker container with:
ZOMBIE_PROVIDER=native(native provider inside container)RUST_LOG=debugfor verbose output- Mounted polkadot-sdk workspace
- Extracts binaries to
/tmp/binariesinside the container
Examples
# Reproduce from GitHub Actions (requires gh CLI authentication)
POLKADOT_SDK_PATH=/path/to/polkadot-sdk \
zombie-cli reproduce zombienet-sdk 123456789
# Reproduce from local archive (no gh CLI needed)
POLKADOT_SDK_PATH=/path/to/polkadot-sdk \
zombie-cli reproduce --archive ./my-archive.tar.zst
# Download only artifacts matching "ray" pattern
zombie-cli reproduce zombienet-sdk 123456789 -p ray
# Skip specific slow tests
zombie-cli reproduce zombienet-sdk 123456789 --skip "slow_test"
# Pass filters to nextest
zombie-cli reproduce zombienet-sdk 123456789 -- --test-filter my_test
Nextest Archive Format
The .tar.zst archive is a nextest archive containing compiled test binaries and metadata. These are produced by CI runs using cargo nextest archive.
Environment Variables
| Variable | Command | Description |
|---|---|---|
RUST_LOG | All | Logging level (e.g., debug, info) |
ZOMBIE_SPAWN_CONCURRENCY | spawn | Override spawn concurrency |
POLKADOT_SDK_PATH | reproduce | Path to polkadot-sdk workspace |
POLKADOT_IMAGE | spawn | Override Polkadot container image |
CUMULUS_IMAGE | spawn | Override Cumulus container image |
Examples
All examples are in crates/examples/.
Run Rust examples with cargo run --example <example_name>.
Spawn from config files with zombie-cli spawn -p <provider> <config-file>.
Basic Network Setup
Start here to understand network configuration patterns.
| Example | Description |
|---|---|
simple_network_example | Minimal relay chain with two validators using TOML config |
small_network_with_default | Override default command and image for all nodes |
small_network_with_base_dir | Custom base directory for node data |
small_network_config | Minimal programmatic configuration |
Parachain Lifecycle
Configure, register, and manage parachains.
| Example | Description |
|---|---|
small_network_with_para | Basic relay + parachain topology |
register_para | Register parachain via extrinsic |
add_para | Add parachain to running network |
para_upgrade | Runtime upgrade on running parachain |
two_paras_same_id | Handling duplicate parachain IDs |
Node Groups
Scale networks with grouped nodes.
| Example | Description |
|---|---|
big_network_with_group_nodes | Programmatic network with grouped nodes |
network_example_with_group_nodes | TOML config with group nodes |
Advanced Configuration
| Example | Description |
|---|---|
resource_limits | CPU and memory limits for containers |
wasm-override | Custom WASM runtime override |
arg_removal | Remove default CLI arguments |
db_snapshot | Database snapshots for faster init |
docker_db_snapshot | DB snapshots in Docker environments |
raw_spec_override | Override raw chain spec fields |
Chain Spec Generation
| Example | Description |
|---|---|
chain_spec_generation | Dynamic chain spec generation |
chain_spec_runtime_kusama | Kusama with custom runtime WASM |
polkadot_people_wasm_runtime | Polkadot + People parachain with custom runtimes |
genesis_state_generator_example | Custom genesis state generator |
Keys and Security
| Example | Description |
|---|---|
keystore_key_types | Keystore directories and key type validation |
chain_spec_key_types | Chain spec session key configuration |
evm_parachain_session_key | EVM parachain session keys |
Network Utilities
| Example | Description |
|---|---|
from_live | Attach to running network via zombie.json |
test_run_script | Run scripts on nodes |
Config Files
Example TOML configs are in crates/examples/examples/configs/:
| Config | Description |
|---|---|
simple.toml | Basic two-validator relay chain |
simple-group-nodes.toml | Relay chain with node groups |
resource_limits.toml | Container resource limits |
wasm-override.toml | WASM runtime override |
arg-removal.toml | CLI argument removal |
polkadot-ah-chain-spec-runtime.toml | Polkadot + Asset Hub with custom runtimes |
Environment Variables
ZombieNet SDK uses environment variables for configuration overrides, container images, and CI integration.
Container Images
Override default images for Docker and Kubernetes providers:
| Variable | Default | Description |
|---|---|---|
POLKADOT_IMAGE | docker.io/parity/polkadot:latest | Relay chain node image |
CUMULUS_IMAGE | docker.io/parity/polkadot-parachain:latest | Parachain collator image |
MALUS_IMAGE | docker.io/paritypr/malus:latest | Malus (malicious node) image |
COL_IMAGE | docker.io/paritypr/colander:latest | Colander image |
Example:
POLKADOT_IMAGE=docker.io/parity/polkadot:v1.5.0 zombie-cli spawn network.toml
Provider Selection
| Variable | Values | Default | Description |
|---|---|---|---|
ZOMBIE_PROVIDER | native, k8s, docker | docker | Default provider when not specified |
Example:
ZOMBIE_PROVIDER=native zombie-cli spawn network.toml
Spawn Control
| Variable | Description |
|---|---|
ZOMBIE_SPAWN_CONCURRENCY | Override spawn concurrency (default: 100). Set to lower values for resource-constrained environments. |
ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS | Override per-node spawn timeout in seconds |
Example:
ZOMBIE_SPAWN_CONCURRENCY=10 zombie-cli spawn network.toml
CLI-Specific
| Variable | Command | Description |
|---|---|---|
POLKADOT_SDK_PATH | reproduce | Path to local polkadot-sdk workspace (required for reproduce) |
RUST_LOG | All | Logging level (debug, info, warn, error) |
Other
| Variable | Description |
|---|---|
ZOMBIE_RM_TGZ_AFTER_EXTRACT | Remove tgz archives after extraction (used for db snapshot extraction) |
TOML Template Replacement
Environment variables can be referenced in TOML configuration files using {{VAR_NAME}} syntax:
[relaychain]
default_image = "{{POLKADOT_IMAGE}}"
[[relaychain.nodes]]
name = "alice"
image = "{{CUSTOM_NODE_IMAGE}}"
Any environment variable can be substituted. If the variable is not set, the placeholder remains unchanged.
Runtime Token Replacement
For dynamic values from running nodes, use {{ZOMBIE:node_name:field}} syntax in node arguments:
[[relaychain.nodes]]
name = "bob"
args = ["--sync-target", "{{ZOMBIE:alice:multiaddr}}"]
Available fields:
| Field | Aliases | Description |
|---|---|---|
multiaddr | multiAddress | Node's libp2p multiaddress |
ws_uri | wsUri | WebSocket RPC endpoint |
prometheus_uri | prometheusUri | Prometheus metrics endpoint |
When using token replacement, spawn concurrency is automatically set to 1 (serial) to ensure dependencies are resolved correctly.