Parity Substrate is a blockchain development framework with many cool features like upgradable blockchains, modular architecture, customizable block execution logic, and hot-swappable consensus. For an overview of the Substrate framework, watch this talk from Dr. Gavin Wood (Web3 Summit, October 2018).
This article is about how you can get started with the Substrate framework to build your own blockchain. Let’s jump right in.
The first step towards getting started with Substrate is setting up your development environment. The Substrate team has created a bash script which installs all the dependencies and compiles the relevant packages as part of installing the Substrate framework.
Run the following command in your terminal, and it will set up your machine ready to hack on Substrate.
curl https://getsubstrate.io -sSf | bash
The command takes a few minutes to complete (depending on your hardware) as it also compiles all the Rust packages needed for the Substrate framework.
There is a faster option also available, but it installs only the dependencies and does not compile the Substrate packages. To use this option, run the following command.
curl https://getsubstrate.io -sSf | bash -s -- --fast
Note: In the faster option you will not have the Substrate CLI installed globally in your system.
Once the install script finishes execution, along with the dependencies, you will also have a couple of commands installed on your machine.
One of these commands is the substrate-node-new command which helps you set up a template node. Think of this as a project scaffolding template. The command downloads a Rust code base and compiles it. This codebase packages together all the bootstrapping code needed to set up a Substrate runtime.
To create an instance of the node template, run the following command in your terminal.
substrate-node-new <project name> <author name>
The first parameter is a name for your blockchain project and the second (optional) parameter is the name of the author for this chain.
For example:
substrate-node-new substrate-demo demoauthor
Once this command completes, it will create the following directory structure inside the substrate-demo (or the project name you used) directory. It will also initialize a git repository in this directory.
substrate-node-template directory structure
The runtime sub-directory houses the blockchain runtime related logic. The runtime can be called the business logic for your blockchain. It is further divided into runtime modules and each module packages together it’s own state (storage) and behavior (logic). The runtime directory contains files for the runtime modules.
The src directory contains the low-level code which brings together all the components of the Substrate framework for execution of the runtime.
The node template contains a build.sh script which allows us to build the Substrate runtime for the Wasm (Web Assembly) environment.
Once the substrate-node-new command finishes execution, it would have also compiled the source code of the node template (takes a few mins). At this moment, you can already start the node, and it will begin producing blocks.
To start the Substrate node, run the following command, in the context of the node-template directory. The following command will start a Substrate node based on the node-template using the dev configuration. In essence, it is running the executable generated by the compilation of the node-template codebase.
cd substrate-demo // in case you haven't done this already
./target/release/substrate-demo --dev
The command will produce output similar to the following.
2019-04-08 17:28:31 Substrate Node
2019-04-08 17:28:31 version 1.0.0-x86_64-linux-gnu
2019-04-08 17:28:31 by demoauthor, 2017, 2018
2019-04-08 17:28:31 Chain specification: Development
2019-04-08 17:28:31 Node name: adorable-wind-3578
2019-04-08 17:28:31 Roles: AUTHORITY
2019-04-08 17:28:31 Initializing Genesis block/state (state: 0x4397…ab51, header-hash: 0x0353…30ef)
2019-04-08 17:28:31 Loaded block-time = 10 seconds from genesis on first-launch
2019-04-08 17:28:31 Best block: #0
2019-04-08 17:28:31 Local node address is: /ip4/0.0.0.0/tcp/30333/p2p/QmYsPTbsxQiKV8Dk3rWL19xFXxfpt2NrzFRd2P63AjRM3o
2019-04-08 17:28:31 Listening for new connections on 127.0.0.1:9944.
2019-04-08 17:28:31 Using authority key 5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TmTd
2019-04-08 17:28:40 Starting consensus session on top of parent 0x03534673f220e0514d5324acd179438094e05f4c2419f33226c42e33f5cf30ef
2019-04-08 17:28:40 Prepared block for proposing at 1 [hash: 0xdc6de1c9b04607fe51bf4abed8cfdbd6313a74e9eeda78e3db4f6d2b60e69903; parent_hash: 0x0353…30ef; extrinsics: [0x0421…0149]]
2019-04-08 17:28:40 Pre-sealed block for proposal at 1. Hash now 0x1e5375649660d9e8b8e41fde74c861185a1e55153285e4332111dd0aa240684a, previously 0xdc6de1c9b04607fe51bf4abed8cfdbd6313a74e9eeda78e3db4f6d2b60e69903.
2019-04-08 17:28:40 Imported #1 (0x1e53…684a)
As you can see, the node has started producing blocks.
Now that we are all set up with Substrate and our template node is working as expected, let’s build some custom logic for our blockchain.
The business logic for the blockchain resides in the runtime modules. A collection of runtime modules constitutes a runtime.
Let’s say we want to build a simple blockchain runtime for a token transfer functionality. To implement this, we would be needing the following:
State:
Behavior:
As mentioned before, a Substrate runtime module packages together it’s own state and behavior. We would be creating a custom runtime module with the storage items and functions as mentioned above.
In the node-template directory which we created above, inside the runtime/src directory we will find two files — lib.rs and temaplate.rs.
The lib.rs is the Rust crate root for the runtime. It imports all the necessary dependencies and types. It also initializes the runtime modules using some Rust macros part of the Substrate framework.
The template.rs is a template of a Substrate runtime module which is included in the node-template. It contains some dummy state and behavior (with a description in code comments) and is fully functional runtime module in itself.
To implement our token functionality described above, let’s make some changes to the template.rs file.
Firstly, let’s declare the storage items needed for the token functionality. In the decl_storage! macro call, let’s add the following two items for total supply and balances mapping of the token.
TotalSupply get(total_supply): u64 = 21000000;
BalanceOf get(balance_of): map T::AccountId => u64;
In the first line, we are adding a storage item TotalSupply to save the total number of the tokens. We are also setting a value to this storage item (21000000).
In the second line, we are creating another storage item as a StorageMap with a mapping between an AccountId and the token balance associated with it. We are calling this storage item BalanceOf.
The full storage declaration code for this module looks like the following. (Note: We’ve removed the dummy storage items which came with the template module.)
// storage for this runtime module
decl_storage! {
trait Store for Module<T: Trait> as Template {
TotalSupply get(total_supply): u64 = 21000000;
BalanceOf get(balance_of): map T::AccountId => u64;
}
}
Now that we have the storage defined for our Substrate module let’s write some code to manipulate these storage items.
In Substrate modules, public dispatchable functions are defined using the decl_module! macro. We have two functions to implement the simple token transfer functionality in our module. These are the initialization of the token and the transfer function.
In the following snippet, these two dispatchable functions — init and transfer are defined in the decl_module! macro.
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// initialize the token
// transfers the total_supply amout to the caller
fn init(origin) -> Result {
let sender = ensure_signed(origin)?;
<BalanceOf<T>>::insert(sender, Self::total_supply());
Ok(())
}
// transfer tokens from one account to another
fn transfer(_origin, to: T::AccountId, value: u64) -> Result {
let sender = ensure_signed(_origin)?;
let sender_balance = Self::balance_of(sender.clone());
ensure!(sender_balance >= value, "Not enough balance.");
let updated_from_balance =
sender_balance.checked_sub(value)
.ok_or("overflow in calculating balance")?;
let receiver_balance = Self::balance_of(to.clone());
let updated_to_balance = receiver_balance.checked_add(value)
.ok_or("overflow in calculating balance")?;
// reduce sender's balance
<BalanceOf<T>>::insert(sender, updated_from_balance);
// increase receiver's balance
<BalanceOf<T>>::insert(to.clone(), updated_to_balance);
Ok(())
}
}
}
Note how we are accessing the module’s storage withSelf::total_supply() and <BalanceOf<T>> to get and set values of these storage items.
Note: From a security perspective, these functions do need a bit more in terms of checks and validations. But to keep things simple, let’s skip them for now.
That’s it; we have now defined the state and behavior of our tiny blockchain runtime.
The full module code is available in the following GitHub gist. We have removed most of the code with came initially with the template.rs file and have only included the proper storage and functions needed for the token transfer functionality.
<a href="https://medium.com/media/41a8b7e1dc409ee384f1bba18b5e7c61/href">https://medium.com/media/41a8b7e1dc409ee384f1bba18b5e7c61/href</a>
Let’s now run the Substrate runtime we just created through the token transfer functions in the template.rs file.
First, to compile the runtime for the Wasm environment, run the following command in the context of the repository directory.
./build.sh
Once the above command completed, run the following command to build the Substrate node for the native environment.
cargo build --release
Running the node is the same as how we did before, just after creating the node-template.
./target/release/substrate-demo --dev
This command should again have a similar output, and the node should be up and running, producing blocks.
Now that we have the Substrate node running with the token transfer runtime, let’s connect it with a UI to see it working.
The simplest way would be to use the Polkadot Apps Portal. It is a hosted web app primarily made for connecting to the Polkadot network nodes but it can also connect to a local Substrate node.
To try it with the Polkadot Apps UI, follow the following steps,
Once the local node is running, open the following in your browser,
https://polkadot.js.org/apps/
Go to the settings page and select Local Node in the remote node/endpoint to connect to input. Click Save & Reload .
The apps portal would be connected to your local Substrate node and if you go to the Explorer page, then it should show the blocks as they are produced. The following screenshot shows the Explorer view of the Polkadot Apps portal with a local node connected.
To initialize the token, call the init() function under the template section in the Extrinsicspage of the Apps portal. See the following screenshot for reference.
As you can see, there is a pre-selected account Alice and it will be used to sign the function call when the Submit Transaction button is clicked.
When this transaction is finalized in a block, the account Alice will have all the 21000000 tokens as per the logic in the init() function in our module.
After calling the init() function from the UI, the account Alice should have the updated token balance of 21000000. Let’s verify that by checking the storage values from the UI.
Recall from the previous section that we used a storage item called BalanceOf to store token balances against AccountIds. Let’s check what is the balance stored with Alice’s AccountId.
You can use the Chain state page of the portal to query storage items. Navigate to this page, select template from the first drop-down menu (list of modules) and then select balanceOf(AccountId): u64 from the next menu. From the AccountId menu, select Alice. Now click on the + button. It will show you the updated value for Alice’s token balance (as shown in the following screenshot).
That’s it. We have built a simple blockchain runtime and connected it to a UI in less than 20 mins.
Yes, it is really that easy to get started with the Substrate framework.
This was just a tiny demo for kind of a “hello world” tutorial purposes. You can further extend this runtime module by implementing more functions like approval, transfer_from, etc (from the ERC20 interface).
Here are some resources to help you learn and build on Substrate.