When you start blockchain or DApp development, you have to build a lot of things from scratch. The consensus mechanism, network layer, node application, UI, and many other elements should be created before you start constructing the main business logic. Even if you decide to clone an existing blockchain solution, thereโs a risk of repeating its mistakes and copying the forks.
After development is finished, you may face other problems with scaling, upgrading, and uniforming. The version of your software needs to be identical for all users, up to date, and free of software conflicts. During its life cycle, an application should evolve and gain new features and logic, which should be immediately available for every user.
In many cases, these critical issues bring a lot of trouble for developers.
Luckily, the Parity team is developing a new tool for quick and convenient blockchain and DApp development: a framework called Substrate. It solves a lot of problems by supplying predefined functionality so developers can focus on business logic. Substrate may bring about a revolution in blockchain production.
Weโre going to explore the advantages and components of Substrate, then use it to develop a blockchain with a custom token. This article will be useful for developers and project managers who want to increase productivity and create a blockchain-based solution with the prospect of blockchain interoperability.
Contents:
- Overview
- Substrate offers built-in features
- Substrate components
- Substrate Core
- Wasm
- Interface specification
- Substrate Node
- Substrate Runtime Module Library
- API and UI
- How To Build Your Own Blockchain with Parity Substrate
- Prepare the environment
- Run the chain
- Create a module
- Chain update
- Chain interaction
- Advantages of Substrate
- Conclusion
Overview
Substrate is a framework for quickly building distributed and decentralized applications. Itโs an open-source technology stack used to create blockchains and customize them with maximum technical freedom and ease.
This tool is built by the Parity team. Parity blockchain software includes the Ethereum client and the Polkadot network. Polkadot, a cross-chain platform and the next big thing in cryptocurrency, is built using Parity Substrate. Because of this, Substrate supports the direct creation of and migration to Polkadot parachains.
You can use Substrate to create blockchains right now, without any interaction with Polkadot.
The modular design allows developers to choose between full control of code and maximum ease of configuring a blockchain. For example, you can use technologies bundled with Substrate by default, like the Hybrid PBFT/Aurand ัonsensus algorithm and Blake2b hashing function, or swap them out for your own.
Substrate offers built-in features
So what makes Substrate preferable for creating blockchains? It gives a lot of built-in features that can be used in your application:
- A working consensus mechanism, block finalizing algorithm, and validator voting system
- Networking, peer-to-peer connection layer, message sending and data replication functionality
- Full-node templates, ability to run in the browser
- Database abstraction, so almost any custom data can be used and stored on-chain
- A fast and fluent client update mechanism through Wasm, avoiding hard forks
- Ability to migrate to Polkadot as soon as itโs fully released
- A lot of modules to create various functionalities
- An API for interacting with your blockchain for DApp development
- A universal UI with predefined interfaces for applications built on your blockchain
- Development in Rust, so all the features of a full language can be used
With Substrate, a developer has a prepared environment for running their application with a blockchain, consensus mechanism, UI, etc. All you have to do is implement a very small number of hooks in your code. All of them are specified on the Substrate Developer Hub In general, you need only two functions: execute_block and version, along with version info. Starting with this base, youโre free to implement your ideas. You can focus on custom logic and features. Even transactions arenโt mandatory: you can turn them on or off, as theyโre included in a separate module.
As you can see, Substrate provides a prepared set of tools and an abstract interface, so blockchain development becomes easy, fast, and flexible.
Read also:
How to Build a Parachain on Polkadot
Substrate components
Substrate Core
This is the foundation of the Substrate framework. It contains all functionality for networking, peer-to-peer connections, the block synchronization mechanism, consensus, transactions mechanism, etc. Itโs complete enough to provide all necessary low-level components for a custom blockchain, so nothing needs to be built from.
Wasm
The first great feature used by Substrate is compilation into WebAssembly, or Wasm. Your code is compiled into two executables: the first is from the native language (Rust, in the case of Substrate), and the second is a WebAssembly blob.
The first is used by the client to run the node, and the second is deployed on the chain. Both are very fast in execution and both provide for quick network upgradability. Deployed on chain WebAssembly code ensures that all users will definitely have the same version of the chain node.
If someone hasnโt upgraded their client, a Wasm executable will be used but it is slightly slower. And once the client is updated, theyโll switch to the native code version with the speed boost. Because thereโs always a WebAssembly fallback, you can deploy a native version of the code at your own pace and feel safe knowing you can never accidentally get a hard fork or other consensus issues.
Interface specification
Youโre free to create any runtime API you want for your chain, though there are some requirements according to the Substrate specification. Every Substrate blockchain must have execute_block and version functions, to ensure that every block can be executed in a similar way and every user has strong knowledge of the current software version. But the interface for both of these functions โ along with a few others โ are already implemented in the Substrate template, so you only need to add the logic and donโt need to waste time on common interfaces.
Also, Substrate provides a lot of abstractions that can be redefined by the user while keeping the namespace. The block structure, header structure, execution and validation functions, runtime modules, on-chain storage, and events can freely be created if the names and interfaces follow the specification. Essentially, Substrate has no transactions: itโs just a state machine. To make Substrate as generic as possible, there are extrinsics, which are abstractions of the transactions. Theyโre binary blobs that can be fully customized for any data storage.
Substrate Node
Substrate Node is a template structure of a full blockchain node that contains predefined connections with Core functionality. Node is easily configurable and provides all the necessary hierarchy for building and deployment (for example, config files, cargo files, etc). This structure is not mandatory, and you may provide any full node architecture. But the Node template will save a lot of time and resources during application development.
Substrate Runtime Module Library
Substrate is built so you can write every component from scratch in your own way. However, its main advantage is lots of prebuilt components. These tools are found in the Substrate Runtime Module Library (SRML), a set of components that can be included in your runtime Wasm blob to be executed on-chain.
The System Module provides low-level APIs and utilities for other modules. Itโs the most valuable part of SRML, and it can be described as C++ std for Substrate. Think of this as the std (standard) library for SRML. In particular, the system module defines all the core types for the Substrate runtime, extrinsic events, etc.
The Executive Module is responsible for runtime execution. It dispatches incoming extrinsic calls to the respective modules in the runtime.
The support macros are a collection of Rust macros that help with the development of custom modules. In particular, they implement generic types (Module, Call, Store, Event, etc.) and fundamental macros: decl_module, decl_storage, decl_event. Support macros allow a developer to declare custom storage, modules with public APIs, and events.
SRML also provides a set of modules for typical blockchain functionality. There are some consensus modifying modules like Aura and GRANDPA. Balances is a module that implements almost complete token functionality: balances, accounts, transfer transactions, etc. Thereโs a smart contract module. Thereโs a fees module for customizing transaction costs, validator awards, etc. The most valuable module is the System module, which brings a lot of utils, macros, and other features to make development easier.
A lot of modules are in progress now, so the Substrate framework is growing and filling up with tools. For example, the Parity team is developing the Cumulus library, which will help migrate a Substrate project into the Polkadot parachain in a few steps.
API and UI
Among other instruments for blockchain development, Substrate imports a JavaScript API of its own design for building DApps on the chain.
Another great feature is SubstrateUI. Itโs a React-based web interface that brings predefined wallet operations, chain updating, account management, and other operations. Also, itโs fully customizable, so any new UI can be added in a few steps.
As you can see, Substrate imports not only the core blockchain functionality but all supporting structure too.
How to build your own blockchain with Parity Substrate
The Apriorit team has lots of experience developing blockchain applications. Tokenized systems are in demand. As an example of such a system, weโll implement the simplest prototype for creating a custom token and sending it between accounts. This is a common case: while the main system token/coin is used for transaction fees, payments, and financial operations, a custom token is used for service purposes, bonus programs, etc. Development through Substrate is pleasant and quick, so the whole process of creating a functioning blockchain application should take at most 15 minutes.
Prepare the environment
Substrate is written in the Rust programming language using Wasm, so for development, we need a Rust compiler, a cargo builder, and a Wasm compiler. Additionally, we need the latest versions of Node.js and npm for SubstrateUI and API access.
After initial preparations, we need the Substrate source code itself. Parity provides a general script to download and install all necessary components:
curl https://getsubstrate.io -sSf | bash -s -- --fast
This script integrates commands for interacting with Substrate components. Now we can create a Substrate sandbox and UI prototype:
substrate-node-new <folder name> <author-name>
substrate-ui-new <folder name>
Weโve chosen the name substrate-node-template for convenience. At this step, we build a blockchain with test accounts, balances, and validators set in src/chain_spec.rs, with the network options set in src/service.rs.
Transactions are enabled by default, and all fee information is included in src/chain_spec.rs in the genesis function. runtime/src/lib.rs contains rules for modules use and mandatory API functions used by chain and block validators (known to us as the validate_block and version functions).
Weโve also installed a UI interacting with the chain. So in two commands, weโve created a functional blockchain ready for simple financial operations, just like Substrate promised.
Run the chain
Now that we have a working blockchain, we can run it and access it through the UI. First, access the substrate-node-template folder and run:
./target/release/substrate-node-template --dev
After that, youโll see that the application is generating blocks. Now access the UI folder (substrate-ui-template) and run:
yarn run dev
If you access http://localhost:8000, youโll see the simple predefined interface for interacting with the chain.
Now you can perform simple operations for sending funds or accessing the console and using the Substrate API. As we have a predefined set of features up and running, itโs time to implement our own logic.
Create a module
The quickest way to add new features is by creating a new module. Our token will be implemented in a separate module in the substrate-node-template/runtime/src/bonus.rs file. All custom logic will be in the substrate-node-template/runtime/src folder.
A module can be divided into several parts: usage imports, events declared, storage declared, public module functions available for on-chain calls, and other module functions (if they exist). Three predefined Substrate macros are used: decl_event!, decl_storage! and decl_module! Weโll implement the necessary functions one by one.
- Imported items
We need:
- Main system macros
- Tools for handling addresses
- Storage manipulators
Weโll include a few functions for safe math.
use parity_codec::Codec;
use support::{dispatch::Result, StorageMap, StorageValue, Parameter,
decl_storage, decl_module, decl_event, ensure};
use system::{self, ensure_signed};
use runtime_primitives::traits::{CheckedSub, CheckedAdd, SimpleArithmetic,
Member, As};
And we should define some basic types to be used:
pub trait Trait: system::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
type TokenBalance: Parameter + Member + Default + Clone +
SimpleArithmetic + Codec + As<u128>;
}
The Event type allows conversion of function parameters into simple data types. TokenBalance inherits properties of several types. SimpleArithmetic and Codec allow arithmetical operations and default conversion at runtime. Default and Clone allow us to treat our TokenBalance type as an object. Parameter and Member types added to our token type allow numbers to be converted into this type in runtime API calls.
We can construct any possible type from the library of primitives created in Substrate.
- Storage
Our storage is declared through the decl_storage! system macros since we want to implement a simple blockchain runtime for an internal bonus system. If we want our logic to work, we need some additional values to be saved on-chain and be accessible for the nodes: the initialization flag of a module, total supply of tokens, and account balances of the bonus tokens.
decl_storage! {
trait Store for Module<T: Trait> as BonusToken {
Init get(initialized): bool;
TotalSupply get(total_supply): T::TokenBalance;
BalanceOf get(balance_of): map T::AccountId => T::TokenBalance;
}
}
- Events
For simplicity, the only event we define will be the transfer event. This event will be declared through the decl_event! system macros:
decl_event!(
pub enum Event<T> where
AccountId = <T as system::Trait>::AccountId,
Balance = <T as self::Trait>::TokenBalance
{
Transfer(AccountId, AccountId, Balance),
}
);
- Public interface
Our public interface will include the function for initializing a module and a simple function for transferring tokens. As all public interfaces are treated as extrinsics in Substrate, they have to return the Result type.
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
///Function for event emitting is generated by Substrate
fn deposit_event<T>() = default;
fn init(origin, supply: T::TokenBalance ) -> Result {
let sender = ensure_signed(origin)?;
ensure!(Self::initialized() == false, "Bonus program is already initialized.");
<TotalSupply<T>>::put(supply);
<BalanceOf<T>>::insert(sender.clone(), Self::total_supply());
<Init<T>>::put(true);
Ok(())
}
fn transfer(origin, to: T::AccountId, value: T::TokenBalance) -> Result {
let sender = ensure_signed(origin)?;
Self::transfer_mock(sender, to, value)
}
}
}
The first argument is common for all public functions: the address of the caller of the function.
- Private functions
As an example, weโll show the implementation of custom functions that arenโt visible on-chain. In our case, we take a common transfer function, which can be reused when developing a token module:
impl<T: Trait> Module<T> {
fn transfer_mock(
from: T::AccountId,
to: T::AccountId,
value: T::TokenBalance,
) -> Result {
ensure!(Self::initialized() == true,
"Token system is not initialized.");
ensure!(<BalanceOf<T>>::exists(from.clone()),
"Account does not participate in bonus program.");
let balance_from = Self::balance_of(from.clone());
let balance_to = Self::balance_of(to.clone());
ensure!(balance_from >= value, "Not enough bonuses.");
let new_balance_from = balance_from.checked_sub(&value).ok_or("overflow")?;
let new_balance_to = balance_to.checked_add(&value).ok_or("overflow")?;
<BalanceOf<T>>::insert(from.clone(), new_balance_from);
<BalanceOf<T>>::insert(to.clone(), new_balance_to);
Self::deposit_event(RawEvent::Transfer(from, to, value));
Ok(())
}
}
- Include the module in the node code
The final part is to integrate the module into the main Wasm interface declared in the construct_runtime! macros. Weโll modify the src/lib.rs file now.
First, add a variable for the new module:
mod bonus;
Then add the trait implementation of the module:
impl bonus::Trait for Runtime {
type Event = Event;
type TokenBalance = u128;
}
Finally, add the module to the runtime:
construct_runtime!(
pub enum Runtime with Log(InternalLog: DigestItem<Hash, AuthorityId, AuthoritySignature>) where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{default, Log(ChangesTrieRoot)},
Consensus: consensus::{Module, Call, Storage, Config<T>, Log( AuthoritiesChange ), Inherent},
Aura: aura::{Module},
Indices: indices,
Balances: balances,
Sudo: sudo,
BonusToken: bonus::{Module, Call, Storage, Event<T>},
}
);
The last thing is to upgrade the version:
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("apriorit-example"),
impl_name: create_runtime_str!("apriorit-bonus-token"),
authoring_version: 4,
spec_version: 3,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
};
And thatโs it. Our module is integrated into the main runtime, and now we can redeploy it on the chain.
Case Study:
How Blockchain Can be Used in Supply Chain: A Practical Example of a Custom Network Implementation
Chain update
After the module is developed, we can update the Wasm blob running on-chain. First, we should recompile the code. Among other templates, Substrate provides a set of BUILD files for cargo, so we can just run those in the main folder:
./scripts/build.sh
This command updates the Wasm file in the substrate-template-node/target folder. It simply recompiles Rust code with new dependencies for the substrate-node-template/runtime/wasm folder into the Wasm binary file. But you can also recompile it manually if you donโt use the template but rather a custom folder system.
As we have a UI running in the browser, we can then just navigate to the Upgrade section, load our newly created file, and wait while itโs put into the chain. This functionality for fast chain upgrades is also provided by default Substrate modules. The update will be shown in version changes:
In this way, weโve upgraded our chain in a few clicks and can interact with the new module.
Chain interaction
The first way to interact with the chain is directly through the Substrate API and public module interface calls. For example, we can initialize our bonus token program by calling from the web console:
post({sender: runtime.indices.ss58Decode('F7Hs'),
call: calls.bonus.init(1000)}).tie(console.log)
And we can check the balance of bonuses after initialization:
runtime.bonus.totalSupply.then(console.log)
Direct API calls arenโt convenient, however, so we can modify our UI to show bonus balances and allow the user to spend these bonuses.
Weโve already installed SubstrateUI.
Now weโll need a new block with token balances and some mechanism to spend them.
To create this block, navigate to the substrate-ui folder and access the src/app.jsx file, which contains generated UI classes for all default Substrate elements. Weโll add a new class for our bonus token block:
class BonusSegment extends React.Component {
constructor() {
super()
this.tokenOwner = new Bond
}
render() {
return <Segment style={{ margin: '1em' }} padded>
<Header as='h2'>
<Icon name='certificate' />
<Header.Content>Bonus tokens
<Header.Subheader>Bonus tokens</Header.Subheader>
</Header.Content>
</Header>
<div style={{ paddingBottom: '1em' }}>
<div style={{ fontSize: 'small' }}>Holder</div>
<SignerBond bond={this.tokenOwner} />
<If condition={this.tokenOwner.ready()} then={<span>
<Label>Balance
<Label.Detail>
<Pretty value={runtime.bonus.balanceOf(this.tokenOwner)} />
</Label.Detail>
</Label>
</span>} />
</div>
<TransactButton
content="Spend 1 token"
icon='game'
tx={{
sender: this.tokenOwner,
call: calls.bonus.transfer(<address for tokens spending>, 1)
}}
/>
</Segment>
}
}
As you can see, the class contains calls to the API of our module. The last thing to do is add the class to the total render function:
readyRender() {
return (<div>
<Heading />
...
<BonusSegment />
</div>);
}
Now we can see the new block added in our UI:
After we press the transaction button and wait for the update, weโll see the changes:
Development is finished. Itโs taken us little time to develop a simple blockchain runtime for our bonus system: we just added a file with the logic and storage implementation and a single block of code to the UI components.
Advantages of Substrate
Letโs underline all the advantages that Substrate provides to blockchain developers:
- Substrate offers a full set of tools and instruments to create a blockchain with a DApp structure in a very short time. It gives us all the main blockchain components: consensus, networking, UI, genesis structure, transactions, fee structure, etc. We just need to configure our blockchain, customize it (by turning on or off certain modules), and focus on implementing the business logic.
- Because of the wide structure of modules developed, Substrate allows developers to implement almost any type of monetary system. It supports many current token standards (ERC20, ERC721, etc). If we donโt need a transaction-based architecture, we can just turn the modules off and develop a pure decentralized application.
- A system of abstract types enables wide blockchain customization. Basically, we have just two mandatory functions names โ execute_block and version โ and a plan for the block structure. The rest of the design is at the discretion of the developer.
- Development is based on the Rust language. This makes development convenient. What is more important, this makes it possible to use features not supported in smart contracts.
- Thanks to Wasm, you can avoid hard forks and support a single software version for all users.
- Any blockchain developed on Substrate can easily be migrated to the Polkadot parachain. So the speed and convenience of Substrate development can be enhanced with a network effect.
Conclusion
The Apriorit Blockchain Development Team follows the market trends and looks for new, safe, and convenient tools for application development. We have vast experience using frameworks for blockchain development. Substrate is a tool that can definitely change the blockchain development process. Using it, our team can provide a high-quality blockchain-based solution faster than ever. Contact us if youโre planning a decentralized application project of any complexity.