Introduction

“substrate” is defined as “the base on which an organism lives.” It’s an apt name for Polkadot’s developer toolkit, as the Substrate SDK provides developers a flexible, modular framework to build projects that can operate as independent blockchains or leverage the interoperability of other Substrate blockchains, like Polkadot and Kusama.

Substrate changes this equation entirely.

Substrate is a modular blockchain development framework that lets you build production-ready blockchains in weeks instead of years. Created by Parity Technologies (founded by Ethereum co-founder Dr. Gavin Wood), it powers over 100 blockchains in production, handling billions of dollars in value.

This guide explores what Substrate is, how it works technically, and how to use it to build your own blockchain.

What is Substrate?

The Framework That Changes Everything

Substrate is a blockchain development framework written in Rust that provides all essential components needed to build a custom blockchain. Instead of spending years building infrastructure, developers focus on what makes their blockchain unique.

Think of it as WordPress for blockchains, but more powerful. Like Unity for game development or React for web applications a comprehensive environment with modular components you compose to create what you need.

What You Get Out of the Box

Core Infrastructure:

  • Peer-to-peer networking (libp2p)
  • Multiple consensus mechanisms
  • Database management (RocksDB)
  • Transaction pool handling
  • Cryptography libraries
  • RPC server for clients

Modular Runtime:

  • Pre-built pallets (feature modules)
  • Custom business logic support
  • Forkless upgrades
  • WebAssembly execution

Developer Tools:

  • Testing framework
  • Benchmarking system
  • CLI tools
  • Type-safe APIs

Key Characteristics

Development Speed: Projects that traditionally take 2-5 years can be built in weeks to months. This isn’t cutting corners—it’s leveraging comprehensive, tested components.

Flexibility: Full control over blockchain logic, choice of consensus mechanisms, custom governance models, and unlimited customization options.

Security: Battle-tested across production networks, written in memory-safe Rust, extensively audited, and securing billions in value.

Performance: High-throughput transaction processing, efficient state management, optimized resource usage, and production-grade performance.

Upgradeability: Forkless runtime upgrades through on-chain governance, eliminating network splits and community drama.

Why Was Substrate Built?

The Problems of Traditional Blockchain Development

Before Substrate, every blockchain project faced the same exhausting reality. reinventing fundamental infrastructure before building unique features.

What Teams Had to Build:

  • Networking protocols from scratch
  • Consensus algorithms
  • Database management
  • Cryptographic primitives
  • P2P communication

The Costs:

  • Bitcoin: Years of development
  • Ethereum: 18+ months to launch
  • Most projects: 2-5 year minimum timeline
  • Millions in funding burned on infrastructure

The Consequences:

  • New code = new vulnerabilities
  • Limited security audits
  • High risk of critical bugs
  • 90% effort on infrastructure, 10% on innovation
  • Slow iteration cycles

The Upgrade Problem: Hard forks split communities (Bitcoin vs Bitcoin Cash, Ethereum vs Ethereum Classic), creating governance nightmares and network fragmentation.

Their Goals:

  • Lower barriers to entry
  • Accelerate blockchain innovation
  • Enable specialized chains
  • Support seamless evolution
  • Share security infrastructure

The Solution

Substrate provides 90% of blockchain infrastructure pre-built. The modular architecture enables customization where it matters. in your unique logic and features.

Revolutionary Features:

  • Forkless upgrades: Runtime stored on-chain, automatic propagation
  • Production-ready: Securing billions across networks from day one
  • Modular design: Build complex systems from tested components

Architecture: How Substrate Works

The Three-Layer Design

Substrate separates functionality into three distinct layers, enabling flexibility and upgradeability through clean architectural boundaries.

┌─────────────────────────────────────────┐
│       APPLICATION LAYER                                             
│    (Your Custom Business Logic)         
├─────────────────────────────────────────┤
│        RUNTIME LAYER (WASM)            
│  ┌──────────────────────────────────┐ 
│  │     Pallets (Modules)            
│  │  ┌────────┐  ┌────────┐        
│  │  │Balances│  │Staking │  ...    
│  │  └────────┘  └────────┘         
│  │                                  │  │
│  │  FRAME (Development Framework)  
│  └──────────────────────────────────┘ 
├─────────────────────────────────────────┤
│       CLIENT LAYER (Native)           
│  ┌──────────┬──────────┬────────────┐  │
│  │Networking│Consensus │  Database  │  
│  │  (libp2p)│  Engine  │  (RocksDB) │  
│  └──────────┴──────────┴────────────┘  │
└─────────────────────────────────────────┘

Layer 1: The Client Layer

The client layer handles blockchain infrastructure. Written in native Rust for performance, it manages networking, consensus, storage, and communication.

Networking (libp2p):

  • Peer discovery and connections
  • Block propagation
  • Transaction gossip
  • DHT and routing

Consensus (Pluggable):

  • BABE for block production
  • GRANDPA for finality
  • Aura for simple PoA
  • PoW for testing
  • Custom implementations

Storage (RocksDB):

  • High-performance key-value store
  • Merkle-Patricia trie for state
  • Efficient pruning
  • State snapshots

Transaction Pool: Manages pending transactions, priority ordering, validity checking, and DoS protection.

RPC Server: Provides JSON-RPC endpoints, WebSocket support, state queries, and transaction submission.

Key Point: You rarely modify this layer. Substrate handles infrastructure for you.

Layer 2: The Runtime Layer

The runtime is your blockchain’s state transition function—it defines valid transactions, state changes, and blockchain rules.

Why WebAssembly?

  • Platform-independent execution
  • Sandboxed security
  • Deterministic results
  • Near-native performance
  • Stored on-chain (upgradeable!)

Runtime Composition:

construct_runtime!(
    pub enum Runtime {
        // System pallets
        System: frame_system,
        Timestamp: pallet_timestamp,
        
        // Financial
        Balances: pallet_balances,
        TransactionPayment: pallet_transaction_payment,
        
        // Governance
        Democracy: pallet_democracy,
        
        // Your custom logic
        MyCustomFeature: my_custom_pallet,
    }
);

Dual Runtime Execution:

  • WASM Runtime: On-chain, authoritative, upgradeable
  • Native Runtime: Compiled into node, faster when version matches

This enables forkless upgrades. update on-chain WASM, all nodes automatically adopt it.

Layer 3: FRAME

FRAME (Framework for Runtime Aggregation of Modularized Entities) makes modular development practical.

What FRAME Provides:

  • Modular pallet system
  • Powerful macro system (reduces boilerplate)
  • Support libraries and utilities
  • Executive module (orchestrates execution)

Key Macros:

  • #[pallet::pallet] – Define structure
  • #[pallet::storage] – Define state
  • #[pallet::call] – Define functions
  • #[pallet::event] – Define events
  • #[pallet::error] – Define errors

FRAME handles the tedious integration work, letting you focus on unique logic.

Technical Deep Dive

Storage: The State Machine

Substrate uses a key-value database with Merkle-Patricia trie structure for cryptographically verifiable state.

Storage Types:

StorageValue – Single Value

#[pallet::storage]
pub type TotalSupply<T> = StorageValue<_, Balance, ValueQuery>;

TotalSupply::<T>::put(1_000_000);
let supply = TotalSupply::<T>::get();

StorageMap – Key-Value Pairs:

#[pallet::storage]
pub type Balances<T: Config> = StorageMap<
    _,
    Blake2_128Concat,     // Hasher (important!)
    T::AccountId,         // Key
    Balance,              // Value
    ValueQuery,
>;

Balances::<T>::insert(&account, 1000);

StorageDoubleMap – Two Keys:

#[pallet::storage]
pub type Allowances<T: Config> = StorageDoubleMap<
    _,
    Blake2_128Concat, T::AccountId,  // Owner
    Blake2_128Concat, T::AccountId,  // Spender
    Balance,
>;

Critical: Storage Hashers

Secure Hashers (Use for User Keys):

  • Blake2_128Concat – Cryptographically secure
  • Blake2_256 – Even more secure

Fast but Insecure (System Keys Only):

  • Twox64Concat – Vulnerable to attacks!
  • Twox128, Twox256 – Still not secure

Rule: Always use Blake2_128Concat for user-controlled keys. Using weak hashers allows attackers to craft malicious keys causing collisions.

The Weight System

Weights measure computational cost to prevent DoS attacks and ensure fair resource usage.

What Weights Measure:

  • Reference Time: CPU computation (picoseconds)
  • Storage I/O: Database operations
  • Proof Size: For light clients

Example:

#[pallet::weight(
    T::DbWeight::get().reads(2) +
    T::DbWeight::get().writes(1) +
    Weight::from_parts(50_000_000, 0)
)]
pub fn transfer(
    origin: OriginFor<T>,
    dest: T::AccountId,
    amount: Balance,
) -> DispatchResult {
    // Implementation
}

Block Limits: Each block has maximum weight (typically 2 seconds of computation). This prevents:

  • Infinite loops
  • Block stuffing attacks
  • Resource exhaustion

Fees: Calculated as base_fee + weight × fee_multiplier

Benchmarking

Don’t guess weights. measure them:

#[cfg(feature = "runtime-benchmarks")]
benchmarks! {
    transfer {
        let caller: T::AccountId = whitelisted_caller();
        let amount = 1000u32.into();
        
    }: _(RawOrigin::Signed(caller.clone()), dest, amount)
    verify {
        assert_eq!(T::Currency::free_balance(&dest), amount);
    }
}

Run benchmarks to generate accurate weights:

bash

./target/release/node benchmark pallet \
    --pallet pallet_balances \
    --extrinsic transfer

Transaction Lifecycle

1. Creation: User constructs a call to a pallet function

2. Submission: Sent to node via RPC, broadcast to network

3. Validation (SignedExtensions):

  • Verify sender validity
  • Check runtime compatibility
  • Validate nonce
  • Ensure weight within limits
  • Check fee affordability

4. Block Production: Validator selects transactions by priority (typically fees)

5. Execution:

  • Verify signatures
  • Dispatch to correct pallet
  • Execute business logic
  • Update storage
  • Emit events
  • Charge fees

6. Finalization: Block propagated, consensus finalizes, state becomes permanent

Origins: Authorization System

Origins determine who’s calling and their permissions.

Built-in Types:

pub enum RawOrigin<AccountId> {
    Root,                // Governance
    Signed(AccountId),   // User transaction
    None,                // Unsigned
}

Usage:

#[pallet::call]
impl<T: Config> Pallet<T> {
    // Anyone can call
    pub fn public_function(origin: OriginFor<T>) {
        let who = ensure_signed(origin)?;
    }
    
    // Only governance
    pub fn admin_function(origin: OriginFor<T>) {
        ensure_root(origin)?;
    }
}

Custom Origins

pub enum CustomOrigin {
    Council,
    TechnicalCommittee,
    Treasury,
}

Events: Blockchain Logging

Events notify external observers without persisting in state.

Defining Events:

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
    Transfer { from: T::AccountId, to: T::AccountId, amount: Balance },
    AccountFrozen { account: T::AccountId },
}

Emitting:

Self::deposit_event(Event::Transfer { from, to, amount });

Properties:

  • Stored in block header (not state)
  • Indexed by explorers
  • Cheaper than storage
  • Essential for dApp integration

Pallets: The Building Blocks

What is a Pallet?

A pallet is a self-contained module encapsulating specific blockchain functionality. Pallets are composable. they can depend on and interact with each other.

Complete Pallet Structure

#[frame_support::pallet]
pub mod pallet {
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;

    // Configuration
    #[pallet::config]
    pub trait Config: frame_system::Config {
        type RuntimeEvent: From<Event<Self>>;
        type Currency: Currency<Self::AccountId>;
        type MaxItems: Get<u32>;
    }

    #[pallet::pallet]
    pub struct Pallet<T>(_);

    // Storage
    #[pallet::storage]
    pub type ItemCount<T> = StorageValue<_, u32, ValueQuery>;
    
    #[pallet::storage]
    pub type Items<T: Config> = StorageMap<
        _,
        Blake2_128Concat,
        T::AccountId,
        BoundedVec<Item, T::MaxItems>,
    >;

    // Events
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        ItemAdded { owner: T::AccountId, item_id: u32 },
    }

    // Errors
    #[pallet::error]
    pub enum Error<T> {
        TooManyItems,
        ItemNotFound,
    }

    // Callable Functions
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::weight(10_000)]
        pub fn add_item(
            origin: OriginFor<T>,
            item: Item,
        ) -> DispatchResult {
            let who = ensure_signed(origin)?;
            
            Items::<T>::try_mutate(&who, |items| {
                items.try_push(item)
            })?;
            
            Self::deposit_event(Event::ItemAdded { 
                owner: who, 
                item_id: ItemCount::<T>::get() 
            });
            
            Ok(())
        }
    }

    // Lifecycle Hooks
    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
        fn on_initialize(n: BlockNumberFor<T>) -> Weight {
            Weight::zero()
        }
    }
}

Common System Pallets

Core Pallets:

  • frame_system – Core functions, accounts, events
  • pallet_timestamp – Block timestamps
  • pallet_balances – Token balances and transfers
  • pallet_transaction_payment – Fee handling

Governance:

  • pallet_democracy – Public referendums
  • pallet_collective – Council mechanics

Financial:

  • pallet_assets – Custom token creation
  • pallet_treasury – Community funds
  • pallet_vesting – Token vesting schedules

Utility:

  • pallet_utility – Batch transactions
  • pallet_scheduler – Delayed execution
  • pallet_proxy – Proxy accounts

Pallet Composition

Pallets interact through trait dependencies:

// Treasury uses Balances
impl pallet_treasury::Config for Runtime {
    type Currency = Balances;
}

// Inside pallets, use other pallets
impl<T: Config> Pallet<T> {
    pub fn reward_user(who: &T::AccountId, amount: BalanceOf<T>) {
        T::Currency::deposit_creating(who, amount);
    }
}

Conclusion

Substrate democratizes blockchain development by combining modular architecture, WebAssembly-based runtimes, and forkless upgrades. What once required years of development can now be achieved in a structured and efficient manner.

LEAVE A REPLY

Please enter your comment!
Please enter your name here