Substrate

Creating a Custom Substrate chain

Troubleshooting

Substrate is a rapidly evolving project, which means that breaking changes may cause you problems when trying to follow the instructions below. Feel free to contact us with any problems you encounter.

This document will walk you through the steps required to duplicate the demo that Gavin Wood presented at the 2018 Web3 Summit, showing off how you can build a Runtime Library for a Substrate Blockchain in less than 30 min.

This tutorial will be written for a Mac OS X machine, and may require some finessing to get working on other operating systems.

Prerequisites

To start, make sure your machine has the latest node and npm installed. Then we need to make sure you are able to run Substrate, which means installing Rust and other dependencies.

This can be done with this simple one-liner (it may take a little while, so grab some tea):

curl https://getsubstrate.io -sSf | bash

You will also need to set up a few more repositories into your working folder which were used in the demo:

You can do that with some script aliases that were loaded on your machine:

NOTE

You may need to restart your terminal in order for these scripts and other Substrate commands to become available to you.

substrate-node-new substrate-node-template <author-name>
substrate-ui-new substrate

This will create a folder called substrate-node-template and substrate-ui with the corresponding repositories cloned in them. You can of course rename your projects in these commands, but for the sake of the clarity, we will continue with these folder names.

Step 1: Launch a Blockchain

If you have set up everything correctly, you can now start a substrate dev chain! In substrate-node-template run:

./target/release/substrate-node-template --dev

Purge the Chain Database

If you run into any errors starting or running your node, you may need to purge the chain files on your computer. You can do this by running: cargo run -- purge-chain --dev or by manually removing the chain's folder: rm -rf ~/Library/Application\ Support/Substrate/chains/dev/

If everything is working it should start producing blocks!

To interact with the blockchain, you need to start the Substrate UI. Navigate to the substrate-ui folder and run:

npm run dev

Finally, if open your browser to http://localhost:8000, you should be able to interact with your new chain!

Step 2: Add Alice to your network

Alice is a hard-coded account in the substrate system, which is pre-funded to make your life easier. Alice may already be added to your network if you used the latest version of the substrate-node-template. But if she isn't, you can add her easily.

Open a new terminal, and using the installed substrate/subkey package, you can retrieve the seed for this pre-funded account:

subkey restore Alice
    
> Seed
> 0x416c696365202020202020202020202020202020202020202020202020202020 is account:
>    SS58: 5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ
     Hex: 0xd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f

Then in the Substrate UI, you can go into the Wallet section and add Alice using her seed and name.

An image of adding Alice to your wallet

If all is working correctly, you can now go into the Send Funds section and send funds from Alice to Default. You will see that Alice has a bunch of units pre-funded in her account, so send some and wait for the green checkmark and an updated balance for Default to show that the transfer has been successfully recorded on the blockchain.

An image of sending funds from alice to your default account

Step 3: Create a new runtime module

Now it's time to create our own runtime.

Open up the substrate-node-template folder and create a new file:

./runtime/src/demo.rs

This is where our new runtime module will live. Inline comments will hopefully give you insight to what the code is doing.

First, we will need to import a few libraries at the top of our file:

// Encoding library
use parity_codec::Encode;

// Enables access to the runtime storage
use srml_support::{StorageValue, dispatch::Result};

// Enables us to do hashing
use runtime_primitives::traits::Hash;

// Enables access to account balances and interacting with signed messages
use {balances, system::{self, ensure_signed}};

All modules need to expose a configuration trait. In this case, our trait inherits from the Balances module's trait since we will be using features and functions made available to us there.

pub trait Trait: balances::Trait {}

In this example, we will create a simple coin flip game. Users will pay an entry fee to play the game and then "flip a coin". If they win they will get the contents of the pot. If they don't win, they will get nothing. No matter the outcome, their fee will be placed into the pot after the game resolves for the next user to try and win.

To build this game, we will need to create the module declaration. These are the entry points that we handle, and the macro below takes care of the marshalling of arguments and dispatch.

This game will have two entry points: one that lets us play the game, and one that lets us set the payment.

decl_module! {
  pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    fn play(origin) -> Result {
      // Logic for playing the game
    }

    fn set_payment(_origin, value: T::Balance) -> Result {
      // Logic for setting the game payment
    }
  }
}

Now that we have established our module structure, we can add the logic which powers these functions. First, we will write the logic for playing our game:

fn play(origin) -> Result {
  // Ensure we have a signed message, and derive the sender's account id from the signature
  let sender = ensure_signed(origin)?;
  
  // Here we grab the payment, and put it into a local variable.
  // We are able to use Self::payment() because we defined it in our decl_storage! macro below
  // If there is no payment, exit with an error message
  let payment = Self::payment().ok_or("Must have payment amount set")?;

  // First, we decrease the balance of the sender by the payment amount using the balances module
  <balances::Module<T>>::decrease_free_balance(&sender, payment)?;
  
  // Then we flip a coin by generating a random seed
  // We pass the seed with our sender's account id into a hash algorithm
  // Then we check if the first byte of the hash is less than 128
  if (<system::Module<T>>::random_seed(), &sender)
  .using_encoded(<T as system::Trait>::Hashing::hash)
  .using_encoded(|e| e[0] < 128)
  {
    // If the sender wins the coin flip, we increase the sender's balance by the pot amount
    // `::take()` will also remove the pot amount from storage, which by default will give it a value of 0
    <balances::Module<T>>::increase_free_balance_creating(&sender, <Pot<T>>::take());
  }

  // No matter the outcome, we will add the original sender's payment back into the pot
  <Pot<T>>::mutate(|pot| *pot += payment);

  Ok(())
}

Next we will set up logic for initializing the game with the initial payment:

fn set_payment(_origin, value: T::Balance) -> Result {
  //If the payment has not been set...
  if Self::payment().is_none() {
    // ... we will set it to the value we passed in.
    <Payment<T>>::put(value);
    
    // We will also put that initial value into the pot for someone to win
    <Pot<T>>::put(value);
  }
  
  Ok(())
}

Then we will create the storage declaration. Using the decl_storage! macro, we can define the module specific data entries to be stored on-chain. Learn more about this macro here.

decl_storage! {
  trait Store for Module<T: Trait> as Demo {
    Payment get(payment): Option<T::Balance>;
    Pot get(pot): T::Balance;
  }
}

And that's it! This is how easy it can be to build new runtime modules. You can find a complete version of this file here to check your work.

Step 4: Integrate our new module into our runtime

To actually use our module, we need to tell our runtime that it exists. To do this we will be modifying the ./runtime/src/lib.rs file:

First, we need to declare that we are using the new demo module:

...
extern crate substrate_consensus_aura_primitives as consensus_aura;

mod demo;     // Add this line  

Next, we need to implement our configuration trait, which we can do at the end of all the other trait implementations:

...

impl upgrade_key::Trait for Runtime {
  type Event = Event;
}

impl demo::Trait for Runtime {}      // Add this line

Finally, we put our new module into the runtime construction macro, construct_runtime!:

construct_runtime!(
  pub enum Runtime with Log(InternalLog: DigestItem<Hash, AuthorityId>) where
    Block = Block,
    NodeBlock = opaque::Block,
    InherentData = BasicInherentData
  {
    System: system::{default, Log(ChangesTrieRoot)},
    Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
    Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
    Aura: aura::{Module},
    Balances: balances,
    UpgradeKey: upgrade_key,
    Demo: demo::{Module, Call, Storage, Config<T>},  // Add this line
  }
);

Again, you can find a complete version of this file here.

Step 5: Upgrade our chain

Now that we have created a new runtime module, it's time for us to upgrade our blockchain.

To do this, first we will need to build our new runtime. Go into substrate-node-template and run:

./build.sh

If this completes successfully, it will update the following file:

./runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

You can go back to the Substrate UI, and in the Runtime Upgrade section, you can select this file and press upgrade.

An image of a successful chain upgrade

If all went well, you can see at the top of the Substrate UI that the Runtime will have our updated name!

An image of an updated runtime name

Step 6: Interacting with our new module

Finally, we can try and play the game we created. We will begin our interaction through the browser console.

On the page with the Substrate UI, press F12 to open your developer console. We will take advantage of some of the JavaScript libraries loaded on this page.

Before we can play the game, we need to initialize the set_payment from an account. We will call the function on behalf of Alice, who will generously initialize the pot with a signed message.

post({sender: "5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ", call: calls.demo.setPayment(1000)}).tie(console.log)

An image of the setting the payment in the developer console

When this call completed, you should see {finalized: "..."}, showing that it has been added to the chain. We can check this by reading the balance in the pot:

runtime.demo.pot.then(console.log)

Which should return Number {1000}

Step 7: Updating our Substrate UI

Now that we see things are working in the background, it's time to give our UI some new legs. Let's add an interface so that someone can play our game. To do this we will need to modify the substrate-ui repository.

Open the ./src/app.jsx file, and in the readyRender() function, you will see the code which generates all the different UX components.

For example, this code snippet controls the Runtime Upgrade UX that we most recently interacted with:

<Divider hidden />
<Segment style={{margin: '1em'}} padded>
  <Header as='h2'>
    <Icon name='search' />
    <Header.Content>
      Runtime Upgrade
      <Header.Subheader>Upgrade the runtime using the UpgradeKey module</Header.Subheader>
    </Header.Content>
  </Header>
  <div style={{paddingBottom: '1em'}}></div>
  <FileUploadBond bond={this.runtime} content='Select Runtime' />
  <TransactButton
    content="Upgrade"
    icon='warning'
    tx={{
      sender: runtime.upgrade_key.key,
      call: calls.upgrade_key.upgrade(this.runtime)
    }}
  />
</Segment>

We can use this as a template for how we should add our game's UX. After the last </Segment>, create a new one with the following code:

...

</Segment>
<Divider hidden />
<Segment style={{margin: '1em'}} padded>
  <Header as='h2'>
    <Icon name='game' />
    <Header.Content>
      Play the game
      <Header.Subheader>Play the game here!</Header.Subheader>
    </Header.Content>
  </Header>
  <div style={{paddingBottom: '1em'}}>
    <div style={{fontSize: 'small'}}>player</div>
    <SignerBond bond={this.player}/>
    <If condition={this.player.ready()} then={<span>
      <Label>Balance
        <Label.Detail>
          <Pretty value={runtime.balances.balance(this.player)}/>
        </Label.Detail>
      </Label>
    </span>}/>
  </div>
  <TransactButton
    content="Play"
    icon='game'
    tx={{
      sender: this.player,
      call: calls.demo.play()
    }}
  />
  <Label>Pot Balance
    <Label.Detail>
      <Pretty value={runtime.demo.pot}/>
    </Label.Detail>
  </Label>
</Segment>

Beyond the updated text, you can see we are accessing a new this.player bond, which represents the user context playing the game.

Using this, we can get details like the user's balance:

runtime.balances.balance(this.player)

And submit transactions on behalf of this user:

tx={{
  sender: this.player,
  call: calls.demo.play()
}}

Also notice that we are able to dynamically show content like the current balance of the pot in a similar way to how we retrieved it in the developer console:

<Label>Pot Balance
  <Label.Detail>
    <Pretty value={runtime.demo.pot}/>
  </Label.Detail>
</Label>

The only thing left for us to do, is to create the new player bond in our constructor() function at the top of the same file:

...

this.runtime = new Bond;
this.player = new Bond;         // Add this line

If you save your changes and reload the page, you should see your new UX! You can now try playing the game with the Default user:

An image of the player losing the game

Here you can see the player lost the game, which means that their 1000 units got added to the pot, and an additional 1 unit transaction fee was taken from their balance.

If we try a few more times, eventually the player will win the game, and the pot will be reset back to its starting amount for the next player:

An image of the player winning the game

Final Notes

That's all folks! While you can't actually make a profit playing this game, hopefully you see just how simple Substrate can make it to develop your next blockchain.

In summary, we showed you how to:

  • Download and install substrate to your machine in a single command
  • Set up a fresh substrate-node-template and substrate-ui so that you can start hacking right away
  • Program a new runtime for your blockchain
  • Upgrade your runtime, in real-time and without forking, via the substrate-ui
  • Update the substrate-ui to reflect your new runtime features and functionality

Substrate is a rapidly developing technology, and we would love to get your feedback, answer questions, and learn more about what you want to build! Feel free to contact us using the details provided here.