Substrate Developer Hub

Substrate Developer Hub

  • 教程
  • 知识库
  • 进阶菜谱
  • API 文档
  • Languages icon简体中文
    • English
    • 繁體中文
    • 协助翻译

›Runtime

开始

  • 总览
  • 安装
  • 在 Windows 系统开始
  • 词汇表

学习 Substrate

  • Extrinsics
  • 交易池
  • 账户摘要
  • 会话密钥
  • 交易权重
  • 链下功能

Runtime

  • Runtime 总览
  • Runtime 的基本类型
  • FRAME
  • Pallets
  • Runtime宏
  • Runtime 元数据
  • Runtime 存储
  • Runtime 来源
  • Runtime 执行流程
  • Runtime事件
  • Runtime 错误
  • 交易费用
  • 链下工作机
  • 调试
  • Runtime 测试
  • 链上随机生成
  • Runtime 升级

智能合约

  • 总览
  • ink! 智能合约
  • ink! 概念
  • ink! 开发
  • EVM 模块
  • ink! 常问问题

整合

  • Polkadot-JS
  • 客户端库
  • 链描述
  • Subkey 工具
  • 内存分析

进阶

  • SCALE 编解码器
  • 共识机制
  • 区块导入过程
  • 执行器
  • 密码学
  • 存储
  • SS58 地址格式

贡献

  • 协助翻译
Translate

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。

文档和注释

  • API 文档

  • 该宏声明了一个 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,以将类型转换为其指定的值。

文档

  • API 文档

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。

文档

  • API 文档

impl_outer_event!

何时使用

此宏用于在runtime时构造一个 Event 结构体类型, 这个宏通常由 construct_runtime!宏自动调用。 然而开发人员也可以直接调用该宏来构造一个Event 枚举类型,以筛选其想要接收的特定pallet事件, 这对于构造测试用的模拟runtime非常有用。

有何作用

此宏创建了一个Event枚举类型,并在该类型上实现各种了辅助traits,包括core::clone::Clone、core::marker::StructuralPartialEq、core::fmt::Debug、数据编码/解码traits等。 但最终该宏仅会为runtime实现指定的pallet事件。

文档

  • API 文档

impl_outer_dispatch!

何时使用

此宏用于实现一个元调用模块,以把调用分派给其它调用者。 它通常是由 construct_runtime!自动调用的。 然而开发者可以直接调用该宏来构造一个 Call枚举类型,并筛选要调用的特定pallet, 这对于构造测试用的模拟runtime非常有用。

有何作用

该宏创建了一个Call 枚举类型,并在Event 类型上实现了各种辅助traits,包括Clone、PartialEq和RuntimeDebug 等。 最后,该宏还为 Call枚举类型实现了 GetDispatchInfo、GetCallMetadata、IsSubType这几个traits。

文档

  • API 文档

结论

通过本文,开发人员将能对Substrate runtime如何利用Rust宏有很好的了解,并对runtime开发中一些常用的宏有基本的掌握。

参考文档

  • 《Rustic编程语言》第19章第5节《宏》
  • 《Rust宏小册》
← PalletsRuntime 元数据 →
  • 介绍
  • 宏基础知识
  • Substrate Runtime宏
    • decl_storage!
    • decl_event!
    • decl_error!
    • decl_module!
    • construct_runtime!
    • parameter_types!
    • impl_runtime_apis!
    • app_crypto!
    • impl_outer_origin!
    • impl_outer_event!
    • impl_outer_dispatch!
  • 结论
  • 参考文档
Substrate Developer Hub
开发者中心
教程知识库进阶菜谱API 文档
社区
社区主页通讯Substrate 技术聊天室Substrate 研讨会Stack Overflow推特事件 (Events)
更多
Substrate Builders 计划BlogSubstrate GitHub开发者中心 GitHub隐私政策使用条款Cookie 设置
Copyright © 2021 Parity Technologies