infrablockchain-docs
en
en
  • InfraBlockchain
    • Learn
      • Architecture
        • Architecture
        • Network Participants
        • Parachain
          • System Parachains
      • Protocol
        • System Token
        • Transaction Fee
        • Proof of Transaction
      • Substrate
        • Learn
          • Basic
            • Cryptography
            • Blockchain Basics
            • Consensus
            • Networks and Nodes
            • Blockchain Transaction
            • Transaction Life Cycle
            • Offchain Operations
            • Light Client
            • Rust for Substrate
            • Introduction to Library
            • Architecture and Rust Libraries
            • File Architecture
            • Accounts, Addresses, and Keys
            • Transaction Format
            • Blockchain Randomness
          • FRAME
            • FRAME Pallets
            • FRAME Macros
            • Custom Pallets
            • Pallet Coupling
            • Origin
            • Events and Erros
            • Runtime Storage
            • State Transitions and Storage
            • SCALE Encoding
            • Weight and Fee
            • Runtime API
            • Runtime Development
          • Account
          • Address Format
          • Glossary
          • CLI
            • Archive
            • Memory Profiler
            • Node Template
            • sidecar
            • srtool
            • Subkey
            • subxt
            • try-runtime
            • tx-wrapper
          • Runtime Development
            • Basics
              • Configure Genesis State
              • Configure Runtime Constants
              • Customize a Chain Spec
              • Import a Pallet
              • Use Helper Function
            • Consensus Model
              • PoW
              • Create a Hybrid Node
            • Offchain Worker
              • Request Offchain HTTP
              • Offchain Indexing
              • Offchain Local Storage
            • Pallet Design
              • Create a Storage Structure
              • Implement Lockable Currency
              • Incorporate Randomness
              • Loose Coupling
              • Tight Coupling
            • Parachain Development
              • Add HRMP Channel
              • Add Paranodes
              • Connect to a Local Relay Chain
              • Convert a Solo Chain
              • Prepare to Launch
              • Select Collator
              • Upgrade a Parachain
            • Storage Migration
              • Basics
              • Trigger Migration
            • Test
              • Basics
              • Test a Transfer Transaction
            • Tools
              • Create a TxWrapper
              • Use Sidecar
              • try-runtime
              • Verify WASM
            • Weigths
              • Benchmark
              • Calculate Fees
              • Use Conditional Weights
              • Use Custom Weights
        • Build
          • Decide What to Build
          • Build Process
          • Determinisitc Runtime
          • Chain Spec
          • Genesis Configuration
          • Application Development
          • RPC
          • Troubleshoot Your Code
        • Tutorials
          • Install
            • Developer Tools
            • Linux
            • macOS
            • Rust Toolchain
            • Issues
            • Windows
          • Quick Start
            • Explore the Code
            • Modify Runtime
            • Start a Node
            • Substrate Basics
          • Build a Blockchain
            • Add Trusted Nodes
            • Authorize Specific Nodes
            • Build a Local Blockchain
            • Simulate Network
            • Upgrade a Running Network
          • Build Application Logic
            • Add a Pallet
            • Add Offchasin Workers
            • Publish Custom Pallets
            • Specify Origin for a Call
            • Use Macros in a Custom Pallet
          • Integrate with Tools
            • Access EVM Accounts
            • EVM Integration
            • Explore Sidecar Endpoints
            • Integrate a Light Client Node
          • Smart Contracts
            • Strategy
            • Build a Token Contract
            • Develop a Smart Contract
            • Prepare Your First Contract
            • Troubleshoot Smart Contracts
            • Use Maps for Storing Values
      • XCM
        • XCM
        • XCM Format
    • Service Chains
      • InfraDID
      • InfraEVM
      • URAuth(Universal Resource Auth)
    • DevOps
      • Build
      • Deploy
      • Monitoring
      • Runtime Upgrade
    • Tutorials
      • Basic
        • How to Interact with System Token
        • How To Pay Transaction Fee
        • How To Vote with TaaV
        • Hot to Get Validator Reward
      • Build
        • Build InfraRelayChain
        • Build Parachain
        • Open Message Passing Channels
        • Transfer Assets with XCM
      • Test
        • Benchmark
        • Check Runtime
        • Debug
        • Simulate Parachains
        • Unit Testing
      • Service Chains
        • Play with InfraDID
          • Build
          • Add Keys
          • Add Service Endpoint
          • Create InfraDID
        • Play with InfraEVM
          • Build
          • Deposit and Withdraw Token
          • Deploy ERC20 Contract
          • Deploy ERC721 Contract
          • Deploy ERC1155 Contract
  • Newnal Data Market
Powered by GitBook
On this page
  • Digital objects and hashes
  • Digital objects and account signatures
  • How much time do you need to complete this tutorial?
  • Before you begin
  • Tutorial objectives
  • Design the application
  • Build a custom pallet
  • Configure the pallet to emit events
  • Implement pallet events
  • Include pallet errors
  • Implement a storage map for stored items
  • Implement callable functions
  • Build the runtime with your new pallet
  • Interact with your blockchain
  • Next steps
  1. InfraBlockchain
  2. Learn
  3. Substrate
  4. Tutorials
  5. Build Application Logic

Use Macros in a Custom Pallet

Create a custom pallet for a Substrate runtime using a skeleton of FRAME macros.

PreviousSpecify Origin for a CallNextIntegrate with Tools

Last updated 1 year ago

This tutorial illustrates how to create a custom pallet for a Substrate runtime using macros that are part of the development environment.

For this tutorial, you'll build a simple proof-of-existence application. Proof-of-existence is an approach to validating the authenticity and ownership of a digital object by storing information about the object on the blockchain. Because the blockchain associates a timestamp and account with the object, the blockchain record can be used to "prove" that a particular object existed at a specific date and time. It can also verify who the owner of a record was at that date and time.

Digital objects and hashes

Instead of storing an entire file on the blockchain, it can be much more efficient to simply store a of that file. This is also known as a "digital fingerprint". The hash enables the blockchain to store files of arbitrary size efficiently by using a small and unique hash value. Because any change to a file would result in a different hash, users can prove the validity of a file by computing the hash and comparing that hash with the hash stored on chain.

Digital objects and account signatures

How much time do you need to complete this tutorial?

This tutorial requires compiling Rust code and takes approximately one to two hours to complete.

Before you begin

For this tutorial, you download and use working code. Before you begin, verify the following:

  • You are generally familiar with software development and use command-line interfaces.

Tutorial objectives

By completing this tutorial, you will accomplish the following objectives:

  • Learn the basic structure of a custom pallet.

  • See examples of how Rust macros simplify the code you need to write.

  • Start a blockchain node that contains a custom pallet.

  • Add front-end code that exposes the proof-of-existence pallet.

Design the application

The proof-of-existence application exposes the following callable functions:

  • create_claim() allows a user to claim the existence of a file by uploading a hash.

  • revoke_claim() allows the current owner of a claim to revoke ownership.

Build a custom pallet

This tutorial demonstrates how to create your own FRAME pallet to be included in your custom blockchain.

Set up scaffolding for your pallet

This tutorial demonstrates how to create a custom pallet from scratch. Therefore, the first step is to remove some files and content from the files in the node template directory.

  1. Open a terminal shell and navigate to the root directory for the node template.

  2. Change to the pallets/template/src directory by running the following command:

    cd pallets/template/src
  3. Remove the following files:

    benchmarking.rs
    mock.rs
    tests.rs
  4. Open the lib.rs file in a text editor.

    This file contains code that you can use as a template for a new pallet. You won't be using the template code in this tutorial. However, you can review the template code to see what it provides before you delete it.

  5. Delete all of the lines in the lib.rs file.

  6. Add the macro required to build both the native Rust binary (std) and the WebAssembly (no_std) binary.

    #![cfg_attr(not(feature = "std"), no_std)]

    All of the pallets used in a runtime must be set to compile with the no_std features.

  7. // Re-export pallet items so that they can be accessed from the crate namespace.
    pub use pallet::*;
    
    #[frame_support::pallet]
    pub mod pallet {
      use frame_support::pallet_prelude::*;
      use frame_system::pallet_prelude::*;
    
      #[pallet::pallet]
      #[pallet::generate_store(pub(super) trait Store)]
      pub struct Pallet<T>(_);
    
      #[pallet::config]  // <-- Step 2. code block will replace this.
      #[pallet::event]   // <-- Step 3. code block will replace this.
      #[pallet::error]   // <-- Step 4. code block will replace this.
      #[pallet::storage] // <-- Step 5. code block will replace this.
      #[pallet::call]    // <-- Step 6. code block will replace this.
    }

    You now have a framework that includes placeholders for events, errors, storage, and callable functions.

  8. Save your changes.

Configure the pallet to emit events

To define the Config trait for the proof-of-existence pallet:

  1. Open the pallets/template/src/lib.rs file in a text editor.

  2. Replace the #[pallet::config] line with the following code block:

    /// Configure the pallet by specifying the parameters and types on which it depends.
    #[pallet::config]
    pub trait Config: frame_system::Config {
      /// Because this pallet emits events, it depends on the runtime's definition of an event.
      type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
    }
  3. Save your changes.

Implement pallet events

  • When a new claim is added to the blockchain.

  • When a claim is revoked.

Each event also displays an AccountId to identify who triggered the event and the proof-of-existence claim (as Hash) that is being stored or removed.

To implement the pallet events:

  1. Open the pallets/template/src/lib.rs file in a text editor.

  2. Replace the #[pallet::event] line with the following code block:

    // Pallets use events to inform users when important changes are made.
    // Event documentation should end with an array that provides descriptive names for parameters.
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
      /// Event emitted when a claim has been created.
      ClaimCreated { who: T::AccountId, claim: T::Hash },
      /// Event emitted when a claim is revoked by the owner.
      ClaimRevoked { who: T::AccountId, claim: T::Hash },
    }
  3. Save your changes.

Include pallet errors

The events you defined indicate when calls to the pallet have completed successfully. Errors indicate that a call has failed, and why it has failed. For this tutorial, you define the following error conditions:

  • An attempt to make a claim when a claim already exists.

  • An attempt to revoke a claim that doesn't exist.

  • An attempt to revoke a claim that is owned by another account.

To implement the errors for the proof-of-existence pallet:

  1. Open the pallets/template/src/lib.rs file in a text editor.

  2. Replace the #[pallet::error] line with the following code block:

    #[pallet::error]
    pub enum Error<T> {
      /// The claim already exists.
      AlreadyClaimed,
      /// The claim does not exist, so it cannot be revoked.
      NoSuchClaim,
      /// The claim is owned by another account, so caller can't revoke it.
      NotClaimOwner,
    }
  3. Save your changes.

Implement a storage map for stored items

To implement storage for the proof-of-existence pallet:

  1. Open the pallets/template/src/lib.rs file in a text editor.

  2. Replace the #[pallet::storage] line with the following code block:

    #[pallet::storage]
    pub(super) type Claims<T: Config> = StorageMap<_, Blake2_128Concat, T::Hash, (T::AccountId, T::BlockNumber)>;
  3. Save your changes.

Implement callable functions

The proof-of-existence pallet exposes two callable functions to users:

  • create_claim() allows a user to claim the existence of a file with a hash.

  • revoke_claim() allows the owner of a claim to revoke the claim.

These functions use the StorageMap to implement the following logic:

  • If a claim is already in storage, then it already has an owner and cannot be claimed again.

  • If a claim doesn't exist in storage, then it is available to be claimed and written to storage.

To implement this logic in the proof-of-existence pallet:

  1. Open the pallets/template/src/lib.rs file in a text editor.

  2. Replace the #[pallet::call] line with the following code block. You might try to implement the revoke_claim function yourself. Just copy the function signature and not the content. The Claims::<T>::get and Claims::<T>::remove should be used to get or remove a claim.

    // Dispatchable functions allow users to interact with the pallet and invoke state changes.
    // These functions materialize as "extrinsics", which are often compared to transactions.
    // Dispatchable functions must be annotated with a weight and must return a DispatchResult.
    #[pallet::call]
    impl<T: Config> Pallet<T> {
      #[pallet::weight(0)]
      #[pallet::call_index(1)]
      pub fn create_claim(origin: OriginFor<T>, claim: T::Hash) -> DispatchResult {
        // Check that the extrinsic was signed and get the signer.
        // This function will return an error if the extrinsic is not signed.
        let sender = ensure_signed(origin)?;
    
        // Verify that the specified claim has not already been stored.
        ensure!(!Claims::<T>::contains_key(&claim), Error::<T>::AlreadyClaimed);
    
        // Get the block number from the FRAME System pallet.
        let current_block = <frame_system::Pallet<T>>::block_number();
    
        // Store the claim with the sender and block number.
        Claims::<T>::insert(&claim, (&sender, current_block));
    
        // Emit an event that the claim was created.
        Self::deposit_event(Event::ClaimCreated { who: sender, claim });
    
        Ok(())
      }
    
      #[pallet::weight(0)]
      #[pallet::call_index(2)]
      pub fn revoke_claim(origin: OriginFor<T>, claim: T::Hash) -> DispatchResult {
        // Check that the extrinsic was signed and get the signer.
        // This function will return an error if the extrinsic is not signed.
        let sender = ensure_signed(origin)?;
    
        // Get owner of the claim, if none return an error.
        let (owner, _) = Claims::<T>::get(&claim).ok_or(Error::<T>::NoSuchClaim)?;
    
        // Verify that sender of the current call is the claim owner.
        ensure!(sender == owner, Error::<T>::NotClaimOwner);
    
        // Remove claim from storage.
        Claims::<T>::remove(&claim);
    
        // Emit an event that the claim was erased.
        Self::deposit_event(Event::ClaimRevoked { who: sender, claim });
        Ok(())
      }
    }
  3. Save your changes and close the file.

  4. Check that your code compiles by running the following command:

    cargo check -p node-template-runtime --release

    The [-p](https://doc.rust-lang.org/cargo/commands/cargo-check.html#options) node-template-runtime directive tells cargo to only check the node_template_runtime package.

Build the runtime with your new pallet

After you've copied all of the parts of the proof-of-existence pallet into the pallets/template/lib.rsfile, you are ready to compile and start the node.

To compile and start the updated Substrate node:

  1. Open a terminal shell.

  2. Change to the root directory for the node template.

  3. Compile the node template by running the following command:

    cargo build --release
  4. Start the node in development mode by running the following command:

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

    The --dev option starts the node using the predefined development chain specification. Using the --dev option ensures that you have a clean working state any time you stop and restart the node.

  5. Verify the node produces blocks.

Interact with your blockchain

Now that you have a new blockchain running with the custom proof-of-existence pallet, we can interact with the chain to make sure all the functionality works as expected!

To do this, we will use Polkadot JS Apps, which is a developer tool that can connect to and interact with any Substrate based blockchain.

By default, your blockchain should be running on ws://127.0.0.1:9944, so to connect to it we can use this link:

https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/

If your Substrate blockchain is running and Polkadot JS Apps is connected, you should see your block number increase in the top left corner:

Submit a claim

To test the proof-of-existence pallet using the front-end:

  1. Adjust the extrinsics page to select "ALICE" as the account, and "templateModule > createClaim" as the extrinsic.

  2. Then you can toggle "hash a file", which will allow you to select a file to hash and claim on the blockchain.

  3. Click "Submit Transaction" in the bottom right corner, then on the pop up click "Sign and Submit".

  4. If everything was successful, you should see a green extrinsic success notification!

Read a claim

The final step of this tutorial is to check what claims have been stored on your blockchain.

  1. Adjust the state query to "templateModule > claims".

  2. Toggle off the "include option" on the hash input to leave the input empty.

    This will allow us to see all the claims, rather than just one at a time.

  3. Press the "+" button to execute the query.

    Now you can see that the claim is stored in the blockchain with the data about the owners address and the block number when the claim was made!

Next steps

In this tutorial, you learned the basics of how to create a new custom pallet, including:

  • How to add events, errors, storage, and callable functions to a custom pallet.

  • How to integrate the custom pallet into the runtime.

  • How to compile and start a node that includes your custom pallet.

  • How you can use the Polkadot JS Apps developer tool to interact with your custom blockchain.

This tutorial covered the basics without diving too deeply into the code. However, there's much more you can do as you work toward building your own fully-customized blockchain. Custom pallets enable you to expose the features you want your blockchain to support.

To complete your understanding of the proof-of-existence chain try:

  • Claiming the same file again with "ALICE" or even the "BOB" account.

    • You should get an error!

  • Claiming other files with the "ALICE" and/or "BOB" accounts.

  • Revoking the claims with the appropriate claim owner account.

  • Looking at the final list of claims from reading storage.

Blockchains use to map digital identities to accounts that have private keys. The blockchain records the account you use to store the hash for a digital object as part of the transaction. Because the account information is stored as part of the transaction, the controller of the private key for that account can later prove ownership as the person who initially uploaded the file.

You have configured your environment for Substrate development by installing .

You have completed and have the Substrate node template installed locally.

You have used predefined accounts as described in to start nodes on a single computer.

The Substrate node template has a FRAME-based runtime. As you learned in , FRAME is a library of code that allows you to build a Substrate runtime by composing modules called pallets. You can think of the pallets as specialized logical units that define what your blockchain can do. Substrate provides you with a number of pre-built pallets for use in FRAME-based runtimes.

Add a skeleton set of pallet dependencies and that the custom pallet requires by copying the following code:

Every pallet has a called Config. You use this trait to configure the settings that your specific pallet requires. For this tutorial, the configuration setting enables the pallet to emit events.

Now that you've configured the pallet to emit events, you are ready to define those events. As described in , the proof-of-existence pallet emits an event under the following conditions:

To add a new claim to the blockchain, the proof-of-existence pallet requires a storage mechanism. To address this requirement, you can create a key-value map, where each claim points to the owner and the block number when the claim was made. To create this key-value map, you can use the FRAME .

You can refer to the node template if you get stuck.

Navigate to the tab.

Navigate to the tab.

public key cryptography
Rust and the Rust toolchain
Build a local blockchain
Simulate a network
Runtime development
macros
Rust "trait"
StorageMap
solution
"Developer > Extrinsics"
"Developer > Chain State"
Design the application
FRAME
cryptographic hash
File Hash
Runtime composition
Polkadot JS Explorer
Extrinsics Tab
Create Claim
Hash File
Submit Extrinsic
Extrinsic Success
Chain State
Query All Claims
Query Results