substrate进阶(4)-pallet结构之调度函数、钩子函数、Config

本文主要介绍pallet的调度函数的实现

1. 调度函数

调度函数即Extrinsics(外部调用),在substrate中共有三种Extrinsics,分别是Inherents、Signed transactions和Unsigned transactions。一般主要用到后两种:

  1. Signed transactions:签名交易包含发起该交易的账户的签名,并会支付交易费用。因为签名交易的合法性可以在执行之前识别,所以它们可以在节点之间的网络上传播,属于垃圾信息的风险比较小。通俗说,就是需要支付gas的行为
  2. Unsigned transactions:不需要支付gas的行为,如查阅链上账户余额等行为,一般是只读行为。
  3. 实现: 所有调度函数都放置在#[pallet::call]标注的代码段内,每个调度函数上面需要标注其调用的权重,并根据需要是否添加#[transactional],如下格式:
1
2
3
4
5
6
7
8
9
10
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(10_000)] //具体的函数使用对应的权重计算函数
pub fn function1(origin: OriginFor<T>, param: u32, ...) -> DispatchResult {}

#[transactional]
#[pallet::weight(10_000)] //具体的函数换成对应的权重计算函数
pub fn function2(origin: OriginFor<T>, param: u32, ...) -> DispatchResult {}
//其它调度函数
}
  1. 一个函数的实现,主要是分三部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[pallet::weight(10_000)] 
pub fn set_number_bigger_than_100(origin: OriginFor<T>, number: u32) -> DispatchResult {
//这第一步根据实际情况选择:
// 如果是签名调用的则判断的写法是 ensure_signed(origin)?。
// 如果是需要root用户调用的则判断的写法是ensure_root(origin)?。
// 如果非签名的函数调用的判断写法则是ensure_none(origin)?。
ensure_signed(origin)?; //1、判断是否是合法调用

// 2、函数的具体逻辑
//...

//3、成功后需要发出事件
Self::deposit_event(Event::xx事件);

Ok(()) //固定写法
}
  1. 关于权重
    每个调度函数上面都需要标注其权重,在具体开发时,我们一般是先写一个固定的权重,然后在所以功能开发完成后,再写对应的benchmarking(后续文章讲解)

  2. 关于#[transactional]
    按我个人的理解,就是关系型数据库中的事务概念,保证调度函数执行的一致性。更具体点说就是当在函数中遇到错误后,会回滚状态,保证错误发生之前写入的状态回滚。
    该宏需要引入:use frame_support::transactional;

2. 钩子函数

  1. 交易到打包的过程
    1. 用户通过钱包发起交易;
    2. 和钱包相连的全节点收到交易后会把交易广播到网络中;
    3. 然后根据共识算法打包区块,某个全节点获得了打包权(图中画的是节点4), 然后将交易打包到区块中;
    4. 打包好区块后,将区块广播到网络中;
    5. 其它每个节点收到区块后验证,然后执行区块里面的交易,更新自己本地的账本。
  2. 在substrate中区块执行主要分为三步,分别是:
    1. 初始化区块(Initializes the block);
      • 初始化区块时就会执行所有pallet(就是construct_runtime!中包含的pallet,并且也是按照construct_runtime!中定义的顺序)的on_initialize函数,不过会最先执行System模块的(frame-system).
    2. 执行区块(Executes extrinsics);
      • 区块初始化后,就会根据交易(extrinsics)列表的顺序执行。
    3. 确认区块(Finalizes the block).
      • 区块中的交易执行完后,确认区块。确认区块时会调用所有pallet(就是construct_runtime!中包含的pallet,并且也是按照construct_runtime!中定义的顺序)的on_idle和on_finalize函数,不过这次最后执行System模块(frame-system)的hooks函数.
  3. hooks模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pub trait Hooks<BlockNumber> {
//在区块finalize的时候调用。
fn on_finalize(_n: BlockNumber) {}
//区块finalize的时候调用,不过比on_finalize先调用。
fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight {}
//区块初始化的时候调用。
fn on_initialize(_n: BlockNumber) -> Weight {}
//执行模块升级的时候调用。
fn on_runtime_upgrade() -> Weight {}
//升级之前的检查。
fn pre_upgrade() -> Result<(), &'static str> {}
//升级之后的处理。
fn post_upgrade() -> Result<(), &'static str> {}
//在一个pallet上实现此函数后可以在此函数中长时间的执行需要链下执行的功能。该函数会在每次区块导入的时候调用。后续讲ocw使用的时候就需要和这个函数打交道
fn offchain_worker(_n: BlockNumber) {}
//运行集成测试。
fn integrity_test() {}
}

3. Config

pallet的配置部分,主要是定义常量、数据类型。该内容比较简单,这里只大概介绍下。
如下定义数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::Event>;

//(1)声明了StudentNumberType和StudentNameType
//声明StudentNumber类型
type StudentNumberType: Member
+ Parameter
+ AtLeast32BitUnsigned
+ Codec
+ Copy
+ Debug
+ Default
+ MaxEncodedLen
+ MaybeSerializeDeserialize;

//声明StudentName类型
type StudentNameType: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ From<u128>
+ Into<u128>
+ Copy
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ Debug;
}

在runtime/src/lib.rs中引入的时候,在设置具体类型,确保了pallet更通用:

1
2
3
4
5
impl pallet_use_config2::Config for Runtime {
type Event = Event;
type StudentNumberType = u32; //指定具体的类型
type StudentNameType = u128; //指定具体的类型
}

总结

本文编辑完毕

  • Copyrights © 2017-2023 Jason
  • Visitors: | Views:

谢谢打赏~

微信