Runtime宏
介绍
Substrate的runtime宏使开发人员可以专注于runtiem逻辑,而不是想着如何对链上变量进行编码和解码。 因此,开发人员经常利用runtime宏带来的代码简洁性去优化runtime开发。 但有的时候,开发人员可能会碰到一些奇怪的错误,这些错误来自于runtime宏新创建的结构体和类型,它们与宏内其它内容交互时产生了错误。
本文的目的是对宏作一个概述,并对runtime工程师经常遇到的Substrate宏作详细解释。
宏基础知识
在Rust语言中有四种类型的宏:
- 用
macro_rules!
定义的声明宏 - 作为 自定义派生的过程宏
- 作为 类属性的过程宏
- 作过 类函数的过程宏
大多数Substrate runtime宏都是使用声明宏或类函数宏定义的。
学习Substrate runtime宏的方法如下:
- 阅读某个特定宏的文档
- 运行
cargo expand
命令去查看宏扩展后的代码 - 阅读宏定义。 如能很好地掌握 表达式模式匹配的宏规则是非常有帮助的。
Substrate Runtime宏
大多数开发者在开发Substrate runtime时都会使用到几个关键宏, 以下将对这几个宏做出全面解释。 阅读完本文之后,开发人员将能更好地了解到,在Substrate Node Template中这些宏的用法、作用以及扩展后代码。 我们鼓励想要了解实现细节的开发人员访问文档和示例 中的链接, 以了解如何扩展宏代码。
decl_storage!
何时使用
此宏用于在FRAME pallet中定义一个存储项目, 存储项目的定义包括:
数据类型,为下列其中一种:
StorageValue
类型:rust-type
StorageMap
类型:map hasher($hasher) rust_type => rust_type
StorageDoubleMap
类型:doublemap hasher($hasher) rust_type, hasher($hasher) rust_type => rust_type
获取函数,
键类型及哈希函数 (如果是map或double-map类型),
存储的名称,
默认值
这些存储值可通过其后的add_extra_genesis
模块在其创世区块中进行初始化。
decl_storage! {
trait Storage for Module<T: Trait> as MyModule {
// ...
}
add_extra_genesis {
build (|config| {
//...
});
}
}
有何作用
这个宏使用了简炼的语句:
#vis #name get(fn #getter) config(#field_name) build(#closure): #type = #default;
可在#name
的位置定义一个名字,该宏即可生成同名的新结构体,并自动实现其存储 trait。
该宏还声明并实现了Store
trait,为pallet设置存储空间,并在由 decl_module!
定义的Module
结构体上实现了获取函数。
文档和注释
- API 文档
- 每个存储名称都声明了
Store
trait,因此均成为了trait中的关联类型。 Module
的实现包含了每个存储项的获取函数和元数据的实现。- 每个存储项都变成了一个结构体。
decl_event!
何时使用
该宏用于定义runtime pallet发出的事件。
有何作用
该宏通过实现 Event
枚举类型来定义pallet事件,而宏中的每个事件类型都是Event
枚举类型内的一个成员。
文档和注释
- API 文档
- 定义
Event
或Event<T>
类型, 赋值给RawEvent
. RawEvent<...使用的所有泛型>
枚举类型里面的枚举成员是宏内列出的所有事件。- 宏为
RawEvent
实现了各种辅助traits,包括数据编码/解码,core::cmp::PartialEq
,core::cmp:: Clone
和core::fmt:: Debug
。 RawEvent t<...>
实现了metadata()
函数,以检索pallet中发出的事件元数据。
decl_error!
何时使用
此宏用于定义pallet在可调用函数中可能返回的错误类型。 可调用函数将返回 DispatchResult
类型,它的值可能是Ok(())
,也可能是包含了宏自定义错误的DispatchError
。
有何作用
此宏定义了Error<T: Trait>
类型,并实现了一些可把每个错误类型都映射到一个错误代码序数及相应字符串的辅助方法。
一个关键功能是,宏自动为DispatchError
实现了 From<Error<T>>
trait, 因此,DispatchError
能为特定的错误类型返回正确的模块索引、错误代码、错误字符串,以及返回作为元数据的文档。
文档和注释
- API 文档
Error<T: Trait>
枚举类型是由宏内定义的错误类型成员构成的。Error
实现了各种帮助函数,并将每个错误映射到相应的错误代码序号和错误字符串。Error
类型的实现包含了fn metadata()
函数的实现。Error
为DispatchError
实现了From<Error<T>>
这个trait,因此可调用函数能够返回相应的错误类型。
decl_module!
何时使用
此宏用于定义pallet中的可调用函数。
有何作用
此宏为包含它的pallet声明了一个名为 Module
的结构体,以及一个名为 Call
的枚举类型。 它通过用户自定义的可调用函数,将必要的逻辑组合成两种类型。 除了为 Module
和 Call
实现了各种辅助traits,如 Copy
、StructuralEq
、 Debug
以外,该宏还为Module
实现了生命周期trait,如 frame_support::traits::OnInitialize
, frame_support::traits::OnFinalize
, frame_support::traits::OnRuntimeUpgrade
,和frame_support::traits::OffchainWorker
。
文档和注释
该宏声明了一个
Module<T>
结构体和一个Call<T>
枚举类型,而其中枚举类型实现了可调用逻辑。宏会自动为
Module
结构体实现诸如core::cmp::Eq
、core::clone::Clone
等的辅助traits。Module
实现了各种生命周期traits,如frame_support::traits::OnInitialize
: 区块在初始化时的回调函数,frame_support::traits::OnRuntimeUpgrade
: 链上正在进行runtime升级时的回调函数,frame_support::traits::OnFinalize
: 区块在完结时的回调函数,以及frame_support::traits::OffchainWorker
: 区块完结时链下工作机的入口
Call<T: Trait>
实现了frame_support::dispatch::GetDispatchInfo
和frame_support::traits::UnfilteredDispatchable
这两个traits,包含了可调用函数核心逻辑。Module<T>
实现了frame_support::dispatch::Callable<T>
这个trait,而该trait关联类型Call
则被赋值给了Call
枚举类型。每个可调用函数都预先规划了其内部追踪逻辑,用于追踪节点活动。 开发者可设置一个兴趣等级,来决定是否要追踪一个可调用函数。
construct_runtime!
何时使用
此宏用于构造Substrate runtime,将各个pallets集成到runtime。
有何作用
该宏声明及实现了各种不同的结构体和枚举类型,如Runtime
、Event
、Origin
、 Call
、GenesisConfig
等,同时也为这些结构体类型实现了不同的辅助traits。 该宏还添加了一个逻辑,映射不同的事件和可调用函数,从整体的runtime到一个特定的pallet。
文档和注释
- API 文档
Runtime
结构类型是为Substrate runtime而定义的。Event
枚举类型的成员变量是所有可发出事件的pallets,并且实现了辅助traits和编码/解码traits。 用于不同类型转换的traitEvent
,实现了TryInto<pallets::Event<Runtime>>
这个trait,以从枚举类型中提取事件。Origin
枚举类型是通过实现辅助traits来定义的,如PartialEq
、Clone
、Debug
等traits。 此枚举类型定义了是谁调用了extrinsic:NONE
、ROOT
还是由特定帐户签名调用。 这三个原生的来源类型是由FRAME系统模块定义的,但是可选的FRAME pallets也可以定义额外的来源类型。Call
枚举类型由所有的集成pallet作为成员变量来定义的。 它包含每个集成pallet的数据和元数据,并通过实现frame_support::traits::UnfilteredDispatchable
trait将调用重定向到特定pallet。- 该宏定义了
GenesisConfig
结构类型,并实现了sp_runtime:: BuildStorage
trait以建立存储的创世配置。 - 该宏收集每一个pallet对
frame_support::unsigned::ValidateUnsigned
这个trait的实现, 如果没有任何一个pallet实现了ValidateUnsigned
trait,则所有的无签名交易都将被拒绝。
parameter_types!
何时使用
此宏用于在构造runtime时声明参数类型,这些参数类型将赋值给各pallet的可配置trait关联类型。
有何作用
该宏使用get()
函数返回的具体值,来替换掉结构体中指定的类型。 每个参数的结构体类型还实现了 frame_support::traits::Get<I>
这个trait,以将类型转换为其指定的值。
文档
impl_runtime_apis!
何时使用
此宏通过RuntimeApi
和RuntimeApiImpl
这两个结构体类型为客户端实现API。
有何作用
该宏定义了 RuntimeApi
和 RuntimeApiImpl
这两个结构体,以暴露给Substrate节点客户端。 它提供了RuntimeApiImpl初始的具体实现细节,在加上用户对RuntimeApiImpl
的特定实现。
文档和注释
- API 文档
- 宏声明了
RuntimeApi
和RuntimeApiImpl
两个结构体, 并为RuntimeApiImpl
实现了各种辅助trait。 - 开发人员在
impl_runtime_apis!
宏内定义的内容将被追加到RuntimeApiImpl
实现的末尾。 - 为了暴露runtime的版本信息,宏定义了一个常量
RUNTIME_API_VERSIONS
。 该常量包含runtime内核的ID
/VERSION
,元数据 的ID
/VERSION
,以及会话密钥的ID
/VERSION
等等。 - 公有模块
api
定义了一个dispatch()
函数,该函数可决定如何将不同的字符串映射到相应的元数据或链上生命周期函数。
app_crypto!
何时使用
此宏用于指定交给pallet管理的密钥对及其签名算法。
有何作用
该宏声明了三种结构体类型, Public
、Signature
和Pair
。 这三种类型除实现了各种辅助traits外, Public
类型还用于生成密钥对、签名和验证签名,Signature
type则用于在确定了签名加密方法 (e.g. SR25519, ED25519 etc)情况下保存签名属性,Pair
类型则用于用种子生成一个公私密钥对。
文档和注释
- API 文档
- 宏声明了
Public
结构体类型,并实现了定义公钥类型的sp_application_crypto::AppKey
trait,及实现了用于生成密钥对、签名和验证签名的sp_application_crypto::RuntimeAppPublic
trait。 - 宏声明了
Signature
结构体类型,并就签名数据如何哈希处理实现了core::hash::Hash
trait。 - 宏定义了
Pair
结构体类型来封装密钥对。 该类型实现了sp_application_crypto::Pair
和sp_application_crypto::AppKey
两个traits,从而决定了如何从助记词或者密码种子生成公私密钥对。
impl_outer_origin!
何时使用
此宏用于为runtime构造一个 Origin
结构体类型。 它通常由construct_runtime!
自动调用,但是开发人员也可以直接调用它,以构建用于测试的模拟runtime,而模拟runtime通常会比实际运行的runtime要简单很多。
每个extrinsic调用都传入了一个Origin
类型参数,以表示该调用是从NONE
、 ROOT
还是某个特定帐户发起的。
有何作用
该宏创建了一个Origin
结构体类型,并为该类型实现了各种辅助traits。
文档
impl_outer_event!
何时使用
此宏用于在runtime时构造一个 Event
结构体类型, 这个宏通常由 construct_runtime!
宏自动调用。 然而开发人员也可以直接调用该宏来构造一个Event
枚举类型,以筛选其想要接收的特定pallet事件, 这对于构造测试用的模拟runtime非常有用。
有何作用
此宏创建了一个Event
枚举类型,并在该类型上实现各种了辅助traits,包括core::clone::Clone
、core::marker::StructuralPartialEq
、core::fmt::Debug
、数据编码/解码traits等。 但最终该宏仅会为runtime实现指定的pallet事件。
文档
impl_outer_dispatch!
何时使用
此宏用于实现一个元调用模块,以把调用分派给其它调用者。 它通常是由 construct_runtime!
自动调用的。 然而开发者可以直接调用该宏来构造一个 Call
枚举类型,并筛选要调用的特定pallet, 这对于构造测试用的模拟runtime非常有用。
有何作用
该宏创建了一个Call
枚举类型,并在Event
类型上实现了各种辅助traits,包括Clone
、PartialEq
和RuntimeDebug
等。 最后,该宏还为 Call
枚举类型实现了 GetDispatchInfo
、GetCallMetadata
、IsSubType
这几个traits。
文档
结论
通过本文,开发人员将能对Substrate runtime如何利用Rust宏有很好的了解,并对runtime开发中一些常用的宏有基本的掌握。