深入substrate pallet

Pallet

Pallet到底是什么

从框架角度理解

  1. 框架和库的区别是什么? 框架和库本身都是一堆写好的代码和逻辑,使用起来都是先安装/下载。 但是二者最本质的区别在于“控制反转“:
  • 库是用来给开发者调用的,开发者将各种库同自己的代码结合起来编程一个有特定逻辑的程序
  • 框架是用来调用开发者写的业务逻辑。这里就出现’控制反转’,是框架来控制开发者编写的代码的使用时机
  • 结合这个使用时机,就出现了生命周期这个概念,这点不展开论述
  1. Substrate是一个框架,所以pallet其实就是它预留出来的“空格“ 开发者可以很方便地只实现业务相关的代码,整理成pallet,供substrate这个框架使用

Pallet基础组成

pallet基础模版

#![allow(unused)]
fn main() {
// 1. Imports and Dependencies
pub use pallet::*;

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

    // 2. Declaration of the Pallet type
    // This is a placeholder to implement traits and methods.
    #[pallet::pallet]
    #[pallet::generate_store(pub (super) trait Store)]
    pub struct Pallet<T>(_);

    // 3. Runtime Configuration Trait
    // All types and constants go here.
    // Use #[pallet::constant] and #[pallet::extra_constants]
    // to pass in values to metadata.
    #[pallet::config]
    pub trait Config: frame_system::Config { ... }

    // 4. Runtime Storage
    // Use to declare storage items.
    [pallet::storage]
    [pallet::getter(fn something)]
    pub MyStorage<T: Config> = StorageValue<_, u32>;

    // 5. Runtime Events
    // Can stringify event types to metadata.
    #[pallet::event]
    #[pallet::generate_deposit(pub (super) fn deposit_event)]
    pub enum Event<T: Config> {...
}

// 6. Hooks
// Define some logic that should be executed
// regularly in some context, for e.g. on_initialize.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { ... }

// 7. Extrinsics
// Functions that are callable from outside the runtime.
#[pallet::call]
impl<T: Config> Pallet<T> { ... }

}
}
Pallet基本格式1. 依赖:Imports and Dependencies2. 类型声明: Declaration of the Pallet typeRuntime3. 运行时配置:Runtime Configuration Trait4. 运行时存储: Runtime StorageStorage ValueStorage MapStorage Double MapStorage N Map5. 运行时事件: Runtime Events6. 生命周期钩子函数-Hooksnormalon_initializeon_idleon_finalizeupgradepre_upgradeon_runtime_upgradepost_upgradeoffchain_workerintegrity_test7. Extrinsics: 对外提供的功能

Pallet组件深入

返回顶部

1. Pallet Hooks

基于执行过程看hooks

Initializes the block会执行所有pallet就是construct_runtime!中包含的pallet,并且也是按照construct_runtime!中定义的顺序的on_initialize函数,不过会最先执行System模块的(frame-system).on_initialize区块初始化的时候调用Upgrade the blockpre_upgrade升级之前的检查on_runtime_upgrade执行模块升级的时候调用post_upgrade升级之后的处理Testintegrity_test运行集成测试Executes extrinsics区块初始化后,就会根据交易(extrinsics)列表的顺序执行。offchain_worker:在一个 pallet 上实现此函数后可以在此函数中长时间的执行需要链下执行的功能。该函数会在每次区块导入的时候调用Finalizes the blockon_idle:区块finalize的时候调用,不过比on_finalize先调用区块中的交易执行完后,确认区块确认区块时会调用所有pallet(就是construct_runtime!中包含的pallet,并且也是按照construct_runtime!中定义的顺序)的on_idle和on_finalize函数,不过这次最后执行System模块(frame-system)的hooks函数.on_initialize:区块初始化的时候调用

2. Pallet Extrinsics

Pallet-Extrinsics

3. Pallet Errors

Pallet-Errors

4. Pallet Config

Pallet-Config

5. Pallet Use Other Pallet

Pallet-Use-Other-Pallet

6. Pallet Extension

Pallet-Extension

7. Pallet Debug

Pallet-Debug

8. Pallet RPC

Pallet-RPC

9. Pallet Benchmarking

Pallet-Benchmarking

参考资源

官方资料

pallet

编写pallet到rust前置知识

编写简单到pallet

  • learn-substrate-easy/6编写简单的pallet.md at main · KuanHsiaoKuo/learn-substrate-easy
    • node-template的结构

    • 编写pallet的一般格式, 整理出7个部分, 1和2基本上是固定的写法,而对于后面的3-7部分,则是根据实际需要写或者不写。关于模板中每部分的解释,可以参考文档.

      1. 依赖;
      2. pallet类型声明;
      3. config trait;
      4. 定义要使用的链上存储;
      5. 事件;
      6. 钩子函数;
      7. 交易调用函数;
    • 举例编写simple-pallet

      功能介绍: simple-pallet是一个存证的pallet,简单说就是提供一个存取一段hash到链上的功能,和从链上读取的功能。

    • 将pallet添加到runtime中

    • 编译运行

    • 调试使用pallet中的功能

pallet的组成

  • learn-substrate-easy/7Pallet的组成.md at main · KuanHsiaoKuo/learn-substrate-easy

    要想熟练的开发pallet,我们必须得把pallet中的各个组成部分弄清楚。本节,我们就按照模板中的各个部分的顺序来讲解pallet的组成

    1. 导出和依赖:Pub mod pallet{}就是将我们的pallet暴露出来, pub use pallet::*;是可以使用pallet中的所有类型,函数,数据等
    2. pallet类型声明:它是一系列trait和方法的拥有者,实际的作用类似于占位符,这里举例rust程序
    3. config trait: 这部分是指定Runtime的配置trait,Pallet中使用的一些类型和常量在此trait中进行配置。
    4. Storage-定义要使用的链上存储: 存储(Storage)允许我们在链上存储数据,使用它存储的数据可以通过Runtime进行访问。substrate提供了四种存储方式,分别是:
      • Storage Value: 存储单个的值, 无键
      • Storage Map: 以map方式存储,单键,key-value
      • Storage Double Map: 以双键方式存储,(key1, key2)-value
      • Storage N Map: 以多键方式存储,(key1, key2, …, keyn)-value
    5. Event-事件:当pallet需要把运行时上的更改或变化通知给外部主体时,就需要用到事件。事件是一个枚举类型
    6. hooks-钩子函数:钩子函数,是在区块链运行过程中希望固定执行的函数,例如我们希望在每个区块构建之前、之后的时候执行某些逻辑等,就可以把这些逻辑放在钩子函数中
    7. Extrinsic-调度函数,交易调用函数: Extrinsic则是可以从runtime外部可以调用的函数,也是pallet对外提供的逻辑功能。比如交易

    路径用于引用模块树中的项 - Rust 程序设计语言 简体中文版

Pallet技巧细节

storage(链上)各个类型使用

区别pallet用到的storage和平时开发谈到的持久化storage

在pallet中要使用的storage更多的其实是一个应用层的概念,如果用城市建造来类比,持久化存储就像是整个城市的马路或者是管道,而我们谈论的storage则是某个具体建筑或者房屋里面的水管会小路,至于这些小水管(或小路)是怎么和整个城市的大路联系起来的,不是我们讨论的范围。

#![allow(unused)]
fn main() {
// 表示下面定义一个pallet storage
#[pallet::storage]
// 自动为storage生成一个getter函数,名字叫some_value
// 后续可以在pallet使用some_value()函数来获取该Storage中存储的值
#[pallet::getter(fn some_value)]
pub(super) type SomeValue = StorageValue<_, u64, ValueQuery>;
}

Error类型的使用

  • pallet中Error类型的使用 > 在runtime代码执行时,代码必须是“非抛出的”,或者说不应该panic,应该是优雅的处理错误,所以在写pallet代码时,允许我们自定义错误类型,当错误发生时,可以返回我们定义的错误类型。这里的Error类型是指运行时在执行调度函数(也就是交易函数)时返回的错误。因为在调度函数执行时,返回的结果为DispatchResult类型,当执行结果错误时,返回DispatchError。
    • 错误类型的定义
    • 在函数中返回错误
    • 简单示例

写调度函数的套路

  • substrate轻松学:写调度函数

    调度函数在substrate官方文档里面叫做Extrinsics(外部调用),详细的Extrinsics介绍可以参考这里. 在substrate中共有三种Extrinsics,分别是Inherents、Signed transactions和Unsigned transactions。 而在我们开发pallet的过程中,比较常用到的是后两种,所以我们这里也主要介绍后两种,对于Inherents有兴趣的小伙伴可以自己看官方文档研究下。

    • Signed transactions
    • Unsigned transactions
    • 通常写法:调度函数的位置->函数体的写法->权重->transactional
    • 示例

    参考:extrinsics &weights-and-fees

hooks的使用

交易到打包的过程

  1. 用户通过钱包发起交易;
  2. 和钱包相连的全节点收到交易后会把交易广播到网络中;
  3. 然后根据共识算法打包区块,某个全节点获得了打包权(图中画的是节点4), 然后将交易打包到区块中;
  4. 打包好区块后,将区块广播到网络中;
  5. 其它每个节点收到区块后验证,然后执行区块里面的交易,更新自己本地的账本。
- substrate中的执行过程
    1. 初始化区块(Initializes the block)
    2. 执行区块(Executes extrinsics)
    3. 确认区块( Finalizes the block).
- hooks介绍:
    1. on_finalize: 在区块 finalize 的时候调用。
    2. on_idle:区块finalize的时候调用,不过比on_finalize先调用。
    3. on_initialize:区块初始化的时候调用。
    4. on_runtime_upgrade:执行模块升级的时候调用。
    5. pre_upgrade:升级之前的检查。
    6. post_upgrade:升级之后的处理。
    7. offchain_worker:在一个 pallet 上实现此函数后可以在此函数中长时间的执行需要链下执行的功能。该函数会在每次区块导入的时候调用。后续我们讲ocw使用的时候就需要和这个函数打交道。
    8. integrity_test:运行集成测试。
- 示例
- [资料](https://docs.substrate.io/v3/concepts/execution/)
- [substrate源码](https://paritytech.github.io/substrate/master/frame_support/traits/trait.Hooks.html)

pallet中的Config

在pallet中使用其它pallet

  • learn-substrate-easy/8.6在pallet中使用其它pallet.md at main · KuanHsiaoKuo/learn-substrate-easy
  • 在pallet中使用其他pallet
    • 在自己的pallet中使用其它的pallet主要有以下几种情况:

      1. 指定某个现成的pallet: 在pallet的config中定义类型,然后runtime中使用时指定这个类型为frame中指定某个现成的pallet;
      2. 指定某个自定义的pallet: 在pallet的config中定义类型,然后runtime中使用时指定这个类型为frame中指定某个自定义的pallet;
      3. 封装和扩展现有的 pallet 。
    • 在runtime中直接指定某个类型为其它的pallet

      这种方式比较常见的就是在pallet中定义currency类型,然后用指定currency类型为balances pallet。详细的可以看substrate中node中的使用,在pallet_assets中使用了pallet_balances,就是通过指定前者的currency类型为后者

    • pallet中使用其它pallet的storage

      自定义两个pallet,分别叫做pallet-use-other-pallet1和pallet-storage-provider,然后我们在前一个pallet中读取和存储后一个pallet

封装和扩展现有pallet

调试

pallet中的类型转换;

在pallet中使用链下工作者(Offchain worker)

在pallet中链上写本地存储(offchain index);

在pallet的ocw中使用链下存储(offchain storage);

在pallet中使用其它pallet(使用其它pallet的存储);

在pallet中添加rpc接口

为某些trait提供默认实现。