This article presents a demo for cross-chain function calling through Polkadot’s XCMP, with Substrate’s Ping Pallet code example.
All code mentioned in this article can be found in the OAK Network Github repo: https://github.com/AvaProtocol/ping-workshop
1. Overview
In this demo, we will leverage the polkadot v0.9.8 developed by Parity Technologies to create a relay chain network and two parachain networks, and call the cross-chain ping-pong function from parachain A to parachain B.
Specifically, we will create 4 command-line environments to host 2 validators for the relay chain network, and 1 validator for each parachain. 4 is the minimum number of nodes for this cross-chain ping-pong demonstration.
Below we explain the details.
1.1 Ping-pallet in Cumulus
Cumulus is an extension to Substrate that makes it easy to make any Substrate built runtime into a Polkadot-compatible parachain. To learn more about it, please refer to Polkadot Wiki: Cumulus.
# Custom Dependencies
cumulus-ping = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.8', default-features = false }
2.2 Add Cumulus-Ping into runtime code
2.2.1 Implement cumulus-ping::config in runtime
Here we refer to the code in rococo repository.
impl cumulus_ping::Config for Runtime {
type Event = Event;
type Origin = Origin;
type Call = Call;
type XcmSender = XcmRouter;
}
2.2.2 Add cumulus-ping to construct_runtime
Here we also refer to the code in rococo repository
construct_runtime! {
pub enum Runtime where
Block = Block,
NodeBlock = generic::Block<Header,sp_runtime::OpaqueExtrinsic>,
UncheckedExtrinsic = UncheckedExtrinsic,
{
....
Spambot: cumulus_ping::{Pallet, Call, Storage, Event<T>} = 99,
}
}
And now the cumulus_ping pallet is included in the substrate-parachain-template.
3. Run relay chain + 2 parachains
3.1 Prerequisite
3.1.1 Rust environment
Run the following code to make sure you have the correct version of Rust installed.
# setup Rust nightly toolchain
rustup default nightly-2021-03-01
# setup wasm toolchain
rustup target add wasm32-unknown-unknown --toolchain nightly-2021-03-01
3.1.2 Relay chain: polkadot v0.9.8
https://github.com/paritytech/polkadot/tree/release-v0.9.8
3.1.3 Parachain:
Here we use the code base in OAK Network’s ping-workshop repo.
https://github.com/AvaProtocol/ping-workshop
The ping-workshop is based on the most recent release of substrate-parachain-template, which is compatible with Polkadot v0.9.8.
3.2 Compile and run the relay chain
3.2.1 Compile the relay chain code
git clone -b release-v0.9.8 https://github.com/paritytech/polkadot
cd polkadot
cargo build --release
3.2.2 Generate config
./target/release/polkadot build-spec --chain=rococo-local --disable-default-bootnode --raw > rococo-local.json
3.2.3 Run the code
# First node
./target/release/polkadot --name alice --chain rococo-local --alice -d ./data/alice --ws-external --rpc-external --rpc-cors all --node-key 0000000000000000000000000000000000000000000000000000000000000001
#Second Node
./target/release/polkadot --name bob --chain rococo-local --bob -d ./data/bob --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
Check the following tutorial for details on starting a network.
https://substrate.dev/docs/en/tutorials/start-a-private-network/alicebob
So far we have launched two nodes and formed the relay chain network.
3.3 Compile and run the parachains
3.3.1 Compile the code
git clone https://github.com/imstar15/ping-workshop
cd ping-workshop
cargo build --release
3.3.2 Generate genesis state & wasm
We are giving the two parachain IDs 2000 and 2001. The genesis wasm file is the same for the 2 parachains, but each needs a separate genesis state file.
# Export genesis wasm
./target/release/parachain-collator export-genesis-wasm > genesis-wasm
# Export genesis-state-2000
./target/release/parachain-collator export-genesis-state --parachain-id 2000 > genesis-state-2000
# Export genesis-state-2001
./target/release/parachain-collator export-genesis-state --parachain-id 2001 > genesis-state-2001
3.3.3 Run the parachains
// First chain
RUST_LOG=runtime=debug ./target/release/parachain-collator -d ./data/alice --collator --alice --force-authoring --port 40557 --ws-port 9951 --parachain-id 2000 --ws-external --rpc-cors all --rpc-methods=unsafe -- --execution wasm --chain ../polkadot/rococo-local.json --port 40558 --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
// Second chain
RUST_LOG=runtime=debug ./target/release/parachain-collator -d ./data/bob --collator --bob --force-authoring --port 40777 --ws-port 9971 --parachain-id 2001 --ws-external --rpc-cors all --rpc-methods=unsafe -- --execution wasm --chain ../polkadot/rococo-local.json --port 40778 --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
For more details about the parachain-collator program, use the help command to check out the examples.
./target/release/parachain-collator --help
3.3.4 Connect the parachains to the relay chain
In this step, we will need to use polkadot.js/apps website to send request to the chains. Use sudo access to submit the registration information of the two parachains to araSudoWrapper.sudoScheduleParaInitialize.
As mentioned above, the para-ids are 2000 and 2001.
Navigate to polkadot.js/apps website with a local rpc target.
https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/sudo
Under the Network tab you should be able to see the 2 parachains registered on the relay chain.
And here you can see the parachain’s blocks are finalized, indicating that the Parachains are successfully connected to the relay chain.
4. Build HRMP channels and call the cross-chain function
XCMP-light(HRMP) is an alternative messaging channel to the XCMP which has the same interface. It’s the current implementation of a messaging channel.
4.1 Call the cross-chain function
First, let’s try to call the cross-chain function. In this case, it’s spambot.start which sends a ping message from parachain 2000 to parachain 2001
Navigate to the Developer tab of polkadot.js/apps
https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9951#/sudo
Execute spambot.start with below parameters,
However, all we received is an error, ErrorSendingPing:
After debugging we found the root cause of that error, as seen in below code.
In this get_channel_max, the number of channels is 0. Therefore, the binary_search_by_key fails to execute. The cross chain messaging channel is not yet established.
4.2 Build the HRMP channel
Now we execute paraSudoWrapper.sudoEstabllishHrmpChannel to build the messaging channel between 2000 and 2001. We also need to repeat the process for the channel from 2001 to 2000.
After the channel setup, we repeat the spambot.start function in 4.1, and now we see the spambot.PingSent and spambot.Ponged events shown in recent blocks, indicating that the ping function is successfully called cross-chain.
https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9951#/explorer
5. Analyze the code
As mentioned in 1., the code of ping function is from cumulus-ping pallet.
5.1 The Start function
Use this code to start
5.2 on_finalize function
The code defines a hook that when the block is finalized, the start method will send the requested message out to the destination chain with a ping message.
5.3 The ping function
When ping is executed, it will reply with a XCMP, calling pong function; therefore, it completes a round-trip for ping-pong.
6. Cross-chain message filtering
One important thing to note in above implementation is that a cross-chain message needs to be whitelisted in the receiver end to be accepted. The whitelisting happens in a place called the Barrier.
Without the whitelisting, the receiving chain will encounter an XcmError.Barrier error, as shown in below screenshot.
Let’s take a look at the Barrier setup below.
In runtime/src/lib.rs
It shows pub type Barrier = (….), where it defines 4 Barrier, and each has a message filter criteria.
For example, the AllowUnpaidExecutionFrom Barrier limits the origin to be within a range,
- AllowUnpaidExecutionFrom<ParentOrParentsUnitPlurality>: only messages from the relay chain council will be executed.
- AllowUnpaidExecutionFrom<All<MultiLocation>>: accept all messages origins
- AllowUnpaidExecutionFrom<SpecParachain>: parachain 2000 and 2001 can only execute messages from each other
match_type! {
pub type SpecParachain: impl Contains<MultiLocation> = {
X2(Parent, Parachain(2000)) | X2(Parent, Parachain(2001))
};
}
That’s all for today’s tutorial. Hope you grasp the idea of Polkadot cross-chain function calling from our article. The parachain code is based off of substrate-parachain-template and ready in the OAK Network Github:
https://github.com/AvaProtocol//ping-workshop
Stay tuned for further information about the OAK Network here on Medium, Twitter and LinkedIn, as well as in our Discord Server!
Please check out our engineering and growth job openings, contact partner@oak.tech for partnerships, or contact@oak.tech for any general inquiries.
About Chris Li
Chris Li (LinkedIn) is the founder and CEO of Ava Protocol. Prior to establishing Ava Protocol, Chris gained valuable experience as a Messaging Protocol Engineer at Microsoft and as a successful serial entrepreneur. He is also a long-term EVM smart contract developer, Web3 Foundation grant recipient, and Polkadot core contributor.