学习
实践
活动
专区
工具
TVP
写文章

深入浅出Substrate:剖析运行时Runtime

基于Substrate开发自己的运行时模块,会遇到一个比较大的挑战,就是理解Substrate运行时(Runtime)。本文首先介绍了Runtime的架构,类型,常用宏,并结合一个实际的演示项目,做了具体代码分析,以帮助大家更好地理解在Substrate中它们是如何一起工作的。

Runtime架构

Runtime的类型

,是个结构体类型

,是个枚举类型

,是个结构体类型

Runtime的宏

SRML架构:

有四个主要框架组件支持运行时模块:

System模块,它为其他模块提供底层级别的API和实用工具集。可以将其视为SRML的“std”(标准)库。特别是,系统模块定义了Substrate运行时的所有核心类型。

Executive模块,它充当运行时的业务流程层。它将传入的外部调用分派给运行时中的各个模块。

常见宏,它帮助实现模块的常见组件。这些宏在运行时扩展以生成类型(,,,等),运行时使用这些类型与模块进行通信。常见的宏是,,等。

Runtime,汇集了所有组件和模块。它扩展了宏以获取每个模块的类型和特征实现。它还调用Executive模块来分派各个模块的调用。

SRML(Substrate Runtime Module Library,运行时模块库),包含了一组预定义的模块,这些模块可以作为独立的功能在运行时重用。例如,SRML中的模块可用于跟踪帐户和余额,模块可用于创建和管理可替换资产等等。

可以通过派生System模块,或者选择其它预定义SRML模块(Assets, Aura, Balances, ..., Executive, Support)构建自己的自定义模块。

结构体

结构体是每个Substrate运行时模块的主干,由Substrate提供的宏生成。同时开发人员在编写自己的运行时模块时,可以为定义跟自己业务相关的函数和实现。

在宏中定义结构体:

友情提醒熟悉Rust语言中宏概念的,都应该知道:在宏中的代码,有些不是标准的Rust语法,而是Substrate扩展后的语法。

这个宏展开后,最终生成标准Rust语法的结构体定义如下:

如果您对模块完全展开后的代码感兴趣,可以尝试使用。

在这个结构体的基础上,Substrate实现了以下函数和特性,如:

可调用函数,为自己的运行时模块,提供维护操作区块链状态的逻辑。

,包含模块公开的所有运行时存储项,每个存储项都有一个结构体,其中定义了所有存储API。

,包含在块执行的开始或结束时运行的函数。

...

此外结构体可用于实现各种模块的内部函数,在宏中定义一个名为的函数,示例代码如下:

宏展开后的代码如下:

如示例中定义的函数,我们可以使用在整个模块中访问这些函数。

为了确保可以通过外部调用函数,结构体同时通过连接到模块的枚举实现了特性。所有可调用的函数都将通过枚举暴露给外部。下面会具体介绍。

最后,Substrate使用宏将整个结构体导入区块链的运行时。这个宏将自定义的模块和所有其他模块包含在一个名为的元组中。运行时的模块,使用此元组来处理执行这些模块的编排。

Call枚举

Substrate中,枚举列出运行时模块公开的可分派函数。每个模块都有自己的枚举,其中包含该模块的函数名称和参数。然后,在构造运行时,会生成一个外部枚举,作为每个模块特定的聚合。

在之前的示例中,在宏中定义函数,宏展开后会生成一个枚举。代码如下:

运行时中每个模块生成的枚举,Substrate会将此枚举传递给宏用于生成外部枚举,该枚举列出所有运行时模块并引用了它们各自的对象。示例如下:

宏展开后,生成以下外部枚举:

外部枚举收集了宏中的所有模块暴露的枚举,因此,它定义了区块链中完整的公开可调度函数集。

最后,当运行Substrate节点时,它将自动生成一个API,其中包含运行时生成的对象。这可以用于生成JavaScript函数,允许将调用分派给运行时。

Event枚举

Substrate中,枚举用作终端用户和客户端间通信。

声明Events

使用宏来声明。示例定义了如下:

宏展开

在编译时,宏展开,会为每个模块生成枚举。然后使用宏中指定的特征将事件类型生成为的具体实现。

示例中宏展开后生成的和类型。

跟类似,除了每个模块的类型之外,还有一个使用宏,为整个运行时生成的外部类型。此类型是合并了所有运行时模块的枚举。

为了订阅相关事件,客户端和应用程序需要知道哪些事件是运行时中每个模块的一部分。为此,Substrate的RPC API具有,它公开有关事件(和其他元数据)的信息。

可以在宏中声明要包含在区块链运行时中的所有运行时模块,包括SRML中的任何模块,以及自定义模块。

支持的类型:,,,,,,, 默认类型相当于包含前五个类型。

通过宏,定义模块公开的公共函数,它们充当访问运行时的入口点。这些特性和功能最终将包含在区块链的运行时中。

Substrate运行时模块库中的每个不同组件都是运行时模块的示例。

我们从最简单的形式开始,看如何使用:

类型的声明

通常在宏中的第一行,通过以下代码,定义类型:

对于大多数模块开发,这段代码不需要修改。

定义模块为泛型表示的类型。模块内的函数可以使用此泛型来访问自定义类型。

枚举是宏所需要的。将中定义的函数分派到此枚举中,并明确定义函数名称和参数。由运行时公开,以允许API和前端轻松交互。

最后是为简化中函数的参数定义而进行的优化。它的意思就是,函数中使用了变量,它的类型是由System模块定义的。

实现函数时的要求

为确保模块按预期运行,在开发模块功能时需要遵循这些规则。

绝不能。它可能导致潜在的拒绝服务(DoS)攻击。应该提前检查可能的错误情况并优雅地处理它们。

没有副作用的错误。函数必须完全完成,并返回,或者它返回对存储没有副作用的。基于Substrate开发,你必须知道如何设计运行时逻辑,对区块链状态所做的任何更改,确保遵循“先验证,后写入”的模式。它跟在以太坊平台上开发智能合约不一样。在以太坊,如果交易在任何时候失败(错误,没有汽油等),智能合约的状态将不受影响。但是,在Substrate上并非如此。一旦交易开始修改区块链的存储,这些更改就是永久性的,即使交易在运行时执行期间失败也是如此。

函数返回。模块中的函数无法返回一个值。它只能返回一个,当一切成功完成时返回,或者如果出现错误则返回。如果没有明确指定作为返回值,宏将自动添加它,将在最后返回。

计算成本。

检查。所有函数都使用来确定调用的来源。模块支持三种类型的检查:

应该总是使用其中一个作为在函数中做的第一件事,否则链可能是可攻击的。宏会自动转换没有的函数,并在函数中增加一行,来检查是否为。

签名的Extrinsic -

固有的Extrinsic -

Root -

保留函数

有一些函数名称是保留的,可以在自己的模块中使用它们。

如果想要模块发送事件,则需要定义函数,该函数处理在宏中定义的事件。事件可以包含泛型,在这种情况下,应该定义函数。

宏为函数提供了一个默认实现,可以通过简单地定义函数来访问它:

他们是特殊的函数,每个块执行一次。可以不带参数调用这些函数,也可以接受一个区块号的参数。

可以使用,在运行时的任何逻辑执行之前,运行需要运行的任务。可以使用,清理任何不需要的存储项或为下一个块重置某些值。

特权函数

特权函数是只能在调用来源是时调用的函数。可以在模块中找到特权函数的示例,进行运行时升级:

大多数运行时模块包含存储项,它在区块链运行时,用户与模块交互时被更改。

在宏中,初始化存储项的四种方式:

硬编码默认值:使用,并将初始值置于行末尾。

部署时赋值:仅使用,部署时赋值:1)在Rust代码中;2)在配置文件中。

单值计算:使用,通过闭包返回想要的初始值。

多值计算:使用。

示例中的存储项定义如下:

结构体

创世配置用于在Substrate运行时的第一个块初始化存储项的状态。

当宏展开时,它生成类型,其中包含使用参数声明的每个存储项的引用。支持创世配置的每个模块也将其类型别名为,作宏展开的一部分。

启动节点时,将使用外部将初始值设置到存储中。

结语

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190824A0O86300?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

关注

腾讯云开发者公众号
10元无门槛代金券
洞察腾讯核心技术
剖析业界实践案例
腾讯云开发者公众号二维码

扫码关注腾讯云开发者

领取腾讯云代金券