Zombienet is an excellenet CLI tool to spawn a local test network. With Zombienet we can spawn a local relay chain with several validators as well as parachains with multiple collators.
To see the full list of features Zombienet offers, take a look at the README:
Setup a local Rococo testnet with the following:
You can use Zombienet with binaries, docker, kubernetes, and more.
In this tutorial we will setup Zombienet to use binaries.
In your root directory let's create a bin
folder to hold your binaries:
mkdir bin
The first binary we will need is Polkadot. This binary will be used to spin up our relay chain.
If you're running Linux you can skip this section and download your Polkadot binary from here:
First let's clone down Polkadot:
git clone https://github.com/paritytech/polkadot
cd
into the repo, checkout the latest release of Polkadot and compile:
git checkout release-v0.9.32
cargo build -release
We now have a Polkadot binary!
Copy the Polkadot binary into your bin
folder:
Tip: It is good practice to append the version of polkadot to your binary name. This is a nice way of organizing in your
bin
folder multiple versions on your binary.
cp ./target/release/polkadot ../bin/polkadot-v0.9.32
cd
back to your root directory
The next binary we will need is the substrate-parachain-template binary. We need this binary to spin up our parachains!
Let's clone down substrate-parachain-template:
git clone https://github.com/substrate-developer-hub/substrate-parachain-template
cd
into the repo, checkout the latest Polkadot release, and compile:
git checkout polkadot-v0.9.32
cargo build -release
We now have a parachain collator binary.
Copy the parachain binary into your bin
folder:
cp ./target/release/parachain-template-node ../bin/parachain-template-node-v0.9.32
Nice! We have all the binaries needed.
In this next section we will setup Zombienet and learn how to configure our testnet.
We will need to download the Zombienet executable.
You can find linux and macOS executables of the Zombienet CLI here:
Download the Zombienet CLI according to your operating system.
Tip: If you want the executable to be available system-wide then you can follow these steps (otherwise just download the executable to your working directory):
wget https://github.com/paritytech/zombienet/releases/download/v1.3.18/zombienet-macos chmod +x zombienet-macos cp zombienet-macos /usr/local/bin
Let's make sure Zombienet CLI is installed correctly:
./zombienet-macos --help
You should see some similar output:
Usage: zombienet [options] [command]
Options:
-c, --spawn-concurrency <concurrency> Number of concurrent spawning process to launch, default is 1
-p, --provider <provider> Override provider to use (choices: "podman", "kubernetes", "native")
-m, --monitor Start as monitor, do not auto cleanup network
-h, --help display help for command
Commands:
spawn <networkConfig> [creds] Spawn the network defined in the config
test <testFile> [runningNetworkSpec] Run tests on the network defined
setup <binaries...> Setup is meant for downloading and making dev environment of Zombienet ready
version Prints zombienet version
help [command] display help for command
Zombienet works with a config. The config is where you specify your test network's configuration. The config is also where we specify our binaries.
Create a config file:
touch config.toml
So we want to specify in our config that we want a Rococo relay chain with four validators and two parachains each with one collator:
[relaychain]
default_command = "./bin/polkadot-v0.9.32"
default_args = [ "-lparachain=debug" ]
chain = "rococo-local"
# relaychain nodes are by default validators
[[relaychain.nodes]]
name = "alice"
[[relaychain.nodes]]
name = "bob"
[[relaychain.nodes]]
name = "charlie"
[[relaychain.nodes]]
name = "dave"
[[parachains]]
id = 1000
cumulus_based = true
[parachains.collator]
name = "parachain-A-1000-collator-01"
command = "./bin/parachain-template-node-v0.9.32"
[[parachains]]
id = 1001
cumulus_based = true
[parachains.collator]
name = "parachain-B-1001-collator-01"
command = "../bin/parachain-template-node-v0.9.32"
Save the file and run the Zombienet CLI using this config:
./zombienet-macos -p native spawn config.toml
You should see some nicely formatted output stating that the nodes are up and ready.
You can now interact with your nodes on polkadotJS apps!
Background on HRMP (XCMP-Lite)
While XCMP is still being implemented, a stop-gap
protocol (see definition below) known as Horizontal Relay-routed
Message Passing (HRMP) exists in its place. HRMP has the same
interface and functionality as XCMP but is much more demanding on
resources since it stores all messages in the Relay Chain storage.
When XCMP has been implemented, HRMP is planned to be deprecated and
phased out in favor of it.
This post will show how to open HRMP channels between two parachains: Parachain A and Parachain B.
Channels are unidirectional
The parachain that wants to open an HRMP channel must make a request to the parachain it wishes to have an open channel with. Once that is done, the other parachain needs to accept this request. This is what we mean by unidirectional flow. For bidirectional communication we need to open another channel in the opposite way.
A channel can be opened only after the recipient confirms it and only on a session change.
We will have Parachain A initiate a request to open an HRMP channel with Parachain B.
This is done on the relay chain.
Open the relay chain's polkadotJS apps and create the following extrinsic:
hrmp.hrmpInitOpenChannel(
recipient: 2000 // the other parachain you want to open the channel with
proposedMaxCapacity: 1000 // specifies how many messages can be in the channel at once
proposed_max_message_size: 102400 // specifies the maximum size of the messages
)
We will not submit this transaction. Instead, after setting the desired parameters, copy the encoded call data. We will need this later to craft our XCM message.
Here is a past example of an encoded call data in Rococo:
0x1700b80b0000e803000000900100
Note that proposedMaxCapacity
and proposed_max_message_size
numbers are subject to the relay chain's configuration limits. The relay chain's configuration can be found in configuration.activeConfig()
:
The image above is an example of Rococo's active configuration at the time of writing this article.
The next step is done on parachain A.
Connect to Parachain A on polkadotJS apps.
Create a polkadotXcm extrinsic to notify the relay chain that we want to open a channel with parachain B (using the encoded call data from the previous step):
polkadotXcm.send(
dest: V1
parents: 1
interior: Here
message: V2
XcmV2Instruction: WithdrawAsset
id: Concrete
parents: 0
interior: Here
fun: Fungible
Fungible: 1_000_000_000_000
XcmV2Instruction: BuyExecution
id: Concrete
parents: 0
interior: Here
fun: Fungible
Fungible: 1_000_000_000_000
weightLimit: Unlimited
XcmV2Instruction: Transact
originType: Native
requireWeightAtMost: 4_000_000_000
encoded: 0x1700b80b0000e803000000900100 // our hrmpInitOpenChannel encoded call data
)
Note: you should compose your message taking into account the active XCM configuration, this is just an example.
So far Parachain A has done its part: it has requested to open an HRMP channel to Parachain B.
Now this request has to be accepted by Parachain B.
On the relay chain, create the following extrinsic:
hrmp.hrmpAcceptOpenChannel(
sender: 2000
)
There is no need to submit the transaction. Instead copy the encoded call data.
Here is an example of an encoded call data in Rococo:
0x1701d0070000
Now on Parachain B we use polkadotXcm.send
extrinsic to craft an XCM message (using the encoded call data from the previous step).
polkadotXcm.send(
dest: V1
parents: 1
interior: Here
message: V2
XcmV2Instruction: WithdrawAsset
id: Concrete
parents: 0
interior: Here
fun: Fungible
Fungible: 1_000_000_000_000
XcmV2Instruction: BuyExecution
id: Concrete
parents: 0
interior: Here
fun: Fungible
Fungible: 1_000_000_000_000
weightLimit: Unlimited
XcmV2Instruction: Transact
originType: Native
requireWeightAtMost: 4_000_000_000
encoded: 0x1701d0070000 // our hrmpAcceptOpenChannel encoded call data
)
Note: you should compose your message taking into account the active XCM configuration, this is just an example.
And that's it — the channel has been accepted and it will remain open, such that communication from Parachain A to Parachain B can now flow.
For making this a bidirectional channel you'll need to open another channel, from Parachain B to Parachain A. You can do this by repeating the steps above (inversely).
More info here:
Zombienet also has an HRMP configuration to quickly open HRMP channels.
For testing purposes this can be useful.
Simply add the following to your config:
[[hrmpChannels]]
sender = 1000
recipient = 1001
maxCapacity = 1000
maxMessageSize = 102400
[[hrmpChannels]]
sender = 1001
recipient = 1000
maxCapacity = 1000
maxMessageSize = 102400
Restart Zombienet and you now have a bidirectional HRMP channel open between the two parachains!