How to Configure a New Testnet forYour Substrate Blockchain

Vimukthi Wickramasinghe
Vimukthi in Cyberspace
4 min readSep 1, 2019

--

Assuming you have a substrate runtime implemented on top of the master branch of substrate repo, you can follow these steps to configure a new testnet.

In substrate, testnet configurations(chain specs) are defined in the src/chain_spec.rs file. By default there are two testnets configured Development and LocalTestnet in the Alternative enum. Development testnet allows you to run a single node development setup where this node its self is the only validator (note here that substrate by default runs a PoA style network unless you have defined a validator selection algorithm). Hence its starts producing blocks immediately. The definition looks like the following.

// ... Alternative::Development => ChainSpec::from_genesis(
"Development",
"dev",
|| testnet_genesis(vec![
// initiali authority
get_authority_keys_from_seed("Alice"),
],
// root key
get_from_seed::<AccountId>("Alice"),
// endowed accounts
vec![
get_from_seed::<AccountId>("Alice"),
get_from_seed::<AccountId>("Bob"),
get_from_seed::<AccountId>("Alice//stash"),
get_from_seed::<AccountId>("Bob//stash"),
],
true),
vec![],
None,
None,
None,
None
)
// ...

Here account Alice is configured both as the initial authority able to produce/validate blocks as well as the root(sudo) key which could invoke any action on the blockchain. This is a pretty handy setup to test out things with a new chain since https://polkadot.js.org/apps/#/explorer has this account private key configured(among others) by default. Since its also funded here using the endowed account field, you can start spending immediately. This is the easiest way to test out your runtime logic while developing it.

LocalTestnet defines a multi-validator network which is configured similar to Development but with an additional validator Bob. This is a more realistic but slower test setup since you would need to run two validator nodes to start producing blocks. In order to run this network you need to pass --chain=local flag, eg: cargo run -- --chain=local.

Now let’s look at how we can define our own new testnet here with test accounts and keys such as Alice and Bob removed. There are two methods of achieving this.

  1. You can generate a testnet genesis file using one of the existing testnets and edit and save that as the genesis configuration for your new testnet. Skip to step 5 if you rather define the testnet this way.
  2. Second method is nicer in that it allows to edit/define the testnet genesis config in code while allowing easy customisation of the config while the testnet is under development. For this you first need to modify the Alternative enum to include your new testnet.

Step 1 (only method 2)

#[derive(Clone, Debug)]
pub enum Alternative {
/// Whatever the current runtime is, with just Alice as an auth.
Development,
/// Whatever the current runtime is, with simple Alice/Bob auths.
LocalTestnet,

/// this is my new testnet
MyNewTestnet,
}

Step 2 (only method 2)

Then we need to complete the match arm corresponding to this new enum value in the implementation of Alternative. But before that let’s define some utility functions we need to complete the definition. Add hex package to your root level Cargo.toml so that we can use its functions.

# ...[dependencies.hex]
package = 'hex'
version = "0.3.2"
# ...

Step 3 (only method 2)

The following function(s) allow us to convert a public key hex string into a substrate AccountId. Add them to your chain_spec.rs.

pub fn get_from_pub_str<TPublic: Public>(pubkey_hex: &str) -> AccountId {
primitives::sr25519::Public::from_raw(
byte32_from_slice(Vec::from_hex(pubkey_hex)
.expect("a static hex string is valid")
.as_slice()))
}
fn byte32_from_slice(bytes: &[u8]) -> [u8; 32] {
let mut array = [0; 32];
let bytes = &bytes[..array.len()];
array.copy_from_slice(bytes);
array
}

Step 4 (only method 2)

Now using these we can define the keys use for our new testnet.

Ok(match self {// ...// MyNewTestnet initial config
Alternative::MyNewTestnet => ChainSpec::from_genesis(
"MyNewTestnet",
"mynewtestnet",
|| {
testnet_genesis(
vec![
// DO NOT commit these seeds into the code repository!!
get_authority_keys_from_seed("YourOwnValidator1"),
get_authority_keys_from_seed("YourOwnValidator2"),
],
get_from_pub_str::<AccountId>("your_acc_pubkey_hex"),
vec![
get_from_pub_str::<AccountId>("your_acc_pubkey_hex")
],
true,
)
},
vec![
// bootnode urls for your testnet
String::from("/ip4/10.1.1.1/tcp/30333/p2p/QmSqbcHcJh7DvKDdMYxWREtnAfqqxLiX7J2YDGiV6e5Lwe")
],
Some("telemetry endpoints"),
Some("your-chain-protocol"),
None,
None,
),

// ...

Step 5

Now to generate the chain spec,
cargo run -- build-spec --chain=mynewtestnet > mynewtestnet.json

To generate the raw chain spec,
cargo run -- build-spec --chain=mynewtestnet --raw > testnets/mynewtestnet.raw.json

These needs to be generated as the validators need to agree on a genesis config which is an exact match to each-other. Since compiled wasm blob is not reproducible independently, we need to share this exact file with all the validators.

Step 6

Time to start you validator nodes with brand new chain spec.

  • Validator 1
    your-chain-executable --ws-external --validator --rpc-cors=all --chain=mynewtestnet.raw.json --port=30333
  • Validator 2
    your-chain-executable --ws-external --validator --rpc-cors=all --chain=mynewtestnet.raw.json --port=30334 --rpc-port=9934

Step 7

Now you need to activate the validators by setting the keys that they would use. We defined two seeds in the chain spec for the two validators. Now we can use them to set the keys.

curl -H 'Content-Type: application/json' --data '{ "jsonrpc":"2.0", "method":"author_insertKey", "params":["gran", "YourOwnValidator1"],"id":1 }' localhost:9933curl -H 'Content-Type: application/json' --data '{ "jsonrpc":"2.0", "method":"author_insertKey", "params":["babe", "YourOwnValidator1"],"id":1 }' localhost:9933curl -H 'Content-Type: application/json' --data '{ "jsonrpc":"2.0", "method":"author_insertKey", "params":["gran", "YourOwnValidator2"],"id":1 }' localhost:9934curl -H 'Content-Type: application/json' --data '{ "jsonrpc":"2.0", "method":"author_insertKey", "params":["babe", "YourOwnValidator2"],"id":1 }' localhost:9934

Now your testnet is correctly set up locally and the validators should produce blocks correctly.

--

--