├── assets ├── RADME ├── 封装1.JPG ├── 封装2.JPG ├── 封装3.JPG ├── 运行图.PNG ├── hook2.PNG ├── hooks.PNG ├── 创建存证.PNG ├── 查询存证.PNG ├── 第一次设置.PNG ├── 设置前后.JPG ├── hooks1.PNG ├── 以太坊架构图.png ├── 区块链系统1.PNG ├── 发送http.JPG ├── 查询class.PNG ├── 查询寝室信息.PNG ├── 第二次设置错误.PNG ├── 设置class.PNG ├── 设置学生信息.PNG ├── 设置寝室信息.PNG ├── insertkey1.JPG ├── 设置storage.JPG ├── 调用其它pallet.JPG ├── substrate架构图.PNG ├── local_storage1.JPG ├── node-template关系.PNG ├── offchain_index1.JPG └── offchain_index2.JPG ├── README.md ├── 11升级runtime.md ├── 8编写pallet技巧概述.md ├── 12编写复杂的benchmarking.md ├── SUMMARY.md ├── 12升级substrate版本.md ├── 1前言.md ├── 2区块链与substrate.md ├── 4substrate快速了解.md ├── 3substrate构建一条链的体验.md ├── 8.14在ocw中发送http请求.md ├── 8.13使用offchain_index.md ├── 8.10在ocw中提交未签名交易.md ├── 8.2Error类型的使用.md ├── 8.8调试.md ├── 8.4Hooks函数使用.md ├── 5编写pallet的Rust前置知识.md ├── 8.12在ocw中使用链下存储.md ├── 7Pallet的组成.md ├── 8.7封装和扩展现有pallet.md ├── 8.3写调度函数的套路.md ├── 9编写tests.md ├── 8.11在ocw中提交具有签名payload的未签名交易.md ├── 8.1storage使用介绍.md ├── 8.5pallet中的Config.md ├── 8.6在pallet中使用其它pallet.md ├── 8.15在pallet中添加rpc接口.md ├── 8.9使用OCW提交签名交易.md ├── 6编写简单的pallet.md └── 10编写benchmarking.md /assets/RADME: -------------------------------------------------------------------------------- 1 | 文章中的图片 2 | -------------------------------------------------------------------------------- /assets/封装1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/封装1.JPG -------------------------------------------------------------------------------- /assets/封装2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/封装2.JPG -------------------------------------------------------------------------------- /assets/封装3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/封装3.JPG -------------------------------------------------------------------------------- /assets/运行图.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/运行图.PNG -------------------------------------------------------------------------------- /assets/hook2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/hook2.PNG -------------------------------------------------------------------------------- /assets/hooks.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/hooks.PNG -------------------------------------------------------------------------------- /assets/创建存证.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/创建存证.PNG -------------------------------------------------------------------------------- /assets/查询存证.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/查询存证.PNG -------------------------------------------------------------------------------- /assets/第一次设置.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/第一次设置.PNG -------------------------------------------------------------------------------- /assets/设置前后.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/设置前后.JPG -------------------------------------------------------------------------------- /assets/hooks1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/hooks1.PNG -------------------------------------------------------------------------------- /assets/以太坊架构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/以太坊架构图.png -------------------------------------------------------------------------------- /assets/区块链系统1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/区块链系统1.PNG -------------------------------------------------------------------------------- /assets/发送http.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/发送http.JPG -------------------------------------------------------------------------------- /assets/查询class.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/查询class.PNG -------------------------------------------------------------------------------- /assets/查询寝室信息.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/查询寝室信息.PNG -------------------------------------------------------------------------------- /assets/第二次设置错误.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/第二次设置错误.PNG -------------------------------------------------------------------------------- /assets/设置class.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/设置class.PNG -------------------------------------------------------------------------------- /assets/设置学生信息.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/设置学生信息.PNG -------------------------------------------------------------------------------- /assets/设置寝室信息.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/设置寝室信息.PNG -------------------------------------------------------------------------------- /assets/insertkey1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/insertkey1.JPG -------------------------------------------------------------------------------- /assets/设置storage.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/设置storage.JPG -------------------------------------------------------------------------------- /assets/调用其它pallet.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/调用其它pallet.JPG -------------------------------------------------------------------------------- /assets/substrate架构图.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/substrate架构图.PNG -------------------------------------------------------------------------------- /assets/local_storage1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/local_storage1.JPG -------------------------------------------------------------------------------- /assets/node-template关系.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/node-template关系.PNG -------------------------------------------------------------------------------- /assets/offchain_index1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/offchain_index1.JPG -------------------------------------------------------------------------------- /assets/offchain_index2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anonymousGiga/learn-substrate-easy/HEAD/assets/offchain_index2.JPG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | substrate轻松学教程。 4 | 5 | 一份方便入门的substrate pallet开发教程。 6 | 7 | 阅读更多Rust和区块链技术文章,可搜索微信公众号“令狐一冲” 8 | -------------------------------------------------------------------------------- /11升级runtime.md: -------------------------------------------------------------------------------- 1 | # 升级Runtime 2 | 3 | runtime的升级比较简单,基本上只要按照官方的文档,就可以顺利的完成runtime的升级。 4 | 5 | # 基本过程描述 6 | 修改pallet 7 | 设置spec_version 8 | 重新编译runtime 9 | 使用polkadot-js-app进行升级,步骤为选择sudo,选择uncheckWeights,然后选择system,选择setCode。 10 | 11 | 12 | # 参考文档 13 | 14 | https://docs.substrate.io/tutorials/get-started/forkless-upgrade/ 15 | 16 | 17 | -------------------------------------------------------------------------------- /8编写pallet技巧概述.md: -------------------------------------------------------------------------------- 1 | 本节开始,我们讲解编写pallet的一些技巧,主要分为以下部分: 2 | * [storage(链上)各个类型使用](8.1storage使用介绍.md); 3 | * [Error类型的使用](8.2Error类型的使用.md); 4 | * [写调度函数的套路](8.3写调度函数的套路.md); 5 | * [hooks的使用](8.4Hooks函数使用.md); 6 | * [pallet中的Config](8.5pallet中的Config.md); 7 | * [在pallet中使用其它pallet](8.6在pallet中使用其它pallet.md); 8 | * [封装和扩展现有pallet](8.7封装和扩展现有pallet.md); 9 | * [调试](8.8调试.md); 10 | * pallet中的类型转换; 11 | * [在pallet中使用链下工作者(Offchain worker)](8.5在pallet中使用OCW.md); 12 | * 在pallet的ocw中使用链下存储(offchain storage); 13 | * 在pallet中链上写本地存储(offchain index); 14 | * 在pallet中使用其它pallet(使用其它pallet的存储); 15 | * 为某些trait提供默认实现。 16 | -------------------------------------------------------------------------------- /12编写复杂的benchmarking.md: -------------------------------------------------------------------------------- 1 | # 编写复杂的benchmarking 2 | 上节我们讲了编写benchmarking的基本步骤,也基本上可以满足大部分编写benchmarking的需求。但是在某些情况下,我们还有些稍微复杂的benchmarking的编写,会变得不一样。这里的不一样主要是当你的调度函数比较复杂,一些使用条件是需要外部的pallet来构建的情况,就例如substrate官方frame中的session和offences pallet。 3 | 4 | 最关键的就是config,要求config兼容多个pallet。下面我们就以一个完整的例子说明。 5 | 6 | # 1 准备一个简单的pallet 7 | 我们首先准备一个简单的pallet,其源码如下: 8 | ``` 9 | //todo 10 | ``` 11 | 12 | # 2 编写mock runtime 13 | 编写mock runtime,源码如下: 14 | ``` 15 | //todo 16 | ``` 17 | 18 | # 3 编写benchmakring 19 | 编写benchmarking的代码如下: 20 | ``` 21 | //todo 22 | ``` 23 | 24 | # 4 在runtime中添加 25 | todo 26 | 27 | # 5 执行命令,生成权重 28 | 29 | # 6 源码地址 30 | 31 | # 7 参考文档 32 | 33 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # substrate轻松学总览 2 | * [1 前言](1前言.md) 3 | * [2 区块链与substrate](2区块链与substrate.md) 4 | * [3 substrate构建一条链的体验](3substrate构建一条链的体验.md) 5 | * [4 substrate快速了解](4substrate快速了解.md) 6 | * [5 编写pallet的Rust前置知识](5编写pallet的Rust前置知识.md) 7 | * [6 编写简单的pallet](6编写简单的pallet.md) 8 | * [7 Pallet的组成.md](7Pallet的组成.md) 9 | * [8 编写pallet技巧概述](8编写pallet技巧概述.md) 10 | * [8.1 storage使用介绍](8.1storage使用介绍.md) 11 | * [8.2 Error类型的使用](8.2Error类型的使用.md) 12 | * [8.3 写交易函数的套路](8.3写交易函数的套路.md) 13 | * [8.4 pallet中的类型转换](8.4pallet中的类型转换.md) 14 | * [8.5 在pallet中添加rpc接口](8.5在pallet中添加rpc接口.md) 15 | * [8.6 在pallet中使用OCW](8.6在pallet中使用OCW.md) 16 | * 9 测试 17 | * 10 benchmarking 18 | -------------------------------------------------------------------------------- /12升级substrate版本.md: -------------------------------------------------------------------------------- 1 | # 升级substrate版本 2 | 首先这里的升级主要是指跟随这substrate官方代码进行的升级,例如之前是polkadot-0.9.12,现在要升级到polkadot-0.9.13. 3 | 4 | # 1 为什么要升级substrate版本? 5 | 跟随substrate升级的原因主要如下: 6 | * 有些之前老版本存在的一些bug,会在后续的版本修复,如果不跟着升级,可能会有功能和性能的问题,更严重的是怕遇到安全问题。 7 | * 新版本可能有一些行的功能,不跟随升级就无法使用这些新特性、新功能。 8 | * 社区里面一般都是讨论的最新版本的相关技术,不跟随升级可能很难在社区进行交流。 9 | 10 | # 2 升级过程 11 | 这里通过举例说明。假定升级前我们的代码是polkadot-0.9.12的代码,然后我们要升级到polkadot-0.9.13. 那么大致的过程是,我们需要在polkadot-0.9.13的模板上重新加上我们的业务代码,变化编译测试成功。 12 | 13 | 下面主要讲一下如何再已经运行了之前版本的主网或者测试网上升级新的版本的节点。主要分以下几步: 14 | 15 | * 准备新版本(例子中应该是polkadot-0.9.13)的节点执行程序; 16 | * 关闭一个节点,启动新版本的节点; 17 | * 依次重复第二步。 18 | 19 | 这里需要特别注意的是第一步中,准备新版本的执行程序时,不管其它的pallet或者是runtime相关的东西有没有更新,runtime中的spec_version都必须在以前的基础上加1,否则在进行第二步和第三步后出现莫名其妙的链停掉的问题。 20 | -------------------------------------------------------------------------------- /1前言.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 很羡慕那些动不动就能把一个东西搞的清晰见底的大神,当面临一门新或者是比较难的技术时,他们总能够像武侠中那些练武奇才一样,快速摸通技术的底层原理,迅速打通任督二脉,然后用的比一般的熟手还厉害。可能是天性愚笨,这样的境界于我而言常常感觉是无法企及,但是很多时候虽然愚而自知,却仍改不了好为人师的毛病。之前录制了一些Rust和区块链技术相关的视频,虽然质量不高,但是仍然收到了大伙的鼓励,突然顿悟,一门技术晦涩难懂的高深的知识需要人分享,浅显入门的知识同样需要人分享。 4 | 5 | substrate作为一个区块链框架,使用的人在日益增多,资料也在日益完善。但是对于普通的小白来说,要快速入门还是有一些难度。所以这又给好为人师的我提供了机会,搞一个substrate的入门教程,能尽量让更多人的快速入门。因为substrate是一个区块链框架,其中的逻辑往往以pallet的形式出现,所以这个教程其实主要是讲怎么来开发pallet的教程。我目前(后续可能会变化)计划的讲解内容主要如下: 6 | 7 | * 区块链与substrate 8 | 9 | * substrate构建一条链的体验 10 | 11 | * substrate快速了解 12 | 13 | * 编写pallet的Rust前置知识 14 | 15 | * 编写简单的pallet 16 | 17 | * pallet的组成讲解 18 | 19 | * 编写pallet的其它技巧 20 | 21 | * 编写测试 22 | 23 | * 编写benchmark 24 | 25 | * runtime升级 26 | 27 | 编写教程的难度是不小的,编写出能让人方便入门的教材更是困难的。但是我会以我在substrate上的有限的功力,尽力做出一份方便入门的教程,最起码是能够上手开始写pallet的教程。 28 | 29 | 30 | -------------------------------------------------------------------------------- /2区块链与substrate.md: -------------------------------------------------------------------------------- 1 | # 区块链与substrate 2 | 3 | 从2008年中本聪发布《比特币:一种点对点的电子现金系统》开始,区块链技术开始进入我们的世界,区块链防篡改、去信任等等概念更是被炒的热火朝天。那么什么是区块链,它能做什么?到底有没有前途?这不是我们这节要回答的问题(这个机灵是不是抖的很ok?:))我们这节要讲的,是一个区块链系统应用的大体表现形式,有些概念或者表达可能不太准确,但是没有关系,这节的内容只是为了帮我们能更好的学习substrate而已。 4 | 5 | ## 1 区块链系统是什么样的 6 | 很多资料都会提到这点,区块链是一个分布式的数据库,本质是一个分布式的账本。既然是分布式的,那当然具备分布式的特征,既然是数据库,是账本,那么当然能够记录数据。关于区块链是怎样记账的文档很多,此处我们不再累述。 7 | 8 | 要学习substrate,区块链系统具体是怎么去信任、防篡改的,我们可以不用了解太深,但是区块链系统大概长什么样子,是必须要知道的。就像我们要用积木搭一个城堡,可以不用知道从头开始建城堡的实际步骤,但是城堡的大概样子还是要知道的,否则即使有积木也不知道怎么搭。 9 | 10 | ### 1.1 区块链的大体样貌 11 | 下图就是区块链系统的大体样貌(表现形式)。 12 | ![区块链系统的大体样貌](assets/区块链系统1.PNG) 13 | 14 | 从上图中,我们可以看出区块链的几个特点: 15 | * 是一个分布式的系统; 16 | * 每个节点都有一个账本; 17 | * 每个节点的账本都基本上按照同样的逻辑记账; 18 | * 用户可以访问其中的任何一个节点就可以访问系统。(为了好理解,这里全是全节点) 19 | 20 | ### 1.2 区块链程序的组织结构 21 | 上面我们是用户试图看到的区块链系统的表现形式,这里我们看看从代码实现层面区块链的组成。下面是以太坊的架构图: 22 | ![以太坊架构图](assets/以太坊架构图.png) 23 | 24 | 以太坊很具有代表性,大部分的区块链通常都是这个架构(**所以这张图要好好记住,比较有代表性**)。不过我们放这张图的目的并不是为了后面实现这张图里类似的组件。 25 | 26 | 27 | ## 2 substrate与区块链开发 28 | 如果没有substrate,那么我们开发一条区块链基本上是要自己来实现网络、共识、交易池等等组件的,也就是前面区块链架构里面的那些东西。而substrate里面就是像提供积木一样提供了这些组件,然后我们可以像搭积木一样根据需求搭建出一条条的区块链。 29 | 30 | 因为要构成一条链,无非就是共识、网络、密码算法库、rpc这些东西,每个团队开发区块链的时候,可能用的东西都差不多,只有某些和自己特定业务相关的模块才会不一样。而substrate中已经提供了一些基础的东西以及一些写好的模块,开发者可以把现成的模块拿来用,只需要开发自己特定的逻辑就可以了。 31 | 32 | -------------------------------------------------------------------------------- /4substrate快速了解.md: -------------------------------------------------------------------------------- 1 | # substrate快速了解 2 | 3 | ## 1 substrate的应用方式 4 | 5 | 有以下三种方式来使用substrate开发区块链: 6 | 7 | * 直接使用substrate node 8 | 9 | 上一节中快速体验substrate创建一条链就是使用的这种方式,这种方式基本上不修改代码,配置一下chain_spec即可(也可以用默认的)。如果玩过bitcoin或者ethereum开发的小伙伴,想想一下修改genesis block的配置起一个类似的节点直接山寨一条链,基本就和这种使用方式类似。 10 | 11 | * 使用substrate frame构建运行时 12 | 13 | 这种方式就是substrate提供的大的框架下,定义自己的运行时(也就是链的一些具体逻辑),然后形成一条新的链。这个理解起来可能有些费解,举个例子,我们在前面的章节给大家介绍过以太坊的架构,也是常见的区块链的架构,在这个架构中,有一个组件是共识,不同的链可能使用不同的共识。如果我们要实现有着不同共识的链,我们就可以用这种方式来开发,将里面对应的共识模块弄成我们想要的即可。在这种方式中,主要开发的就是这些模块,substrate中叫做pallet。subtrate自身提供了一些lib和pallet,我们也可以自己开发一些pallet。 14 | 15 | **这种方式也是这份教程教大家的方式。** 16 | 17 | 在这里,还要补充一点,对于大多数使用substrate开发的链来说,可能像网络、共识、密码库等等这些模块都不想动,他们只想在某些基础上添加一些自己业务相关的逻辑快速的完成开发,就像我们上一节快速的搭建一条链那样。基于这种情况,substrate提供了一条链的基本配置的模板[node-template](https://github.com/substrate-developer-hub/substrate-node-template)(也可以把它理解为使用substrate开发一条链的示例)。所以,对于大多数使用substrate开发链的情况就变成了基于node-template,然后使用substrate开发自己业务逻辑的pallet,加到node-template上,就完成了整条链的开发。 18 | 19 | * 使用substrate core 20 | 21 | 这种方式是最灵活的方式,但是也是难度最大的方式。这种方式可以忽略substrate中提供的frame,支持完全自定义的开发。这种方式后续在本教程中不加讨论。 22 | 23 | 24 | ## 2 substrate链的架构 25 | 26 | 既然我们主要是使用substrate构建运行时的方式开发,那么这样开发出来的链的架构是怎么样的呢?substrate官方文档已经给我们画出来了,如下: 27 | 28 | ![substrate架构图](assets/substrate架构图.PNG) 29 | 30 | 在这个架构中,已经给我们提供了基本的网络、rpc等这些模块,那么其它的应用逻辑模块就可以使用前面说的第二种方式进行开发,从而实现特定应用的特定的区块链。 31 | 32 | ## 3 参考文档 33 | 34 | https://docs.substrate.io/v3/getting-started/overview/ 35 | 36 | https://docs.substrate.io/v3/getting-started/architecture/ 37 | -------------------------------------------------------------------------------- /3substrate构建一条链的体验.md: -------------------------------------------------------------------------------- 1 | # substrate构建一条链的体验 2 | 3 | 但凡我们要开始学习某个区块链系统,常常做的第一件事情就是把这个区块链系统的代码拉下来,然后编译后起个节点来跑一下。substrate官方教程里面的[第一课](https://docs.substrate.io/tutorials/v3/create-your-first-substrate-chain/)名称叫做创建我们的第一条链,实际上我觉得应该叫做启动substrate默认模板链的节点更贴切,因为这个教程里面实际上就是把一个用substrate已经开发好的模板链的代码拉下来,然后编译一下,然后再启动起来。这个过程实际上和我们拉一个比特币的代码,然后编译下然后再启动 4 | ,并没有太大的不同。不过即使是这样,我们还是要罗嗦一下,快速的把这个过程走一边。 5 | 6 | ## 1 substrate开发环境 7 | 8 | 编译substrate模板主要需要一些预编译包和Rust开发环境,安装的命令如下: 9 | ``` 10 | # 1.安装预编译包 11 | sudo apt update && sudo apt install -y git clang curl libssl-dev llvm libudev-dev 12 | 13 | # 2.安装Rust编译环境 14 | curl https://sh.rustup.rs -sSf | sh 15 | source ~/.cargo/env 16 | rustup default stable 17 | rustup update 18 | rustup update nightly 19 | rustup target add wasm32-unknown-unknown --toolchain nightly 20 | ``` 21 | 执行完上述命令后,可以用如下命令进行查看: 22 | ``` 23 | rustc --version 24 | rustup show 25 | ``` 26 | 27 | 至此,编译环境就准备好了。 28 | 29 | ## 2 启动链的节点 30 | 31 | 接下来就是启动链的节点,这里要用到node-template的代码。node-template实际上是官方提供的使用substrate开发的模板链,可以理解为substrate官方提供的样例,后续任何人想使用substrate可以在这个样例的基础上进行修改,这样开发链就更方便。这就好比以前的好多山寨链,在btc的源码上改下创世区块的配置,就是一条新链。那么substrate其实也一样,提供了node-template这样一个模板,后续根据需求在这个上面改吧改吧,就能产生一条新链。 32 | 33 | 当然我们这里是快速演示,所以就不需要修改,直接使用就行了。 34 | 35 | ### 2.1 下载node-template 36 | 37 | 命令如下: 38 | ``` 39 | git clone https://github.com/substrate-developer-hub/substrate-node-template 40 | cd substrate-node-template 41 | git checkout latest 42 | ``` 43 | 44 | ### 2.2 编译 45 | 46 | 命令如下: 47 | ``` 48 | cargo build --release 49 | ``` 50 | 51 | ### 2.3 运行节点 52 | 53 | ``` 54 | ./target/release/node-template --dev 55 | ``` 56 | 57 | 至此,我们就把使用substrate开发的模板链的节点启动起来了,不过我们此时启动的链只有一个节点。 58 | 59 | ## 3 使用polkadot-js访问节点 60 | 61 | 在substrate官方的教程中,是使用了substrate的前端模板来访问刚才启动的节点。但是在实际的开发中,后端人员其实更多的使用polkadot-js-app来访问我们的节点,所以这里我们也使用它来访问我们的节点。 62 | 63 | 访问方式如下: 64 | ``` 65 | 1、在浏览器中输入https://polkadot.js.org/apps; 66 | 2、点击左上角会展开; 67 | 3、在展开的菜单中点击DEVELOPMENT; 68 | 4、点击Local Node; 69 | 5、点击switch。 70 | ``` 71 | 72 | 此时就可以看到连到了node-template节点的界面,可以看到链相关的一切信息。 73 | 74 | ## 4 参考文献 75 | 76 | https://docs.substrate.io/tutorials/v3/create-your-first-substrate-chain/ 77 | 78 | -------------------------------------------------------------------------------- /8.14在ocw中发送http请求.md: -------------------------------------------------------------------------------- 1 | # 在ocw中发送http请求 2 | 3 | 本节我们开始学习在ocw中发送http请求,主要用到sp_runtime::offchain::http和lite_json库。详细的实现我们看代码。 4 | 5 | # 1 ocw中发送http请求实现 6 | 这部分的功能非常简单,就是发送请求https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD 来获取btc价格,获取价格的主要代码如下: 7 | ``` 8 | impl Pallet { 9 | fn parse_price(price_str: &str) -> Option { 10 | let val = lite_json::parse_json(price_str); 11 | let price = match val.ok()? { 12 | JsonValue::Object(obj) => { 13 | let (_, v) = 14 | obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?; 15 | match v { 16 | JsonValue::Number(number) => number, 17 | _ => return None, 18 | } 19 | }, 20 | _ => return None, 21 | }; 22 | 23 | let exp = price.fraction_length.saturating_sub(2); 24 | Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) 25 | } 26 | 27 | fn fetch_price() -> Result { 28 | let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); 29 | let request = http::Request::get( 30 | "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD", 31 | ); 32 | let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; 33 | 34 | let response = 35 | pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; 36 | if response.code != 200 { 37 | log::warn!("Unexpected status code: {}", response.code); 38 | return Err(http::Error::Unknown) 39 | } 40 | 41 | let body = response.body().collect::>(); 42 | 43 | let body_str = sp_std::str::from_utf8(&body).map_err(|_| { 44 | log::warn!("No UTF8 body"); 45 | http::Error::Unknown 46 | })?; 47 | 48 | let price = match Self::parse_price(body_str) { 49 | Some(price) => Ok(price), 50 | None => { 51 | log::warn!("Unable to extract price from the response: {:?}", body_str); 52 | Err(http::Error::Unknown) 53 | }, 54 | }?; 55 | 56 | log::warn!("Got price: {} cents", price); 57 | Ok(price) 58 | } 59 | ``` 60 | 61 | 然后我们在ocw中调用上面实现的fetch_price函数,当获取到价格后便将值打印出来,如下: 62 | ``` 63 | #[pallet::hooks] 64 | impl Hooks> for Pallet { 65 | fn offchain_worker(_block_number: T::BlockNumber) { 66 | if let Ok(data) = Self::fetch_price() { 67 | log::info!(target:"offchain-index-demo", "1. get price, price ======================== {:?}", data); 68 | } else { 69 | log::info!(target:"offchain-index-demo", "2. get price failed ==================== "); 70 | } 71 | } 72 | } 73 | ``` 74 | 至此,pallet中的功能基本就实现好了。然后我们只需要将该pallet加入到runtime中就可以进行测试了(此处不累述,不会的小伙伴可以看看之前的文章)。 75 | 76 | # 2 测试 77 | 编译: 78 | ``` 79 | cargo build 80 | ``` 81 | 然后运行: 82 | ``` 83 | ./target/debug/node-template --dev 84 | ``` 85 | 86 | 可以看到终端中会打印获取到的btc价格的日志: 87 | 88 | ![价格](assets/发送http.JPG) 89 | 90 | # 3 完整源码地址 91 | 92 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-http 93 | -------------------------------------------------------------------------------- /8.13使用offchain_index.md: -------------------------------------------------------------------------------- 1 | # 使用offchain_index 2 | 3 | 前面我们已经介绍了在ocw中使用链下存储,它的所有的使用都是在链下进行的(准确的说是在ocw中进行的)。今天我们来介绍offchain_index。和在ocw中使用链下存储不同,offchain_index提供的是在链上操作链下存储的方式。 4 | 5 | # 1 pallet实现 6 | 本示例的具体功能就是提供一个调度函数,在该调度函数中通过offchain_index设置链下存储,然后我们在ocw中将该链下存储的值打印出来。整体代码如下: 7 | ``` 8 | #![cfg_attr(not(feature = "std"), no_std)] 9 | 10 | pub use pallet::*; 11 | #[frame_support::pallet] 12 | pub mod pallet { 13 | use frame_support::pallet_prelude::*; 14 | use frame_system::pallet_prelude::*; 15 | use scale_info::prelude::vec::Vec; 16 | 17 | use sp_runtime::offchain::storage::StorageValueRef; 18 | 19 | #[pallet::pallet] 20 | #[pallet::generate_store(pub(super) trait Store)] 21 | pub struct Pallet(_); 22 | 23 | #[pallet::config] 24 | pub trait Config: frame_system::Config {} 25 | 26 | #[pallet::hooks] 27 | impl Hooks> for Pallet { 28 | fn offchain_worker(_block_number: T::BlockNumber) { 29 | if let Ok(some_number) = Self::get_local_storage() { 30 | log::info!(target:"offchain-index-demo", "1. Offchain-index, some_number ======================== {:?}", some_number); 31 | } else { 32 | log::info!(target:"offchain-index-demo", "2. Offchain-index, no number in storage ==================== "); 33 | } 34 | } 35 | } 36 | 37 | #[pallet::call] 38 | impl Pallet { 39 | #[pallet::weight(0)] 40 | pub fn set_local_storage( 41 | origin: OriginFor, 42 | some_number: u32, 43 | ) -> DispatchResultWithPostInfo { 44 | ensure_signed(origin)?; 45 | 46 | Self::set_local_storage_with_offchain_index(some_number); 47 | Ok(().into()) 48 | } 49 | } 50 | 51 | impl Pallet { 52 | fn derived_key() -> Vec { 53 | b"offchain-index-demo::value".encode() 54 | } 55 | 56 | // 重点1:看这里,这里是通过offchain_index将值设置到链下存储,请注意key的值 57 | fn set_local_storage_with_offchain_index(some_number: u32) { 58 | let key = Self::derived_key(); 59 | sp_io::offchain_index::set(&key, some_number.encode().as_slice()); 60 | log::info!(target:"offchain-index-demo", "set some_number ======================== {:?}", some_number); 61 | } 62 | 63 | // 重点2:看这里,这里是从对应的链下存储读取,请注意key的值 64 | fn get_local_storage() -> Result { 65 | let key = Self::derived_key(); 66 | let some_number_storage = StorageValueRef::persistent(&key); 67 | 68 | if let Ok(Some(number)) = some_number_storage.get::() { 69 | Ok(number) 70 | } else { 71 | Err("No number in storage.") 72 | } 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | # 2 测试 79 | ## 2.1 编译&运行 80 | 81 | 将pallet的代码实现好后,然后在runtime中加入该pallet(由于这部分我们之前讲过很多次,所以此处就不累述了),接下来就是编译测试了。 82 | 83 | * 编译 84 | ``` 85 | cargo build 86 | ``` 87 | * 运行 88 | 89 | **此处运行要特别注意,需要加上--enable-offchain-indexing=true, 否则offchain-index功能不会打开** 90 | ``` 91 | ./target/debug/node-template --dev --enable-offchain-indexing=true 92 | ``` 93 | 94 | ## 2.2 测试结果 95 | 96 | 我们将节点启动后,打开polkadot-js-app,然后按下图操作: 97 | ![图1](assets/offchain_index1.JPG) 98 | 99 | 然后我们再可以看到打印日志的对比情况: 100 | ![图2](assets/offchain_index2.JPG) 101 | 102 | 我们可以清楚的看到,在我们调用通过调度函数设置之前,ocw中的打印是storage中没有值,而我们设置后,ocw中的打印就会把我们设置的值打印和出来。 103 | 104 | # 3 完整源码地址 105 | 106 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/offchain-index 107 | 108 | 109 | -------------------------------------------------------------------------------- /8.10在ocw中提交未签名交易.md: -------------------------------------------------------------------------------- 1 | # 在ocw中提交未签名交易 2 | 3 | 这节我们继续学习offchain worker的使用,我们将在offchain worker中提交未签名交易。 4 | 5 | # 1 在pallet中添加ocw 6 | 7 | 要在pallet中使用ocw提交未签名交易,我们需要修改几个地方: 8 | 9 | ## 1.1 修改Config配置 10 | 11 | ``` 12 | #[pallet::config] 13 | pub trait Config: frame_system::Config + SendTransactionTypes> { 14 | ... 15 | } 16 | ``` 17 | 在Config需要继承trait SendTransactionTypes>才能在ocw提交未签名交易。 18 | 19 | ## 1.2 实现具体的未签名调度函数 20 | 具体代码如下: 21 | ``` 22 | #[pallet::weight(0)] 23 | pub fn submit_something_unsigned( 24 | origin: OriginFor, 25 | number: u64, 26 | ) -> DispatchResultWithPostInfo { 27 | ensure_none(origin)?; 28 | 29 | let mut cnt: u64 = 0; 30 | if number > 0 { 31 | cnt = number; 32 | } 33 | 34 | log::info!(target:"ocw", "unsigned +++++++++++++++++++ offchain_worker set storage: {:?}, cnt: {:?}", number, cnt); 35 | SomeInfo::::insert(&number, cnt); 36 | 37 | Self::deposit_event(Event::UnsignedPutSetSomeInfo(number, cnt)); 38 | 39 | Ok(().into()) 40 | } 41 | ``` 42 | 43 | ## 1.3 在ocw中调用未签名交易函数 44 | 具体代码如下: 45 | ``` 46 | #[pallet::hooks] 47 | impl Hooks> for Pallet { 48 | fn offchain_worker(block_number: T::BlockNumber) { 49 | let number: u64 = block_number.try_into().unwrap_or(0); 50 | //下面为具体的调用未签名交易的方式 51 | let call = Call::submit_something_unsigned { number }; 52 | if let Err(e) = 53 | SubmitTransaction::>::submit_unsigned_transaction(call.into()) 54 | .map_err(|_| >::OffchainUnsignedTxError) 55 | { 56 | log::error!(target:"ocw", "offchain_worker submit unsigned tx error: {:?}", e); 57 | } else { 58 | log::info!(target:"ocw", "offchain_worker submit unsigned tx success"); 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | 65 | ## 1.4 实现未签名交易验证的trait 66 | 代码如下: 67 | ``` 68 | #[pallet::validate_unsigned] 69 | impl ValidateUnsigned for Pallet { 70 | type Call = Call; 71 | 72 | fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { 73 | //Call冒号后面就是具体的提交未签名交易的函数, 74 | //需要对此交易进行验证 75 | if let Call::submit_something_unsigned { number: _ } = call { 76 | ValidTransaction::with_tag_prefix("OcwUnsigtx") 77 | .priority(TransactionPriority::max_value()) 78 | .longevity(5) 79 | .propagate(false) 80 | .build() 81 | } else { 82 | InvalidTransaction::Call.into() 83 | } 84 | } 85 | } 86 | ``` 87 | 具体的ValidTransaction使用可以参考文档https://paritytech.github.io/substrate/master/sp_runtime/transaction_validity/struct.ValidTransaction.html 88 | 89 | ## 1.5 总结 90 | 当我们要在ocw中提交未签名交易时,上面的1.1、1.3、1.4都是差不多的写法,1.2为具体的未签名交易函数,根据自己的业务修改就好。当然1.4中的ValidTransaction根据自己的情况进行修改。 91 | 92 | 93 | # 2 在runtime中添加相关代码 94 | 接下来就是在runtime中添加代码,本节需要添加的代码比较简单,如下: 95 | ``` 96 | impl pallet_ocw_unsigtx::Config for Runtime { 97 | type Event = Event; 98 | } 99 | 100 | construct_runtime!( 101 | pub enum Runtime where 102 | Block = Block, 103 | NodeBlock = opaque::Block, 104 | UncheckedExtrinsic = UncheckedExtrinsic 105 | { 106 | System: frame_system, 107 | ... 108 | OcwUnSigtx: pallet_ocw_unsigtx, 109 | } 110 | ``` 111 | 112 | # 3 完整代码地址 113 | 114 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/ocw-unsigtx/src 115 | -------------------------------------------------------------------------------- /8.2Error类型的使用.md: -------------------------------------------------------------------------------- 1 | # Error类型的使用 2 | 3 | 在前面的讲解写简单的pallet中,我们提到了写pallet的模板,在模板中我们分了大概7个部分。不过这其实只是写pallet的常见的部分,除了这7个部分,实际上还有Error类型我们没有说明。在runtime代码执行时,代码必须是“非抛出的”,或者说不应该panic,应该是优雅的处理错误,所以在写pallet代码时,允许我们自定义错误类型,当错误发生时,可以返回我们定义的错误类型。这里的Error类型是指运行时在执行调度函数(也就是交易函数)时返回的错误。因为在调度函数执行时,返回的结果为DispatchResult类型,当执行结果错误时,返回DispatchError。 4 | 5 | # 1 错误类型的定义 6 | 和之前我们讲过的Event一样,Error是一个枚举类型,其定义方式如下: 7 | ``` 8 | #[pallet::error] 9 | pub enum Error { 10 | //错误类型1 11 | InvalidParameter, 12 | //错误类型2 13 | OutOfSpace, 14 | ... 15 | //错误类型3 16 | InvalidFee, 17 | } 18 | ``` 19 | 可以看到,定义Error类型时,上面是```#[pallet::error]```宏,然后就是Error枚举类型。 20 | 21 | # 2 在函数中返回错误 22 | 在函数中返回错误的情况,通常如下: 23 | ``` 24 | pub fn xx_function(origin: OriginFor, ...) 25 | -> DispatchResultWithPostInfo { 26 | ... 27 | if 返回错误条件成立 { 28 | return Error::::错误类型 29 | } 30 | ... 31 | Ok(().into()) 32 | } 33 | 34 | ``` 35 | 上面是伪代码,具体的我们可以看下面的完整示例。 36 | 37 | # 3 简单示例 38 | 接下来我们创建一个use-erros的pallet,在其中演示使用Errors类型。为了方便期间,我们直接拷贝之前实现过的use-storage目录,然后将目录名字修改为use-errors,将Cargo.toml中的包名修改为pallet-use-errors。 39 | 40 | 然后我们修改substrate-node-template/pallets/use-errors/src/lib.rs文件,首先是添加Error类型的定义: 41 | ``` 42 | #[pallet::error] 43 | pub enum Error { 44 | // Class 只允许设置一次 45 | SetClassDuplicate, 46 | // 相同学号的只允许设置一次名字 47 | SetStudentsInfoDuplicate, 48 | // 相同床位只允许设置一次 49 | SetDormInfoDuplicate, 50 | } 51 | ``` 52 | 接下来是修改调度函数set_class_info如下: 53 | ``` 54 | #[pallet::weight(0)] 55 | pub fn set_class_info(origin: OriginFor, class: u32) 56 | -> DispatchResultWithPostInfo { 57 | ensure_root(origin)?; 58 | //添加下面的三行,使用Error类型 59 | if Class::::exists() { 60 | return Err(Error::::SetClassDuplicate.into()) 61 | } 62 | 63 | Class::::put(class); 64 | ... 65 | } 66 | ``` 67 | 修改调度函数set_student_info如下: 68 | ``` 69 | #[pallet::weight(0)] 70 | pub fn set_student_info( 71 | origin: OriginFor, 72 | student_number: u32, 73 | student_name: u128, 74 | ) -> DispatchResultWithPostInfo { 75 | ensure_signed(origin)?; 76 | //添加如下三行,使用Error类型 77 | if StudentsInfo::::contains_key(student_number) { 78 | return Err(Error::::SetStudentsInfoDuplicate.into()) 79 | } 80 | ... 81 | } 82 | 83 | ``` 84 | 修改调度函数set_dorm_info如下: 85 | ``` 86 | #[pallet::weight(0)] 87 | pub fn set_dorm_info( 88 | origin: OriginFor, 89 | dorm_number: u32, 90 | bed_number: u32, 91 | student_number: u32, 92 | ) -> DispatchResultWithPostInfo { 93 | ensure_signed(origin)?; 94 | //添加如下三行,使用Error类型 95 | if DormInfo::::contains_key(dorm_number, bed_number) { 96 | return Err(Error::::SetDormInfoDuplicate.into()) 97 | } 98 | ... 99 | } 100 | 101 | ``` 102 | 我们分别在我们之前的函数中使用了Error类型,当满足错误判断条件时,会返回对应的error 103 | 104 | 当pallet写好后,我们需要继续修改substrate-node-template/runtime/Cargo.toml和substrate-node-template/runtime/lib.rs,将我们的use-errors pallet添加到runtime中,因为这个步骤我们在前面simple-pallet和use-storage pallet中演示了几遍,所以此处不重复了。不会的小伙伴可以翻前面的示例,另外本节完整的代码地址也会在末尾给出。 105 | 106 | 修改好代码后,可以执行如下代码编译: 107 | ``` 108 | cargo build 109 | ``` 110 | 111 | 然后启动节点: 112 | ``` 113 | ./target/debug/node-template --dev 114 | ``` 115 | 116 | # 4 测试 117 | 接下来我们使用polkadot-js-app来测试,在浏览器输入https://polkadot.js.org/apps/, 然后选择链接localnode,调用对应的函数。 118 | 此处我们只测试set_class_info函数,set_student_info和set_dorm_info小伙伴们可以自行测试。set_class_info的测试步骤如下: 119 | * 我们调用set_class_info,设置class为1,此时应该设置成功,通过chainstate查看,可以看到class为1 120 | * 再次调用set_class_info,设置class为2,会发现调用报错,浏览器右上角返回错误为```SetClassDuplicate``` 121 | 122 | ![第一次设置](./assets/第一次设置.PNG) 123 | ![第二次设置](./assets/第二次设置错误.PNG) 124 | 125 | 此处可以把右上角的黄色的错误点开,可以看到错误是```SetClassDuplicate```。 126 | 127 | ![查看](./assets/查询class.PNG) 128 | 129 | 130 | # 5 参考文档 131 | 132 | https://docs.substrate.io/v3/runtime/events-and-errors/ 133 | 134 | # 6 完整代码 135 | 136 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/use-errors/src/lib.rs 137 | -------------------------------------------------------------------------------- /8.8调试.md: -------------------------------------------------------------------------------- 1 | # 开发pallet时的调试 2 | 3 | 今天我们来讲讲在开发pallet的时候如何调试。在pallet开发时主要有以下几种调试方式: 4 | * logging uilities; 5 | * printable trait; 6 | * print函数; 7 | * if_std. 8 | 9 | 下面我们来一一演示。 10 | 11 | # 1 准备 12 | 首先我们进入到substrate-node-template/pallets目录下,拷贝template然后命名为Debug,然后修改包名为pallet-debug。然后修改runtime中的内容将pallet-debug加载到runtime中。对于这几步不会的可以看我们前面的讲解,基本上我们每新加一个pallet都会使用这步。 13 | 14 | 接下来就是在pallet-debug中进行修改。 15 | 16 | # 2 使用logging uilities 17 | 这种方式就是使用log包进行打印,需要在pallet-debug的Cargo.toml中添加依赖如下: 18 | ``` 19 | [dependencies] 20 | ... 21 | log = { version = "0.4.14", default-features = false } 22 | ... 23 | 24 | [features] 25 | default = ["std"] 26 | std = [ 27 | ... 28 | "log/std", 29 | "sp-runtime/std", 30 | "sp-std/std", 31 | ] 32 | ``` 33 | 然后我们可以在lib.rs的代码中使用log进行打印,如下: 34 | ``` 35 | pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { 36 | let who = ensure_signed(origin)?; 37 | >::put(something); 38 | log::info!("|||||||||||||||||||||| called by {:?}", who); 39 | 40 | Self::deposit_event(Event::SomethingStored(something, who)); 41 | Ok(()) 42 | } 43 | ``` 44 | 45 | # 3 使用printable trait 46 | 47 | 此种方式我们需要为需要打印的类型实现printable trait,在我们的示例中我们主要为Error类型实现对应的trait,然后再进行打印,需要修改代码如下: 48 | ``` 49 | use sp_runtime::traits::Printable; 50 | use sp_runtime::print; 51 | ... 52 | 53 | #[pallet::error] 54 | pub enum Error { 55 | NoneValue, 56 | StorageOverflow, 57 | } 58 | 59 | impl Printable for Error { 60 | fn print(&self) { 61 | match self { 62 | Error::NoneValue => "Invalid Value".print(), 63 | Error::StorageOverflow => "++++++++++++++++++++++++++ Value Exceeded and Overflowed".print(), 64 | _ => "Invalid Error Case".print(), 65 | } 66 | } 67 | } 68 | 69 | ... 70 | 71 | #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] 72 | pub fn cause_error(origin: OriginFor) -> DispatchResult { 73 | log::info!("|||||||||||||||||||||| cause error"); 74 | let _who = ensure_signed(origin)?; 75 | match >::get() { 76 | None => { 77 | //下面一行打印对应的错误 78 | print(Error::::NoneValue); 79 | Err(Error::::NoneValue)? 80 | }, 81 | Some(old) => { 82 | log::info!("|||||||||||||||||||||| 2 error"); 83 | let new = old.checked_add(1).ok_or({ 84 | //下面一行打印对应的错误 85 | print(Error::::StorageOverflow); 86 | Error::::StorageOverflow 87 | })?; 88 | >::put(new); 89 | Ok(()) 90 | }, 91 | } 92 | } 93 | ``` 94 | 95 | # 4 使用print函数 96 | 此处直接使用print进行打印,不过使用前也需要引入```use sp_runtime::print;``` 97 | 98 | 打印的示例代码如下: 99 | ``` 100 | #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] 101 | pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { 102 | let who = ensure_signed(origin)?; 103 | >::put(something); 104 | //示例代码 105 | print("After storing my_val"); 106 | Self::deposit_event(Event::SomethingStored(something, who)); 107 | Ok(()) 108 | } 109 | ``` 110 | 111 | # 5 使用 if_std 112 | 此种方式我本地没有实验成功,有兴趣的小伙伴可以研究研究。示例代码如下: 113 | ``` 114 | use sp_std::if_std; 115 | ... 116 | 117 | #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] 118 | pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { 119 | let who = ensure_signed(origin)?; 120 | >::put(something); 121 | 122 | if_std! { 123 | println!("Hello native world!"); 124 | println!("My value is: {:#?}", something); 125 | println!("The caller account is: {:#?}", who); 126 | } 127 | 128 | Self::deposit_event(Event::SomethingStored(something, who)); 129 | Ok(()) 130 | } 131 | ``` 132 | 133 | # 6 参考文档 134 | 135 | https://docs.substrate.io/v3/runtime/debugging/ 136 | 137 | # 7 完整源码地址 138 | 139 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/debug/src/lib.rs 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /8.4Hooks函数使用.md: -------------------------------------------------------------------------------- 1 | # 钩子函数的使用 2 | 3 | # 1 背景知识 4 | ## 1.1 交易到打包的过程 5 | 在这个教程中,我其实一直回避原理性的知识,因为感觉讲太多原理性的东西会让入门变得更难。但是当准备讲hooks的时候,发现必须得讲讲区块链打包记账的过程,否则就不好理解。所以这里画了一张区块链运行的示意图,如下: 6 | 7 | ![发起交易到打包的过程](./assets/运行图.PNG) 8 | 9 | 10 | 图中描述了交易产生到打包成到区块中,最后每个节点接受区块存储到本地的全过程,这里我们简单描述下过程: 11 | ``` 12 | 1、用户通过钱包发起交易; 13 | 2、和钱包相连的全节点收到交易后会把交易广播到网络中; 14 | 3、然后根据共识算法打包区块,某个全节点获得了打包权(图中画的是节点4),然后将交易打包到区块中; 15 | 4、打包好区块后,将区块广播到网络中; 16 | 5、其它每个节点收到区块后验证,然后执行区块里面的交易,更新自己本地的账本。 17 | ``` 18 | 19 | 20 | ## 1.2 substrate中的执行过程 21 | 上面我们讲了区块链系统的打包的过程,下面我们再讲讲substrate中具体的区块执行的过程(上面的第5步)。在substrate中区块执行主要分为三步,分别是: 22 | * 初始化区块(Initializes the block); 23 | * 执行区块(Executes extrinsics); 24 | * 确认区块(Finalizes the block). 25 | 26 | ### 1.2.1 初始化区块 27 | 初始化区块时就会执行所有pallet(就是construct_runtime!中包含的pallet,并且也是按照construct_runtime!中定义的顺序)的on_initialize函数,不过会最先执行System模块的(frame-system). 28 | 29 | ### 1.2.2 执行交易 30 | 31 | 区块初始化后,就会根据交易(extrinsics)列表的顺序执行。 32 | 33 | ### 1.2.3 确认区块 34 | 区块中的交易执行完后,确认区块。确认区块时会调用所有pallet(就是construct_runtime!中包含的pallet,并且也是按照construct_runtime!中定义的顺序)的on_idle和on_finalize函数,不过这次最后执行System模块(frame-system)的hooks函数. 35 | 36 | # 2 hooks介绍 37 | ``` 38 | pub trait Hooks { 39 | fn on_finalize(_n: BlockNumber) { ... } 40 | fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { ... } 41 | fn on_initialize(_n: BlockNumber) -> Weight { ... } 42 | fn on_runtime_upgrade() -> Weight { ... } 43 | fn pre_upgrade() -> Result<(), &'static str> { ... } 44 | fn post_upgrade() -> Result<(), &'static str> { ... } 45 | fn offchain_worker(_n: BlockNumber) { ... } 46 | fn integrity_test() { ... } 47 | } 48 | ``` 49 | 每个钩子函数在对应的时间自动调用执行,开发者可以根据需要在这些钩子函数中添加业务逻辑。 50 | 51 | * on_finalize: 在区块finalize的时候调用。 52 | * on_idle:区块finalize的时候调用,不过比on_finalize先调用。 53 | * on_initialize:区块初始化的时候调用。 54 | * on_runtime_upgrade:执行模块升级的时候调用。 55 | * pre_upgrade:升级之前的检查。 56 | * post_upgrade:升级之后的处理。 57 | * offchain_worker:在一个pallet上实现此函数后可以在此函数中长时间的执行需要链下执行的功能。该函数会在每次区块导入的时候调用。后续我们将ocw使用的时候就需要和这个函数打交道。 58 | * integrity_test:运行集成测试。 59 | 60 | # 3 示例 61 | 下面我们就写一个使用```on_initialize```和```on_finalize```的例子。同样的,我们之前的例子ext-example上进行修改。 62 | ``` 63 | cd substrate-node-template/pallets 64 | cp ext-example use-hooks -rf 65 | ``` 66 | 然后进入use-hooks目录,修改Cargo.toml中的包名为pallet-use-hooks,同时添加log的依赖,如下: 67 | ``` 68 | [dependencies] 69 | ... 70 | log = { version = "0.4.14", default-features = false } #添加这个依赖 71 | ``` 72 | 73 | 接下来在use-hooks/src/lib.rs中添加如下代码: 74 | ``` 75 | // 6. Hooks 76 | // 添加hooks函数 77 | #[pallet::hooks] 78 | impl Hooks> for Pallet { 79 | fn on_initialize(n: BlockNumberFor) -> Weight { 80 | log::info!(target: "use-hooks", 81 | "++++++++++++ on_initialize, block number is {:?}", n); 82 | 0 83 | } 84 | 85 | fn on_finalize(n: BlockNumberFor) { 86 | log::info!(target: "use-hooks", 87 | "------------ on_finalize, block number is {:?}", n); 88 | } 89 | } 90 | ``` 91 | 然后在我们修改函数set_param_bigger_than_100,在其中添加一行log,如下: 92 | ``` 93 | #[transactional] 94 | #[pallet::weight(0)] 95 | pub fn set_param_bigger_than_100(origin: OriginFor, param: u32) 96 | -> DispatchResult { 97 | ... 98 | 99 | //3、发出事件 100 | Self::deposit_event(Event::SetParam(param)); 101 | //添加log 102 | log::info!(target: "use-hooks", "set param bigger then 100"); 103 | 104 | Ok(().into()) 105 | } 106 | ``` 107 | 108 | 接下来就可以将use-hooks pallet添加到runtime中,因为这个步骤我们在前面simple-pallet和use-storage pallet中演示了几遍,所以此处不重复了。不会的小伙伴可以翻前面的示例,另外本节完整的代码地址也会在末尾给出。 109 | 110 | # 4 交互 111 | 编译: 112 | ``` 113 | cargo build 114 | ``` 115 | 执行: 116 | ``` 117 | ./target/debug/node-template --dev 118 | ``` 119 | 120 | 然后此时,我们可以看下打印的日志,可以清楚的看到我们在on_initialize和on_finalize中的打印,如下图: 121 | ![hook1](./assets/hooks1.PNG) 122 | 123 | 接下来我们可以在polkadot-js-app中调用交易,如下: 124 | ![hook](./assets/hooks.PNG) 125 | 126 | 会看到打印的日志如下: 127 | ![hook2](./assets/hook2.PNG) 128 | 129 | 从这张图中,我们就可以看到,执行区块时,是先执行on_initialize,然后执行交易,最后执行on_finalize. 130 | 131 | 132 | # 5 参考文档 133 | https://paritytech.github.io/substrate/master/frame_support/traits/trait.Hooks.html 134 | 135 | https://docs.substrate.io/v3/concepts/execution/ 136 | 137 | # 6 完整源码 138 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/use-hooks 139 | -------------------------------------------------------------------------------- /5编写pallet的Rust前置知识.md: -------------------------------------------------------------------------------- 1 | # 编写pallet的Rust前置知识 2 | 3 | ## 1 rust中的trait学习 4 | 在substrate的开发中,或者说pallet的开发中,trait的使用是非常常见的,所以理解Rust中的trait非常重要。本节不会从头介绍trait的各种知识,如果你对Rust中的trait还不太了解,建议先学习[trait基础知识](https://course.rs/basic/trait/trait.html)后,再来学习本教程接下来的内容。接下来的内容都是假定你已经对trait有了基本的了解。 5 | 6 | ### 1.1 trait的孤儿规则 7 | Rust中的trait在使用上和其它编程语言中的接口类似,为一个类型实现某个trait就类似于在其它编程语言中为某个类型实现对应的接口。但是在使用trait的时候,有一条 **非常重要的原则**(为什么重要?因为你如果不知道话,那么在某些时候会发现哪都应该ok,但是就是编译不过),那就是: 8 | **如果你想要为类型A实现trait T,那么A或者T至少有一个是在当前作用域中定义的** 9 | 10 | 举两个例子。 11 | * 正确的例子: 12 | ``` 13 | //例子1 14 | pub trait MyTrait { 15 | fn print(); 16 | } 17 | 18 | pub struct MyType; 19 | 20 | impl MyTrait for MyType { 21 | fn print() { 22 | println!("This is ok."); 23 | } 24 | } 25 | ``` 26 | 上面的例子1能够正确的编译,因为它遵循孤儿规则。 27 | 28 | * 错误的例子: 29 | ``` 30 | //例子2 31 | use std::fmt::{Error, Formatter}; 32 | impl std::fmt::Debug for () { 33 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 34 | Ok(()) 35 | } 36 | } 37 | ``` 38 | 例子2无法编译通过,因为不管是trait Debug的定义还是类型()的定义都是外部的,所以无法在我们的代码中为类型()实现trait Debug。 39 | 40 | ### 1.2 trait对象 41 | Rust中不直接将trait当作数据类型使用,但是可以将实现了某个trait的具体的类型当作trait对象使用。看以下例子: 42 | ``` 43 | trait Drive{ 44 | fn drive(&self); 45 | } 46 | 47 | struct Truck; 48 | 49 | impl Drive for Truck { 50 | fn drive(&self) { 51 | println!("Truck run!"); 52 | } 53 | } 54 | 55 | 56 | struct MotorCycle; 57 | 58 | impl Drive for MotorCycle { 59 | fn drive(&self) { 60 | println!("MotorCycle run!"); 61 | } 62 | } 63 | 64 | fn use_transportation(t: Box) { 65 | t.drive(); 66 | } 67 | 68 | fn main() { 69 | let truck = Truck; 70 | use_transportation(Box::new(truck)); 71 | 72 | let moto = MotorCycle; 73 | use_transportation(Box::new(moto)); 74 | } 75 | ``` 76 | 在上面的例子中,```use_transportation```的参数就是一个trait对象,不关注具体的类型,只需要它具备drive能力即可。 77 | 78 | ### 1.3 trait的继承 79 | Rust只支持Trait之间的继承,比如Trait A继承Trait B,语法为: 80 | ``` 81 | trait B{} 82 | trait A: B{} 83 | ``` 84 | Trait A继承Trait B后,当某个类型C想要实现Trait A时,还必须要同时也去实现trait B。 85 | 86 | ### 1.4 关联类型 87 | 关联类型是在trait定义的语句块中,申明一个自定义类型,这样就可以在trait的方法签名中使用该类型。如下: 88 | ``` 89 | pub trait Iterator { 90 | type Item; 91 | fn next(&mut self) -> Option; 92 | } 93 | ``` 94 | 当为某个类型实现具有关联类型的trait时,需要指定关联类型为具体的类型,就像下面这样: 95 | ``` 96 | struct Counter(u32); 97 | impl Iterator for Counter { 98 | type Item = u32; 99 | 100 | fn next(&mut self) -> Option { 101 | // --snip-- 102 | } 103 | } 104 | 105 | fn main() { 106 | let c = Counter(1); 107 | c.next(); 108 | } 109 | ``` 110 | 111 | ## 2 一个例子 112 | 113 | 下面我们来看一个Rust的例子: 114 | 115 | ``` 116 | trait SystemConfig { 117 | fn system_configure(&self) { 118 | println!("system configure."); 119 | } 120 | } 121 | 122 | trait Config: SystemConfig { 123 | type Event: ToString; 124 | type Balance: ToString; 125 | type Currency: ToString; 126 | 127 | fn configure_event(&self, event: Self::Event); 128 | fn configure_balance(&self, balance: Self::Balance); 129 | fn configure_currency(&self, currency: Self::Currency); 130 | } 131 | 132 | struct Pallet { 133 | event: u64, 134 | balance: String, 135 | currency: String, 136 | } 137 | 138 | impl SystemConfig for Pallet {} 139 | 140 | impl Config for Pallet { 141 | type Event = u64; 142 | type Balance = String; 143 | type Currency = String; 144 | fn configure_event(&self, event: Self::Event) { 145 | println!("configure, event is: {:?}", event); 146 | } 147 | fn configure_balance(&self, balance: Self::Balance) { 148 | println!("configure, balance is: {:?}", balance); 149 | } 150 | fn configure_currency(&self, currency: Self::Currency) { 151 | println!("configure, currency is: {:?}", currency); 152 | } 153 | } 154 | 155 | impl Pallet { 156 | fn new(event: u64, balance: String, currency: String) -> Self { 157 | Pallet {event, balance, currency} 158 | } 159 | 160 | fn init(&self) { 161 | self.configure_event(self.event); 162 | self.configure_balance(self.balance.clone()); 163 | self.configure_currency(self.currency.clone()); 164 | } 165 | } 166 | 167 | fn main() { 168 | let my_pallet = Pallet::new(1, "my balance".to_string(), "my currency".to_string()); 169 | my_pallet.init(); 170 | } 171 | ``` 172 | 上述代码中,我们定义了Config trait,然后为Pallet实现了相应的trait,最后在main函数中使用了它。 173 | 174 | 为什么要写这个例子?因为我觉得这个例子对后续我们写pallet时,涉及到的一些类型能有很好的理解。 175 | 176 | 另外为了更好的理解后续的课程,可以读一读洋芋写的文章[深入理解substrate runtime](https://zhuanlan.zhihu.com/p/79539782) 177 | 178 | ## 3 参考文档 179 | 180 | https://course.rs/basic/trait/trait.html 181 | 182 | https://rust-book.junmajinlong.com/ch11/03_trait_inherite.html 183 | 184 | https://zhuanlan.zhihu.com/p/79539782 185 | -------------------------------------------------------------------------------- /8.12在ocw中使用链下存储.md: -------------------------------------------------------------------------------- 1 | # 在pallet中使用链下存储 2 | 3 | 前面我们已经讲解了在offchain worker中提交交易到链上的例子,包括签名交易、未签名交易、具有签名payload的未签名交易三种情况。实际上,在ocw(offchain worker)中除了可以提交交易以外,我们还可以使用链下存储。顾名思义,链下存储不保存在链上,而是保存在节点本地的。 4 | 5 | 本节,我们就在pallet中使用链下存储。 6 | 7 | # 1 在ocw中使用StorageValueRef 8 | ## 1.1 示例实现 9 | 在ocw中可以使用具体StorageValueRef,其详细的文档在[这里](https://paritytech.github.io/substrate/master/sp_runtime/offchain/storage/struct.StorageValueRef.html),下面我们就实现一个在ocw中使用StorageValueRef的例子,如下: 10 | ``` 11 | #![cfg_attr(not(feature = "std"), no_std)] 12 | 13 | pub use pallet::*; 14 | #[frame_support::pallet] 15 | pub mod pallet { 16 | use frame_support::pallet_prelude::*; 17 | use frame_system::pallet_prelude::*; 18 | 19 | use sp_runtime::offchain::storage::{ 20 | MutateStorageError, StorageRetrievalError, StorageValueRef, 21 | }; 22 | 23 | #[pallet::pallet] 24 | #[pallet::generate_store(pub(super) trait Store)] 25 | pub struct Pallet(_); 26 | 27 | #[pallet::config] 28 | pub trait Config: frame_system::Config {} 29 | 30 | #[pallet::hooks] 31 | impl Hooks> for Pallet { 32 | fn offchain_worker(block_number: T::BlockNumber) { 33 | if Self::should_print() { 34 | log::info!(target:"offchain-storage", "1. Block number: {:?}, should print +++++++++++++++++++++++++++ ", block_number); 35 | } else { 36 | log::info!(target:"offchain-storage", "2. Block number: {:?}, Should not print +++++++++++++++++++++++++++ ", block_number); 37 | } 38 | } 39 | } 40 | 41 | impl Pallet { 42 | fn should_print() -> bool { 43 | const LAST_VALUE: () = (); 44 | 45 | //重点看这里 46 | let val = StorageValueRef::persistent(b"ocw-demo::last_value"); 47 | let res = 48 | val.mutate( 49 | |last_flag: Result, StorageRetrievalError>| match last_flag { 50 | Ok(Some(flag)) => Ok(!flag), 51 | _ => Ok(true), 52 | }, 53 | ); 54 | 55 | match res { 56 | Ok(flag) => flag, 57 | Err(MutateStorageError::ValueFunctionFailed(LAST_VALUE)) => false, 58 | Err(MutateStorageError::ConcurrentModification(_)) => false, 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | 在上面的例子中,我们定义了一个叫做ocw-demo::last_value的链下storage,然后在不同的区块高度将此storage的值设置成true或者false。 65 | 66 | ## 1.2 测试 67 | 将上面的代码实现后编译测试,会发现在terminal上会交替的打印如下log: 68 | 69 | ![图片](assets/local_storage1.JPG) 70 | 71 | # 2 在ocw中使用local_storage_set/get 72 | 下面我们在看一个使用local_storage_set和local_storage_get的例子,在这个例子中,我们手动通过rpc对storage demo::flag设置为true,然后在代码中查看此存储的值,然后再将此值写到另外一个存储demo::result中,具体代码如下: 73 | ``` 74 | #![cfg_attr(not(feature = "std"), no_std)] 75 | 76 | pub use pallet::*; 77 | #[frame_support::pallet] 78 | pub mod pallet { 79 | use frame_support::pallet_prelude::*; 80 | use frame_system::pallet_prelude::*; 81 | 82 | extern crate alloc; 83 | use alloc::string::{String, ToString}; 84 | 85 | #[pallet::pallet] 86 | #[pallet::generate_store(pub(super) trait Store)] 87 | pub struct Pallet(_); 88 | 89 | #[pallet::config] 90 | pub trait Config: frame_system::Config {} 91 | 92 | #[pallet::hooks] 93 | impl Hooks> for Pallet { 94 | fn offchain_worker(block_number: T::BlockNumber) { 95 | let flag = Self::should_print(); 96 | 97 | if flag { 98 | log::info!(target:"offchain-storage2222", "1. Offchain-storage2, block number: {:?}, should print +++++++++++ ", block_number); 99 | } else { 100 | log::info!(target:"offchain-storage2222", "2. Offchain-storage2, block number: {:?}, Should not print +++++++++++ ", block_number); 101 | } 102 | 103 | Self::set_result(flag); 104 | } 105 | } 106 | 107 | impl Pallet { 108 | fn should_print() -> bool { 109 | let kind = sp_core::offchain::StorageKind::PERSISTENT; 110 | if let Some(flag) = sp_io::offchain::local_storage_get(kind, b"demo::flag") { 111 | let ret = match String::from_utf8(flag) { 112 | Ok(v) => v.eq(&"true"), 113 | Err(_) => false, 114 | }; 115 | 116 | ret 117 | } else { 118 | false 119 | } 120 | } 121 | 122 | fn set_result(flag: bool) { 123 | let kind = sp_core::offchain::StorageKind::PERSISTENT; 124 | let value = match flag { 125 | true => "true".to_string(), 126 | false => "false".to_string(), 127 | }; 128 | sp_io::offchain::local_storage_set(kind, b"demo::result", value.as_bytes()); 129 | } 130 | } 131 | } 132 | ``` 133 | ## 2.2 测试 134 | 这里我们手动设置local storage需要用polkadot-js-app,然后将对应的storage的值设置为true,如下: 135 | ![设置](assets/设置storage.JPG) 136 | 137 | 设置后运行的结果如下: 138 | ![结果](assets/设置前后.JPG) 139 | 140 | # 3 完整源码地址 141 | 142 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/offchain-storage 143 | 144 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/offchain-storage2 145 | 146 | 147 | -------------------------------------------------------------------------------- /7Pallet的组成.md: -------------------------------------------------------------------------------- 1 | # Pallet的组成 2 | 3 | 在前一节[编写简单的pallet](6编写简单的pallet.md)中,我们已经实现了一个简单的pallet,同时也讲解了写pallet的模板。基本上写pallet都是在该模板的基础上进行修改,完成特定pallet的功能。所以,要想熟练的开发pallet,我们必须得把pallet中的各个组成部分弄清楚。本节,我们就按照模板中的各个部分的顺序来讲解pallet的组成。 4 | 5 | 6 | ## 1 导出和依赖 7 | 导出和依赖的代码如下: 8 | ``` 9 | // 1. Imports and Dependencies 10 | pub use pallet::*; 11 | #[frame_support::pallet] 12 | pub mod pallet { 13 | use frame_support::pallet_prelude::*; 14 | use frame_system::pallet_prelude::*; 15 | ... 16 | } 17 | ``` 18 | 学过Rust的都知道,```Pub mod pallet{}```就是将我们的pallet暴露出来,可以让外部使用(如果这部分不能理解的,建议参考[文档](https://kaisery.github.io/trpl-zh-cn/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#%E4%BD%BF%E7%94%A8-pub-%E5%85%B3%E9%94%AE%E5%AD%97%E6%9A%B4%E9%9C%B2%E8%B7%AF%E5%BE%84)). 19 | 20 | 接下来我们说依赖,第一行```pub use pallet::*;```是可以使用pallet中的所有类型,函数,数据等。还有 ``` use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*;```这两行,引入了相关的依赖。我们在写自己的pallet时候,当pallet使用到什么依赖,可以在这里引入。 21 | 22 | 23 | ## 2 Pallet中的类型声明 24 | ``` 25 | // 2. Declaration of the Pallet type 26 | // This is a placeholder to implement traits and methods. 27 | #[pallet::pallet] 28 | #[pallet::generate_store(pub(super) trait Store)] 29 | pub struct Pallet(_); 30 | ``` 31 | 接下来是Pallet类型声明,它是一系列trait和方法的拥有者,实际的作用类似于占位符。如果对这个还不理解的话,我们可以看如下Rust程序的例子: 32 | ``` 33 | trait MyTrait { 34 | fn info(&self); 35 | } 36 | 37 | struct PlaceHolder(); //本身并没有字段 38 | 39 | impl PlaceHolder { 40 | fn method(&self) { 41 | println!("This is method."); 42 | } 43 | } 44 | 45 | impl MyTrait for PlaceHolder { 46 | fn info(&self) { 47 | println!("This is info method."); 48 | } 49 | } 50 | 51 | fn main() { 52 | let p = PlaceHolder(); 53 | p.method(); 54 | p.info(); 55 | } 56 | ``` 57 | 所以在这部分中定义的```pub struct Pallet(_)```就和上面的Rust例子中定义的```struct PlaceHolder();```作用一样,是method和info方法的主体。 58 | 59 | ## 3 Runtime配置trait 60 | 这部分是指定Runtime的配置trait,Pallet中使用的一些类型和常量在此trait中进行配置。通常的使用方式如下: 61 | ``` 62 | #[pallet::config] 63 | pub trait Config: frame_system::Config { 64 | type Id: Member 65 | + Parameter 66 | + AtLeast32BitUnsigned 67 | + Codec 68 | + Copy 69 | + Debug 70 | + Default 71 | + MaybeSerializeDeserialize; 72 | 73 | #[pallet::constant] 74 | type Limit: Get; 75 | } 76 | ``` 77 | 例如我们这里定义了一个类型Id以及一个常量Limit,定义的格式就是```type 类型名/常量名: trait约束 ```,不同的是常量名字上面会加上```#[pallet::constant]```。此处定义的类型以及常量,会在runtime中(就是代码runtime/src/lib.rs中)使用时,会指定具体的类型。 78 | 79 | 对于Config中的类型,我们可以在我们整个Pallet中使用,使用的方式就是T::类型名/常量名。例如此处定义的Id,我们使用时就是T::Id。 80 | 81 | ## 4 存储 82 | 存储(Storage)允许我们在链上存储数据,使用它存储的数据可以通过Runtime进行访问。substrate提供了四种存储方式,分别是: 83 | 84 | * Storage Value 85 | * Storage Map 86 | * Storage Double Map 87 | * Storage N Map 88 | 89 | 从字面意思,我们基本上也可以看出几种存储的区别,StorageValue是存储单个的值,StorageMap是以map的方式存储(key-value),StorageDoubleMap则是以双键的方式存储(就是两个key对应value的方式),StorageNMap则是N个key的方式。 90 | 91 | 关于存储的介绍可以参考[官方文档](https://docs.substrate.io/v3/runtime/storage/) 92 | 93 | 定义存储通常的方式如下: 94 | ``` 95 | #[pallet::storage] 96 | pub type MyStorageValue = StorageValue<_, u32, ValueQuery>; 97 | 98 | #[pallet::storage] 99 | pub type MyStorageMap = StorageMap<_, Twox64Concat, u32, u32, ValueQuery>; 100 | ``` 101 | 首先是需要添加#[pallet::storage]宏,然后使用pub type 存储名 = 某种存储类型<...>。至于尖括号里面具体填的东西,可以看Storage的Rust文档,如StorageMap就可以参考[这里](https://docs.substrate.io/rustdocs/latest/frame_support/storage/types/struct.StorageMap.html) 102 | 103 | ## 5 事件 104 | 当pallet需要把运行时上的更改或变化通知给外部主体时,就需要用到事件。事件是一个枚举类型,如下: 105 | ``` 106 | #[pallet::event] 107 | #[pallet::metadata(u32 = "Metadata")] 108 | pub enum Event { 109 | /// Set a value. 110 | ValueSet(u32, T::AccountId), 111 | } 112 | ``` 113 | 在区块链写交易函数的时候,一般分为三步,分别是判断条件、修改状态、发出事件。例如我们上一节定义了```pub enum Event {ClaimCreated(u32, u128) }```事件,那么交易函数中就可以使用```Self::deposit_event(Event::ClaimCreated(id, claim));```发出事件。 114 | 115 | ## 6 钩子函数 116 | 钩子函数,是在区块链运行过程中希望固定执行的函数,例如我们希望在每个区块构建之前、之后的时候执行某些逻辑等,就可以把这些逻辑放在钩子函数中。钩子函数一共有: 117 | ``` 118 | pub trait Hooks { 119 | fn on_finalize(_n: BlockNumber) { ... } 120 | fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { ... } 121 | fn on_initialize(_n: BlockNumber) -> Weight { ... } 122 | fn on_runtime_upgrade() -> Weight { ... } 123 | fn pre_upgrade() -> Result<(), &'static str> { ... } 124 | fn post_upgrade() -> Result<(), &'static str> { ... } 125 | fn offchain_worker(_n: BlockNumber) { ... } 126 | fn integrity_test() { ... } 127 | } 128 | ``` 129 | 从函数名字上,我们也基本上可以判断出这些钩子函数什么时候执行。on_finalize是在区块finalize的时候执行;on_idle是在on_finalize之前执行;on_initialize是在准备打包区块之前执行;on_runtime_upgrade是升级的时候执行;pre_upgrade是在升级之前执行;post_upgrade是在升级之后执行;offchain_worker在每个区块同步的时候执行。 130 | 131 | ## 7 交易 132 | 133 | Extrinsic则是可以从runtime外部可以调用的函数,也是pallet对外提供的逻辑功能。交易是放在如下部分: 134 | ``` 135 | #[pallet::call] 136 | impl Pallet { 各种交易函数 } 137 | ``` 138 | 139 | ## 8 小结 140 | 这这个章节中,我们对pallet的组成做了基本介绍,在写pallet的时候,基本上按照前面一章节中的模板,加上自己逻辑需要每个部分的修改,就可以完成自己的pallet。 141 | 142 | ## 9 参考文档 143 | https://kaisery.github.io/trpl-zh-cn/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#%E4%BD%BF%E7%94%A8-pub-%E5%85%B3%E9%94%AE%E5%AD%97%E6%9A%B4%E9%9C%B2%E8%B7%AF%E5%BE%84 144 | 145 | https://docs.substrate.io/v3/runtime/storage/ 146 | 147 | https://docs.substrate.io/v3/runtime/events-and-errors/ 148 | 149 | -------------------------------------------------------------------------------- /8.7封装和扩展现有pallet.md: -------------------------------------------------------------------------------- 1 | # 封装和扩展现有pallet 2 | 3 | 4 | 本节我们讲解在pallet中使用其它palllet的另外一种情况,即在新的pallet中封装和扩展现有的pallet。我们这里substrate提供的contracts pallet,然后对其中的功能进行封装。在我们的封装中,将contracts pallet的call函数封装成sudo_call,即需要root权限才能调用。同时,我们在runtime中加载contracts时,去掉直接调用contracts函数的方式。 5 | 6 | 整个方式我们分成两大步骤,如下: 7 | 8 | * 编写extend-pallet; 9 | * 在runtime配置extend-pallet和contracts pallet。 10 | 11 | # 1 编写extend-pallet 12 | 首先我们编写封装contracts pallet的pallet,取名叫做extend-pallet,主要代码如下: 13 | ``` 14 | #![cfg_attr(not(feature = "std"), no_std)] 15 | 16 | use codec::{Encode, HasCompact}; 17 | use frame_support::traits::Currency; 18 | use scale_info::TypeInfo; 19 | use sp_core::crypto::UncheckedFrom; 20 | use sp_runtime::traits::StaticLookup; 21 | use sp_std::{fmt::Debug, prelude::*}; 22 | 23 | type BalanceOf = <::Currency as Currency< 24 | ::AccountId, 25 | >>::Balance; 26 | 27 | pub use pallet::*; 28 | 29 | #[frame_support::pallet] 30 | pub mod pallet { 31 | use super::*; 32 | use frame_support::pallet_prelude::*; 33 | use frame_system::pallet_prelude::*; 34 | 35 | #[pallet::pallet] 36 | #[pallet::generate_store(pub(super) trait Store)] 37 | pub struct Pallet(_); 38 | 39 | // 重点关注1 40 | #[pallet::config] 41 | pub trait Config: pallet_contracts::Config + frame_system::Config {} 42 | 43 | // 重点关注2 44 | #[pallet::call] 45 | impl Pallet 46 | where 47 | T::AccountId: UncheckedFrom, 48 | T::AccountId: AsRef<[u8]>, 49 | as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, 50 | { 51 | #[pallet::weight(0)] 52 | pub fn sudo_call( 53 | origin: OriginFor, 54 | dest: ::Source, 55 | #[pallet::compact] value: BalanceOf, 56 | #[pallet::compact] gas_limit: Weight, 57 | storage_deposit_limit: Option< as codec::HasCompact>::Type>, 58 | data: Vec, 59 | ) -> DispatchResultWithPostInfo { 60 | //添加下面这行,用于判断是否是root权限 61 | ensure_root(origin.clone())?; 62 | 63 | //直接调用pallet-contracts的call函数 64 | pallet_contracts::Pallet::::call( 65 | origin, 66 | dest, 67 | value, 68 | gas_limit, 69 | storage_deposit_limit, 70 | data, 71 | ) 72 | } 73 | } 74 | } 75 | ``` 76 | 在上面的pallet中,主要有两个部分需要重点关注,一个是Config部分,封装pallet的Config需要集成被封装pallet的Config,如下: 77 | ``` 78 | // 重点关注1 79 | #[pallet::config] 80 | pub trait Config: pallet_contracts::Config + frame_system::Config {} 81 | ``` 82 | 83 | 另外一个是上面的“重点关注2”部分,对主要pallet-contracts的call函数进行封装,在这部分里面,我们添加判断root权限的语句,然后直接调用pallet-contracts的call函数。 84 | 85 | # 2 在runtime中配置pallet 86 | 87 | 将上面的封装pallet准备好了之后,我们就需要将extend-pallet和contracts pallet加载到runtime中,需要修改runtime/src/lib.rs如下: 88 | ``` 89 | // 1、配置Contracts pallet 90 | use pallet_contracts::weights::WeightInfo; 91 | const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); 92 | 93 | pub mod currency { 94 | use node_primitives::Balance; 95 | 96 | pub const MILLICENTS: Balance = 1_000_000_000; 97 | pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. 98 | pub const DOLLARS: Balance = 100 * CENTS; 99 | 100 | pub const fn deposit(items: u32, bytes: u32) -> Balance { 101 | items as Balance * 15 * CENTS + (bytes as Balance) * 6 * CENTS 102 | } 103 | } 104 | 105 | parameter_types! { 106 | pub const DepositPerItem: Balance = currency::deposit(1, 0); 107 | pub const DepositPerByte: Balance = currency::deposit(0, 1); 108 | pub const MaxValueSize: u32 = 16 * 1024; 109 | pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * 110 | BlockWeights::get().max_block; 111 | pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( 112 | ::WeightInfo::on_initialize_per_queue_item(1) - 113 | ::WeightInfo::on_initialize_per_queue_item(0) 114 | )) / 5) as u32; 115 | pub Schedule: pallet_contracts::Schedule = Default::default(); 116 | } 117 | 118 | impl pallet_contracts::Config for Runtime { 119 | type Time = Timestamp; 120 | type Randomness = RandomnessCollectiveFlip; 121 | type Currency = Balances; 122 | type Event = Event; 123 | type Call = Call; 124 | type CallFilter = Nothing; 125 | type DepositPerItem = DepositPerItem; 126 | type DepositPerByte = DepositPerByte; 127 | type CallStack = [pallet_contracts::Frame; 31]; 128 | type WeightPrice = pallet_transaction_payment::Pallet; 129 | type WeightInfo = pallet_contracts::weights::SubstrateWeight; 130 | type ChainExtension = (); 131 | type DeletionQueueDepth = DeletionQueueDepth; 132 | type DeletionWeightLimit = DeletionWeightLimit; 133 | type Schedule = Schedule; 134 | type AddressGenerator = pallet_contracts::DefaultAddressGenerator; 135 | } 136 | 137 | // 2、配置extends pallet 138 | impl pallet_extend_pallet::Config for Runtime {} 139 | 140 | // 3、在runtime中定义两个pallet 141 | construct_runtime!( 142 | pub enum Runtime where 143 | Block = Block, 144 | NodeBlock = opaque::Block, 145 | UncheckedExtrinsic = UncheckedExtrinsic 146 | { 147 | System: frame_system, 148 | ... 149 | 150 | // 注意下面两行的区别: 一定要去掉Call 151 | // Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, 152 | Contracts: pallet_contracts::{Pallet, Storage, Event}, 153 | ExtendContracts: pallet_extend_pallet, 154 | } 155 | ); 156 | 157 | ``` 158 | 对于上面代码中1和2部分,其实就是为runtime配置两个pallet。我们需要重点说明的是第3部分,定义Contracts和ExtendContracts如下: 159 | ``` 160 | Contracts: pallet_contracts::{Pallet, Storage, Event}, 161 | ExtendContracts: pallet_extend_pallet, 162 | ``` 163 | 164 | 对于Contracts,我们将它的Call部分去掉了,这也就表示我们在runtime层面没有对外暴露Contracts的调度函数接口,这样用户只能使用ExtendContracts提供的sudo_call函数,而不能使用Contracts的调度函数。 165 | 166 | # 3 测试 167 | 接下来我们编译和执行: 168 | ``` 169 | cargo build 170 | ./target/debug/node-template --dev 171 | ``` 172 | 173 | 然后我们在浏览器中输入https://polkadot.js.org/apps, 然后可以在sudo选项里面调用ExtendContracts提供的sudo_call函数(下面的第三张图)。而且我们可以看到Contracts没有对外暴露调度函数(下面第一张、第二张图) 174 | 175 | ![图1](assets/封装1.JPG) 176 | ![图2](assets/封装2.JPG) 177 | ![图3](assets/封装3.JPG) 178 | # 4 参考文档 179 | 180 | https://www.shawntabrizi.com/substrate/extending-substrate-runtime-modules/ 181 | 182 | # 5 完整源码地址 183 | 184 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/runtime/src/lib.rs 185 | 186 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/extend-pallet/src/lib.rs 187 | -------------------------------------------------------------------------------- /8.3写调度函数的套路.md: -------------------------------------------------------------------------------- 1 | # 写调度函数的套路 2 | 3 | 调度函数在substrate官方文档里面叫做Extrinsics(外部调用),详细的Extrinsics介绍可以参考[这里](https://docs.substrate.io/v3/concepts/extrinsics/).在substrate中共有三种Extrinsics,分别是Inherents、Signed transactions和Unsigned transactions。而在我们开发pallet的过程中,比较常用到的是后两种,所以我们这里也主要介绍后两种,对于Inherents有兴趣的小伙伴可以自己看官方文档研究下。 4 | 5 | # 1 Signed transactions 6 | 签名交易包含发起该交易的账户的签名,并会支付交易费用。因为签名交易的合法性可以在执行之前识别,所以它们可以在节点之间的网络上传播,属于垃圾信息的风险比较小。签名交易和比特币、以太坊中的交易的概念类似。 7 | 8 | # 2 Unsigned transactions 9 | 但是有时候我们希望使用不需要花费交易费的交易。例如一个节点给链发送在线的心跳,表示自己在线,这种情况肯定是不希望花费交易费的,此时我们就可以使用unsigned transaction。但是需要注意的是,由于交易没有签名,因此也没有人支付费用,所以缺乏防止垃圾信息的经济逻辑。所以我们在使用未签名交易时需要特别小心。 10 | 11 | # 3 通常的写法 12 | ## 3.1 调度函数的位置 13 | 所有调度函数都放置在#[pallet::call]标注的代码段内,每个调度函数上面需要标注其调用的权重,并根据需要是否添加#[transactional] 如下: 14 | ``` 15 | #[pallet::call] 16 | impl Pallet { 17 | 18 | #[pallet::weight(10_000)] //具体的函数使用对应的权重计算函数 19 | pub fn function1(origin: OriginFor, param: u32, ...) -> DispatchResult {} 20 | 21 | #[transactional] 22 | #[pallet::weight(10_000)] //具体的函数换成对应的权重计算函数 23 | pub fn function2(origin: OriginFor, param: u32, ...) -> DispatchResult {} 24 | 25 | //其它调度函数 26 | } 27 | ``` 28 | 29 | ## 3.2 函数体的写法 30 | 31 | 在pallet的开发中,不管是签名的交易还是非签名的交易,通常都遵循以下三个步骤进行,分别是: 32 | ``` 33 | 1、判断调用者是否有权限; 34 | 2、执行逻辑; 35 | 3、发出事件。 36 | ``` 37 | 示例如下: 38 | ``` 39 | #[pallet::weight(10_000)] 40 | pub fn set_number_bigger_than_100(origin: OriginFor, number: u32) -> DispatchResult { 41 | ensure_signed(origin)?; //1、判断是否是合法调用 42 | 43 | // 2、函数的具体逻辑 44 | ... 45 | 46 | //3、成功后需要发出事件 47 | Self::deposit_event(Event::xx事件); 48 | 49 | Ok(()) //固定写法 50 | } 51 | ``` 52 | 这里需要特别说明的是第一步,分三种情况: 53 | ``` 54 | 1、如果是签名调用的则判断的写法是 ensure_signed(origin)?。可以回忆回忆之前的例子(https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/use-storage/src/lib.rs#L78) 55 | 2、如果是需要root用户调用的则判断的写法是ensure_root(origin)?。可以回忆回忆之前的例子(https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/use-storage/src/lib.rs#L64) 56 | 3、如果非签名的函数调用的判断写法则是ensure_none(origin)?。这里暂时没有例子,后续我们会使用到。 57 | ``` 58 | 59 | ## 3.3 权重 60 | 从上面的示例中可以看到,每个调度函数上面都需要标注其权重,在具体开发时,我们一般是先写一个固定的权重,然后在所以功能开发完成后,再写对应的benchmarking(在本教程的最后位置会来学习)。 61 | 关于权重的详细介绍可以参考[官方文档](https://docs.substrate.io/v3/runtime/weights-and-fees/)。 62 | 63 | ## 3.4 transactional 64 | 当我们去看不管是我们上面的例子还是substrate已经实现好的pallet的调度函数,会发现有些调度函数上面会有```#[transactional]```,有些则没有。这个属性宏实际上是保证调度函数执行的一致性。更具体点说就是当在函数中遇到错误后,会回滚状态,保证错误发生之前写入的状态回滚。解释起来比较敖口,看下面的例子可以很好的理解。 65 | ``` 66 | #[transactional] 67 | #[pallet::weight(0)] 68 | pub fn set_param_bigger_than_100(origin: OriginFor, param: u32) -> DispatchResult { 69 | //1、判断调用者权限 70 | ensure_signed(origin)?; 71 | 72 | //2、开始业务逻辑 73 | //2.1、将标志位设置为true 74 | SetFlag::::put(true); 75 | 76 | //2.2、如果参数大于100,则写入到storage praram中 77 | if param <= 100u32 { 78 | return Err(Error::::ParamInvalid.into()) 79 | } 80 | Param::::put(param); 81 | 82 | //3、发出事件 83 | Self::deposit_event(Event::SetParam(param)); 84 | Ok(().into()) 85 | } 86 | ``` 87 | 上面例子中的逻辑很简单,就是设置一个大于100的参数到storage Param中,还有一个Flag标识表示是否已经设置了参数。我们很容易发现一个问题,如果我们在2.2中判断输入的参数小与等于100,则会发生错误直接返回,但是我们前面已经在2.1设置了flag了,这样逻辑上不就错误了吗?对于这个问题,我们有两种方式解决。 88 | 89 | * 第一种就是我们这里介绍的,在函数的上面加上```#[transactional]```属性宏。加上此宏后,表示该函数是原子执行的,函数中的任何位置错误返回都会导致函数中设置的所有状态回滚。像上面的例子中,加上```#[transactional]```后,如果2.2的位置错误返回,也会让2.1位置设置的值无效。 90 | 91 | * 第二种方式就是靠程序员解决,就是调整代码的顺序,遵循先判断,再修改状态的规则实现。如上面的例子可以修改为如下,就不需要加 ```#[transactional]```: 92 | 93 | ``` 94 | //该实现不需要加#[transactional] 95 | #[pallet::weight(0)] 96 | pub fn set_param_bigger_than_100(origin: OriginFor, param: u32) -> DispatchResult { 97 | //1、判断调用者权限 98 | ensure_signed(origin)?; 99 | 100 | //2、开始业务逻辑 101 | //2.2、如果参数大于100,则写入到storage praram中 102 | if param <= 100u32 { 103 | return Err(Error::::ParamInvalid.into()) 104 | } 105 | Param::::put(param); 106 | 107 | //2.1、将标志位设置为true,将此步放在判断后面,保证一定能成功 108 | SetFlag::::put(true); 109 | 110 | //3、发出事件 111 | Self::deposit_event(Event::SetParam(param)); 112 | Ok(().into()) 113 | } 114 | ``` 115 | 116 | 但是在有些复杂实现的时候,往往很难保证代码实现遵循先判断再修改的原则,此时就需要给函数加上```#[transactional]```。 117 | 118 | **不过需要注意的是,使用transactional需要通过```use frame_support::transactional;```引入**。 119 | 120 | # 4 示例 121 | 下面我们来实现一个pallet演示我们这节的内容,pallet的名字叫做ext-example,创建的过程仍然是拷贝template文件夹进行修改,我们此节聚焦于pallet中内容的修改,其它部分可以参考之前的章节(如修改包名,修改runtime中的依赖和lib.rs等)。 122 | substrate-node-template/pallets/ext-example/lib.rs如下: 123 | ``` 124 | #![cfg_attr(not(feature = "std"), no_std)] 125 | 126 | // 1. Imports and Dependencies 127 | pub use pallet::*; 128 | #[frame_support::pallet] 129 | pub mod pallet { 130 | use frame_support::pallet_prelude::*; 131 | use frame_system::pallet_prelude::*; 132 | use frame_support::transactional; 133 | 134 | // 2. Declaration of the Pallet type 135 | // This is a placeholder to implement traits and methods. 136 | #[pallet::pallet] 137 | #[pallet::generate_store(pub(super) trait Store)] 138 | pub struct Pallet(_); 139 | 140 | // 3. Runtime Configuration Trait 141 | // All types and constants go here. 142 | // Use #[pallet::constant] and #[pallet::extra_constants] 143 | // to pass in values to metadata. 144 | #[pallet::config] 145 | pub trait Config: frame_system::Config { 146 | type Event: From> + IsType<::Event>; 147 | } 148 | 149 | // 4. Runtime Storage 150 | #[pallet::storage] 151 | #[pallet::getter(fn my_param)] 152 | pub type Param = StorageValue<_, u32, ValueQuery>; 153 | 154 | #[pallet::storage] 155 | pub type SetFlag = StorageValue<_, bool, ValueQuery>; 156 | 157 | // 5. Runtime Events 158 | // Can stringify event types to metadata. 159 | #[pallet::event] 160 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 161 | pub enum Event { 162 | SetParam(u32), 163 | } 164 | 165 | // 8. Runtime Errors 166 | #[pallet::error] 167 | pub enum Error { 168 | // 参数必须大于100 169 | ParamInvalid, 170 | } 171 | 172 | // 7. Extrinsics 173 | // Functions that are callable from outside the runtime. 174 | #[pallet::call] 175 | impl Pallet { 176 | #[transactional] 177 | #[pallet::weight(0)] 178 | pub fn set_param_bigger_than_100(origin: OriginFor, param: u32) 179 | -> DispatchResult { 180 | //1、判断调用者权限 181 | ensure_signed(origin)?; 182 | 183 | //2、开始业务逻辑 184 | //2.1、将标志位设置为true 185 | SetFlag::::put(true); 186 | 187 | //2.2、如果参数大于100,则写入到storage praram中 188 | if param <= 100u32 { 189 | return Err(Error::::ParamInvalid.into()) 190 | } 191 | Param::::put(param); 192 | 193 | //3、发出事件 194 | Self::deposit_event(Event::SetParam(param)); 195 | 196 | Ok(().into()) 197 | } 198 | } 199 | } 200 | 201 | ``` 202 | # 5 交互 203 | 还是同样的使用polkadot-js-app,然后在developer->extrinsics->submits,然后选择extExample和set_param_bigger_than_100进行调用。 204 | 205 | # 6 参考文档 206 | https://docs.substrate.io/v3/runtime/weights-and-fees/ 207 | 208 | https://docs.substrate.io/v3/concepts/extrinsics/ 209 | 210 | # 7 完整源码地址 211 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/ext-example/src/lib.rs 212 | 213 | -------------------------------------------------------------------------------- /9编写tests.md: -------------------------------------------------------------------------------- 1 | # 编写tests 2 | 3 | 本节开始,我们来学习为pallet编写测试,我们在之前学过的use-errors pallet的基础上进行。首先,我们拷贝之前的use-errors pallet,然后重命名为use-test,在对应的Cargo.toml中将包名修改pallet-use-test。 4 | 5 | 通过前面的学习我们可以知道,pallet写好后需要通过runtime加载到链上(就是runtime/src/lib.rs中的construct_runtime宏包含的部分)。那么对应到我们的测试,如果对pallet进行测试,我们也需要构建一个runtime测试环境,然后在这个环境中加载pallet,对pallet进行测试。所以,编写pallet的测试就分为以下几部分: 6 | 7 | * 编写mock runtime; 8 | * 编写pallet的genesisconfig; 9 | * 编写测试。 10 | 11 | # 0 准备pallet 12 | 为了进行测试,我们先准备一个pallet,其名字叫做pallet-use-test,其src/lib.rs如下: 13 | ``` 14 | #![cfg_attr(not(feature = "std"), no_std)] 15 | 16 | 17 | // 1. Imports and Dependencies 18 | pub use pallet::*; 19 | #[frame_support::pallet] 20 | pub mod pallet { 21 | use codec::Codec; 22 | use frame_support::{ 23 | pallet_prelude::*, sp_runtime::traits::AtLeast32BitUnsigned, sp_std::fmt::Debug, 24 | }; 25 | use frame_system::pallet_prelude::*; 26 | 27 | // 2. Declaration of the Pallet type 28 | // This is a placeholder to implement traits and methods. 29 | #[pallet::pallet] 30 | #[pallet::generate_store(pub(super) trait Store)] 31 | pub struct Pallet(_); 32 | 33 | // 3. Runtime Configuration Trait 34 | // All types and constants go here. 35 | // Use #[pallet::constant] and #[pallet::extra_constants] 36 | // to pass in values to metadata. 37 | #[pallet::config] 38 | pub trait Config: frame_system::Config { 39 | type Event: From> + IsType<::Event>; 40 | type ClassType: Member 41 | + Parameter 42 | + AtLeast32BitUnsigned 43 | + Codec 44 | + Copy 45 | + Debug 46 | + Default 47 | + MaxEncodedLen 48 | + MaybeSerializeDeserialize; 49 | } 50 | 51 | // 4. Runtime Storage 52 | // use storageValue store class. 53 | #[pallet::storage] 54 | #[pallet::getter(fn my_class)] 55 | pub type Class = StorageValue<_, T::ClassType, ValueQuery>; 56 | 57 | // 5. Runtime Events 58 | // Can stringify event types to metadata. 59 | #[pallet::event] 60 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 61 | pub enum Event { 62 | SetClass(T::ClassType), 63 | } 64 | 65 | 66 | // 7. Extrinsics 67 | // Functions that are callable from outside the runtime. 68 | #[pallet::call] 69 | impl Pallet { 70 | #[pallet::weight(0)] 71 | pub fn set_class_info( 72 | origin: OriginFor, 73 | class: T::ClassType, 74 | ) -> DispatchResultWithPostInfo { 75 | ensure_root(origin)?; 76 | 77 | Class::::put(class); 78 | Self::deposit_event(Event::SetClass(class)); 79 | 80 | Ok(().into()) 81 | } 82 | } 83 | } 84 | 85 | ``` 86 | 87 | # 1 编写mock runtime 88 | mock runtime是我们进行pallet测试时需要提供的runtime,用于在测试环境中为pallet的函数提供必要的运行环境。在需要测试的pallet的src目录下创建mock.rs,内容如下: 89 | ``` 90 | use crate as pallet_use_test; 91 | 92 | use frame_support::traits::{ConstU16, ConstU64}; 93 | use frame_system as system; 94 | use sp_core::H256; 95 | use sp_runtime::{ 96 | testing::Header, 97 | traits::{BlakeTwo256, IdentityLookup}, 98 | }; 99 | 100 | type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; 101 | type Block = frame_system::mocking::MockBlock; 102 | 103 | frame_support::construct_runtime!( 104 | pub enum Test where 105 | Block = Block, 106 | NodeBlock = Block, 107 | UncheckedExtrinsic = UncheckedExtrinsic, 108 | { 109 | System: frame_system, 110 | UseTestDemo: pallet_use_test, 111 | } 112 | ); 113 | 114 | impl system::Config for Test { 115 | type BaseCallFilter = frame_support::traits::Everything; 116 | type BlockWeights = (); 117 | type BlockLength = (); 118 | type DbWeight = (); 119 | type Origin = Origin; 120 | type Call = Call; 121 | type Index = u64; 122 | type BlockNumber = u64; 123 | type Hash = H256; 124 | type Hashing = BlakeTwo256; 125 | type AccountId = u64; 126 | type Lookup = IdentityLookup; 127 | type Header = Header; 128 | type Event = Event; 129 | type BlockHashCount = ConstU64<250>; 130 | type Version = (); 131 | type PalletInfo = PalletInfo; 132 | type AccountData = (); 133 | type OnNewAccount = (); 134 | type OnKilledAccount = (); 135 | type SystemWeightInfo = (); 136 | type SS58Prefix = ConstU16<42>; 137 | type OnSetCode = (); 138 | type MaxConsumers = frame_support::traits::ConstU32<16>; 139 | } 140 | 141 | impl pallet_use_test::Config for Test { 142 | type Event = Event; 143 | type ClassType = u32; 144 | } 145 | 146 | pub use frame_support::pallet_prelude::GenesisBuild; 147 | 148 | pub fn new_test_ext() -> sp_io::TestExternalities { 149 | system::GenesisConfig::default().build_storage::().unwrap().into() 150 | } 151 | 152 | ``` 153 | 从上面的代码可以看出,写mock runtime的方式基本上和在runtime/src/lib.rs中加载pallet的写法基本是一样的,只不过在mock runtime中,我们只需要加载我们需要测试的必要的pallet就可以了。另外在配置pallet的时候也只需要能满足测试使用就可以了,而不用配置实际的类型。 154 | 155 | 156 | # 2 设置genesisconfig 157 | 158 | 在上面的代码中,我们还创建了一个new_test_ext函数,这个函数中,我们为测试需要的一些pallet进行初始配置,此处我们只需要为System进行默认的配置,在实际的测试情况中,往往需要为被测试的pallet以及相关的pallet提供一些初始设置。现在,我们这里的pallet-use-test还没有genesisConfig。 159 | 160 | 下面我们为pallet-use-test添加genesisConfig,在use-test/sec/lib.rs中添加代码如下: 161 | ``` 162 | #[pallet::genesis_config] 163 | pub struct GenesisConfig { 164 | pub class: T::ClassType, 165 | } 166 | 167 | #[cfg(feature = "std")] 168 | impl Default for GenesisConfig { 169 | fn default() -> Self { 170 | Self { class: Default::default() } 171 | } 172 | } 173 | 174 | #[pallet::genesis_build] 175 | impl GenesisBuild for GenesisConfig { 176 | fn build(&self) { 177 | Class::::put(self.class); 178 | } 179 | } 180 | ``` 181 | 在这个代码中,我们为pallet添加了默认的class,而这个配置在实际使用中,需要在我们的chainspec文件里面配置上此值(配置chainspec涉及到node/src/chainspec.rs和chainspec的json文件,这些内容我们后续再讲,此处我在代码的示例配置了)。 182 | 183 | **相应的,我们也需要修改上面mock.rs里面的new_test_ext函数如下:** 184 | ``` 185 | pub fn new_test_ext() -> sp_io::TestExternalities { 186 | let mut storage = system::GenesisConfig::default().build_storage::().unwrap().into(); 187 | let config: pallet_use_test::GenesisConfig = pallet_use_test::GenesisConfig { class: 2 }; 188 | config.assimilate_storage(&mut storage).unwrap(); 189 | 190 | storage.into() 191 | } 192 | ``` 193 | 写好mock.rs后,还需要在lib.rs中将其导出,因此在use-test/sec/lib.rs中添加: 194 | ``` 195 | #[cfg(test)] 196 | mod mock; 197 | ``` 198 | 199 | # 3 编写测试函数 200 | mock runtime准备好后就可以写测试函数了,我们在use-test/src目录下创建一个tests.rs的文件,添加测试函数的代码: 201 | ``` 202 | use super::pallet::Class; 203 | use crate::mock::*; 204 | use frame_support::{assert_noop, assert_ok}; 205 | use sp_runtime::traits::BadOrigin; 206 | 207 | #[test] 208 | fn test_set_class_info() { 209 | new_test_ext().execute_with(|| { 210 | assert_noop!(UseTestDemo::set_class_info(Origin::signed(1), 42), BadOrigin); 211 | assert_ok!(UseTestDemo::set_class_info(Origin::root(), 42)); 212 | assert_eq!(Class::::get(), 42); 213 | }); 214 | } 215 | ``` 216 | * 在测试函数中调用pallet的函数,方式如下: 217 | 在mock runtime中定义的模块名字::函数名字(函数参数) 218 | 219 | * 在测试函数中使用pallet的存储,方式如下: 220 | 1、导出pallet中存储,如上面例子中使用```use super::pallet::Class;``` 导出存储Class; 221 | 2、像正常在pallet中使用存储一样使用。 222 | 223 | # 4 运行测试 224 | 进入到对应的pallet中,运行```cargo test```即可。 225 | 226 | 227 | # 5 参考资料 228 | 229 | https://docs.substrate.io/v3/runtime/testing/ 230 | 231 | # 6 完整源码地址 232 | 233 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-test 234 | -------------------------------------------------------------------------------- /8.11在ocw中提交具有签名payload的未签名交易.md: -------------------------------------------------------------------------------- 1 | # 在ocw中提交具有签名payload的未签名交易 2 | 3 | 本节我们继续学习在ocw中提交未签名交易,不过和上一节不同的是,我们这里提交的交易是未签名的交易,但是交易中提交的payload是签过名的。因为交易是未签名的,所以这个交易是没有手续费的,但是交易中的负载是签过名的,所以可以对其中的负载进行验证,可以保证内容的可信性。 4 | 5 | 如果我们开发的系统中,需要自动从链下往链上提交某些信息以用于我们的业务逻辑,但是我们又不能在这些提交上花费交易费用,那么我们就可以选择在ocw中使用具有签名payload的未签名交易的形式。 6 | 7 | 下面我们就学习在ocw中使用具有签名payload的未签名交易的例子。 8 | 9 | # 1 pallet中的实现 10 | ## 1.1 在ocw中签名的子模块 11 | 这部分实现和我们在[章节使用OCW提交签名交易](8.9使用OCW提交签名交易.md)中提到过的一样,主要是用来在offchain worker对payload签名的子模块。在实际的开发中,这部分基本上是固定的写法。在substrate中支持ed25519和sr25519,我们此处使用的是sr29915作为例子。其中KEY_TYPE是offchain worker签名时检索key使用的类型,由开发者指定,我们这里还是指定为“demo”。代码如下: 12 | ``` 13 | use sp_core::crypto::KeyTypeId; 14 | pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo"); 15 | 16 | pub mod crypto { 17 | use super::KEY_TYPE; 18 | use sp_runtime::app_crypto::{app_crypto, sr25519}; 19 | app_crypto!(sr25519, KEY_TYPE); 20 | } 21 | ``` 22 | 23 | ## 1.2 config配置 24 | 这里的写法也基本上和[章节使用OCW提交签名交易](8.9使用OCW提交签名交易.md)中提到过的一样,如下: 25 | ``` 26 | #[pallet::config] 27 | pub trait Config: frame_system::Config + CreateSignedTransaction> { 28 | type AuthorityId: AppCrypto; 29 | type Event: From> + IsType<::Event>; 30 | } 31 | ``` 32 | ## 1.3 实现被ocw调用的调度函数 33 | 这部分在开发过程中是和具体的业务相关,这里我们就写一个简单的调度函数,如下: 34 | ``` 35 | #[pallet::call] 36 | impl Pallet { 37 | #[pallet::weight(0)] 38 | pub fn submit_something_unsigned_with_signed_payload( 39 | origin: OriginFor, 40 | something_payload: SomethingPayload, 41 | _signature: T::Signature, 42 | ) -> DispatchResultWithPostInfo { 43 | ensure_none(origin)?; 44 | 45 | let mut cnt: u64 = 0; 46 | let number: u64 = something_payload.block_number.try_into().unwrap_or(0); 47 | if number > 0 { 48 | cnt = number; 49 | } 50 | 51 | log::info!(target:"ocw", "unsigned with signed payload +++++++++++number: {:?}, cnt: {:?}", number, cnt); 52 | SomeInfo::::insert(&number, cnt); 53 | 54 | Self::deposit_event(Event::UnsignedPutSetSomeInfo(number, cnt)); 55 | 56 | Ok(().into()) 57 | } 58 | } 59 | ``` 60 | 在这个调度函数中,我们就简单的在存储SomeInfo中存储一组数据,分别是(区块高度,cnt计数),为了简单,我们这里的cnt计数就等于区块高度。 61 | 62 | ## 1.4 在ocw中提交具有签名payload的未签名交易 63 | 这部分基本上是固定的写法,代码如下: 64 | ``` 65 | #[pallet::hooks] 66 | impl Hooks> for Pallet { 67 | fn offchain_worker(block_number: T::BlockNumber) { 68 | let number: u64 = block_number.try_into().unwrap_or(0); 69 | 70 | //提交具有签名payload的未签名交易 71 | let _ = Signer::::any_account().send_unsigned_transaction( 72 | //准备payload,实际开发中换成自己定义的格式的payload 73 | |account| SomethingPayload { 74 | block_number, 75 | something: number, 76 | public: account.public.clone(), 77 | }, 78 | //在实际开发中将submit_something_unsigned_with_signed_payload换成自己的调度函数即可 79 | |payload, signature| Call::submit_something_unsigned_with_signed_payload { 80 | something_payload: payload, 81 | signature, 82 | }, 83 | ); 84 | } 85 | } 86 | ``` 87 | 88 | ## 1.5 实现未签名交易验证的trait 89 | 在ocw中要使用未签名交易,就必须为pallet实现ValidateUnsigned这个trait,具体的代码如下: 90 | ``` 91 | #[pallet::validate_unsigned] 92 | impl ValidateUnsigned for Pallet { 93 | type Call = Call; 94 | 95 | fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { 96 | if let Call::submit_something_unsigned_with_signed_payload { 97 | something_payload: ref payload, 98 | ref signature, 99 | } = call 100 | { 101 | let signature_valid = 102 | SignedPayload::::verify::(payload, signature.clone()); 103 | if !signature_valid { 104 | return InvalidTransaction::BadProof.into() 105 | } 106 | 107 | ValidTransaction::with_tag_prefix("OcwUnsigtxPayload") 108 | .priority(TransactionPriority::max_value()) 109 | .longevity(5) 110 | .propagate(false) 111 | .build() 112 | } else { 113 | InvalidTransaction::Call.into() 114 | } 115 | } 116 | } 117 | ``` 118 | 这里我们可以看到,我们对具体的payload的签名进行了验证,代码是: 119 | ``` 120 | let signature_valid = 121 | SignedPayload::::verify::(payload, signature.clone()); 122 | if !signature_valid { 123 | return InvalidTransaction::BadProof.into() 124 | } 125 | ``` 126 | 然后在生成ValidTransaction(这个可以简单看成是固定的写法)。 127 | 128 | 129 | # 2 在runtime中添加 130 | 接下来我们需要在runtime中添加此pallet,首先是添加依赖,已经讲过多次,此处不累述。这里重点讲讲代码中的修改,需要添加如下: 131 | ``` 132 | //因为我们在pallet的Config中继承了CreateSignedTransaction,所以需要添加如下的实现: 133 | impl frame_system::offchain::CreateSignedTransaction for Runtime 134 | where 135 | Call: From, 136 | { 137 | fn create_transaction>( 138 | call: Call, 139 | public: ::Signer, 140 | account: AccountId, 141 | nonce: Index, 142 | ) -> Option<(Call, ::SignaturePayload)> { 143 | let tip = 0; 144 | // take the biggest period possible. 145 | let period = 1 << 7; 146 | // BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; 147 | 148 | let current_block = System::block_number() 149 | .saturated_into::() 150 | // The `System::block_number` is initialized with `n+1`, 151 | // so the actual block number is `n`. 152 | .saturating_sub(1); 153 | let era = Era::mortal(period, current_block); 154 | let extra = ( 155 | frame_system::CheckNonZeroSender::::new(), 156 | frame_system::CheckSpecVersion::::new(), 157 | frame_system::CheckTxVersion::::new(), 158 | frame_system::CheckGenesis::::new(), 159 | frame_system::CheckEra::::from(era), 160 | frame_system::CheckNonce::::from(nonce), 161 | frame_system::CheckWeight::::new(), 162 | pallet_transaction_payment::ChargeTransactionPayment::::from(tip), 163 | ); 164 | let raw_payload = SignedPayload::new(call, extra) 165 | .map_err(|e| { 166 | log::warn!("Unable to create signed payload: {:?}", e); 167 | }) 168 | .ok()?; 169 | let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; 170 | let address = ::Lookup::unlookup(account); 171 | let (call, extra, _) = raw_payload.deconstruct(); 172 | Some((call, (address, signature.into(), extra))) 173 | } 174 | } 175 | 176 | impl frame_system::offchain::SigningTypes for Runtime { 177 | type Public = ::Signer; 178 | type Signature = Signature; 179 | } 180 | 181 | impl frame_system::offchain::SendTransactionTypes for Runtime 182 | where 183 | Call: From, 184 | { 185 | type OverarchingCall = Call; 186 | type Extrinsic = UncheckedExtrinsic; 187 | } 188 | ``` 189 | 然后就是需要为pallet添加相应的配置,这里重点要提的就是添加签名的类型,如下: 190 | ``` 191 | pub struct MyAuthorityId; 192 | 193 | impl frame_system::offchain::AppCrypto<::Signer, Signature> for MyAuthorityId { 194 | type RuntimeAppPublic = pallet_ocw_sigtx::crypto::Public; 195 | type GenericSignature = sp_core::sr25519::Signature; 196 | type GenericPublic = sp_core::sr25519::Public; 197 | } 198 | 199 | impl pallet_ocw_unsigxtx_payload::Config for Runtime { 200 | type Event = Event; 201 | //下面这行 202 | type AuthorityId = MyAuthorityId; 203 | } 204 | ``` 205 | 206 | 至此,我们基本上就把在ocw中提交具有签名payload的为签名交易实现好了。 207 | 208 | # 3 完整示例代码 209 | 210 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/ocw-unsigxtx-payload/src 211 | 212 | -------------------------------------------------------------------------------- /8.1storage使用介绍.md: -------------------------------------------------------------------------------- 1 | # storage使用介绍 2 | 3 | 最开始的时候本来没准备介绍这一章节,觉得storage直接看官方文档就好,但是和身边的小伙伴交流,觉得还是应该讲一下,毕竟这部分是在pallet时经常会使用到的部分。 4 | 5 | 首先我们来讲讲对storage的理解。第一次接触substrate的时候,和容易让人把storage和其它区块链架构中的storage(持久化存储)混淆。在其它的区块链中,如ethereum或者bitcoin中,节点对区块链相关的数据使用leveldb这样的数据库进行持久化存储,substrate中也有些持久化存储。但是,这些持久化存储不是我们本节要说的我们在pallet中使用的storage。**在pallet中要使用的storage更多的其实是一个应用层的概念**,如果用城市建造来类比,持久化存储就像是整个城市的马路或者是管道,而我们谈论的storage则是某个具体建筑或者房屋里面的水管会小路,至于这些小水管(或小路)是怎么和整个城市的大路联系起来的,不是我们讨论的范围。 6 | 7 | # 1 storage的使用方式 8 | 前面我们在pallet的组成中介绍过storage的使用方式,但是这里我们既然是重新来讲这部分,那肯定要讲的深入一点。这里我们把[substrate官方文档](https://docs.substrate.io/rustdocs/latest/frame_support/attr.pallet.html#storage-palletstorage-optional)中的使用方式拿过来,然后逐个讲解: 9 | ``` 10 | 1 #[pallet::storage] 11 | 2 #[pallet::getter(fn $getter_name)] // optional 12 | 3 $vis type $StorageName<$some_generic> $optional_where_clause 13 | 4 = $StorageType<$generic_name = $some_generics, $other_name = $some_other, ...>; 14 | ``` 15 | 上面这几行代码是官方文档中告诉我们如何在pallet中定义storage的代码,为了方便讲解,我加上了行号。 16 | 17 | * 第一行,```#[pallet::storage]```是定义的storage时的固定写法,表示下面定义了一个storage。在定义storage时,无论你怎么使用,都必须写这一行。 18 | * 第二行,```#[pallet::getter(fn $getter_name)]```,在有些定义storage时会使用这一行,有些不会。这一行的意思是自动为下面定义的storage生成一个getter函数,函数的名字就是$getter_name。例如我定义如下的storage: 19 | ``` 20 | #[pallet::storage] 21 | #[pallet::getter(fn my_id)] 22 | pub type MyId = StorageValue<_, u8, ValueQuery>; 23 | ``` 24 | 25 | 这里我定义了一个存储MyId,就自动为其生成了getter函数,函数名字是my_id,后续可以在pallet使用my_id()函数来获取该Storage中存储的值。 26 | * 第三行和第四行就是真正定义Storage。定义的格式一定是$vis type开头(其中$vis是public、或者无这些范围修饰符,也就是表示其在代码中的使用范围)。接下来的$StorageName就是存储的名字,然后紧接着的尖括号中的$some_generic是泛型类型,而$optional_where_clause是对应泛型类型的约束。所以,上面那个例子我们也可以定义成这样: 27 | ``` 28 | #[pallet::storage] 29 | #[pallet::getter(fn my_id)] 30 | pub type MyId where T: Config = StorageValue<_, u8, ValueQuery>; 31 | ``` 32 | 这里我们使用了ValueQuery,表示为查询值实现了QueryKindTrait,具体的此处不展开,可以[查阅文档](https://paritytech.github.io/substrate/master/frame_support/storage/types/struct.ValueQuery.html)。 33 | * 而第四行中的$StorageType则是具体的storage类型(也就是StorageValue\StorageMap\StorageDoubleMap\StorageNMap中的一种),接着的尖括号中的第一个参数```$generic_name = $some_generics```主要用来生产storage的前缀(有兴趣的小伙伴可以深入研究下,可能和底层存储有关),在具体使用中一般都使用```_```即可,尖括号中从第二个参数起,就和具体的Storage类型相关,需要参见具体的Storage类型。 34 | 35 | # 2 使用示例 36 | 下面我们就来用一个例子演示一下各种存储。我们假定有这样一个应用,记录某个年纪各个寝室每个床位的学生姓名,我们将分别使用StorageValue\StorageMap\StorageDoubleMap几种存储类型。在此例子中,我们将重新写一个pallet,其过程和前面我们讲到的实现简单的pallet中的过程一样,后续我们的示例也都是这样的过程。本节我们还是复习一下创建pallet以及加载的过程,但是在后面的例子中,如非必要,我们将只写pallet部分的代码,在runtime中使用pallet我们将不重复赘述。 37 | 38 | ## 2.1 创建pallet 39 | 我们还是在之前的substrate-node-template中进行。 40 | 41 | * 拷贝template,过程如下: 42 | ``` 43 | cd 上层路径/substrate-node-template/pallets 44 | cp template use-storage -rf 45 | ``` 46 | * 修改pallet包名,打开substrate-node-template/pallets/use-storage/Cargo.toml文件,修改如下内容: 47 | ``` 48 | [package] 49 | name = "pallet-use-storage" #修改此行 50 | ... 51 | 52 | ``` 53 | * 添加模板 54 | 55 | 接下来我们将substrate-node-template/pallets/use-storage/src/lib.rs中的内容完全删掉,然后拷贝[模板](https://docs.substrate.io/v3/runtime/frame/)到这个文件中。 56 | ## 2.2 编写pallet中的逻辑 57 | 然后我们在pallet中定义三个存储,分别用来存储班级、学生、寝室的信息,分别如下: 58 | ``` 59 | // 4. Runtime Storage 60 | // use storageValue store class. 61 | #[pallet::storage] 62 | #[pallet::getter(fn my_class)] 63 | pub type Class = StorageValue<_, u32>; 64 | 65 | // use storageMap store (student number -> student name). 66 | #[pallet::storage] 67 | #[pallet::getter(fn students_info)] 68 | pub type StudentsInfo = StorageMap<_, Blake2_128Concat, u32, u128, ValueQuery>; 69 | 70 | #[pallet::storage] 71 | #[pallet::getter(fn dorm_info)] 72 | pub type DormInfo = StorageDoubleMap< 73 | _, 74 | Blake2_128Concat, 75 | u32, //dorm number 76 | Blake2_128Concat, 77 | u32, //bed number 78 | u32, // student number 79 | ValueQuery, 80 | >; 81 | ``` 82 | * Class存储班级编号,需要root权限才能设置,使用StorageValue存储; 83 | * StudentsInfo存储学生的学号和姓名的对应关系,使用StorageMap存储; 84 | * DormInfo存储寝室号、床号、学号之间的对应关系,使用StorageDoubleMap存储。 85 | 86 | 另外我们定义了设置这些信息成功后对应的Event,分别如下: 87 | ``` 88 | // 5. Runtime Events 89 | // Can stringify event types to metadata. 90 | #[pallet::event] 91 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 92 | pub enum Event { 93 | SetClass(u32), 94 | SetStudentInfo(u32, u128), 95 | SetDormInfo(u32, u32, u32), 96 | } 97 | ``` 98 | 99 | 所有的设置信息的交易函数实现如系: 100 | ``` 101 | #[pallet::weight(0)] 102 | pub fn set_class_info(origin: OriginFor, class: u32) -> DispatchResultWithPostInfo { 103 | ensure_root(origin)?; 104 | 105 | Class::::put(class); 106 | Self::deposit_event(Event::SetClass(class)); 107 | 108 | Ok(().into()) 109 | } 110 | 111 | #[pallet::weight(0)] 112 | pub fn set_student_info( 113 | origin: OriginFor, 114 | student_number: u32, 115 | student_name: u128, 116 | ) -> DispatchResultWithPostInfo { 117 | ensure_signed(origin)?; 118 | 119 | StudentsInfo::::insert(&student_number, &student_name); 120 | Self::deposit_event(Event::SetStudentInfo(student_number, student_name)); 121 | 122 | Ok(().into()) 123 | } 124 | 125 | #[pallet::weight(0)] 126 | pub fn set_dorm_info( 127 | origin: OriginFor, 128 | dorm_number: u32, 129 | bed_number: u32, 130 | student_number: u32, 131 | ) -> DispatchResultWithPostInfo { 132 | ensure_signed(origin)?; 133 | 134 | DormInfo::::insert(&dorm_number, &bed_number, &student_number); 135 | Self::deposit_event(Event::SetDormInfo(dorm_number, bed_number, student_number)); 136 | 137 | Ok(().into()) 138 | } 139 | ``` 140 | 141 | 基本上都是判断发起交易者的权限,然后设置信息、发出事件这样的过程,整个pallet完整的代码可以参考[这里](https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/pallets/use-storage/src/lib.rs). 142 | 143 | ## 2.3 在runtime中使用 144 | 145 | 写完pallet后,我们就可以将pallet添加到runtime中。 146 | 147 | * 添加依赖,在substrate-node-template/runtime/Cargo.toml中添加如下代码: 148 | ``` 149 | [dependencies] 150 | ... 151 | #添加下面这行 152 | pallet-use-storage = { 153 | version = "4.0.0-dev", 154 | default-features = false, 155 | path = "../pallets/use-storage" 156 | } 157 | ... 158 | 159 | [features] 160 | default = ["std"] 161 | std = [ 162 | "codec/std", 163 | ... 164 | #添加下面这行 165 | "pallet-use-storage/std", 166 | ... 167 | ] 168 | ``` 169 | 170 | * 修改runtime/src/lib.rs,添加如下代码: 171 | ``` 172 | ... 173 | impl pallet_use_storage::Config for Runtime { 174 | type Event = Event; 175 | } 176 | ... 177 | 178 | construct_runtime!( 179 | pub enum Runtime where 180 | Block = Block, 181 | NodeBlock = opaque::Block, 182 | UncheckedExtrinsic = UncheckedExtrinsic 183 | { 184 | System: frame_system, 185 | ... 186 | TemplateModule: pallet_template, 187 | SimplePallet: pallet_simple_pallet, 188 | //添加下面这行 189 | UseStorage: pallet_use_storage, 190 | } 191 | ); 192 | 193 | ``` 194 | 195 | ## 2.4 编译&运行 196 | 197 | 回到substrate-node-template目录,执行如下编译: 198 | ``` 199 | cargo build 200 | ``` 201 | 202 | 启动节点: 203 | ``` 204 | ./target/debug/node-template --dev 205 | ``` 206 | 207 | ## 2.5 使用前端进行交互 208 | 209 | 我们可以用polkadot-js-app进行交互,在浏览器中输入https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944# 210 | 211 | 然后设置的操作分别如下图: 212 | ![设置班级](./assets/设置class.PNG) 213 | ![设置学生信息](./assets/设置学生信息.PNG) 214 | ![设置寝室信息](./assets/设置寝室信息.PNG) 215 | 216 | 查询寝室信息的操作如下图: 217 | ![查询寝室信息](./assets/查询寝室信息.PNG) 218 | 219 | 小伙伴们可以再查一下学生信息和班级信息。 220 | 221 | 222 | # 3 参考文档 223 | 224 | https://docs.substrate.io/rustdocs/latest/frame_support/attr.pallet.html#storage-palletstorage-optional 225 | 226 | https://paritytech.github.io/substrate/master/frame_support/storage/types/index.html 227 | 228 | https://paritytech.github.io/substrate/master/frame_support/storage/types/struct.ValueQuery.html 229 | 230 | # 4 示例完整代码 231 | 232 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-storage 233 | 234 | -------------------------------------------------------------------------------- /8.5pallet中的Config.md: -------------------------------------------------------------------------------- 1 | # pallet中的Config 2 | 3 | 我们在[pallet的组成](7Pallet的组成.md)这一节介绍了pallet的各个部分,到目前为止,我们对storage、event、error、hooks、交易等进行了比较详细的介绍。这一节,我们介绍其中的Config部分(也就是Runtime配置trait这部分)。 4 | 5 | 学习这一节的内容,首先需要好好理解rust中关于trait和关联类型相关的知识,详细的可以参考[这里](5编写pallet的Rust前置知识.md). 6 | 7 | # 1 pallet简单示例 8 | 在正式开始前,我们先来写一个简单的pallet,名字叫做use-config1,其pallet中的src/lib.rs中的代码如下: 9 | 10 | ``` 11 | #![cfg_attr(not(feature = "std"), no_std)] 12 | 13 | pub use pallet::*; 14 | #[frame_support::pallet] 15 | pub mod pallet { 16 | use frame_support::pallet_prelude::*; 17 | use frame_system::pallet_prelude::*; 18 | 19 | #[pallet::pallet] 20 | #[pallet::generate_store(pub(super) trait Store)] 21 | pub struct Pallet(_); 22 | 23 | // 3. Runtime Configuration Trait 24 | #[pallet::config] 25 | pub trait Config: frame_system::Config { 26 | type Event: From> + IsType<::Event>; 27 | } 28 | 29 | // 4. Runtime Storage 30 | // 用storageMap存储学生信息,(key, value)分别对应的是学号和姓名. 31 | #[pallet::storage] 32 | #[pallet::getter(fn students_info)] 33 | pub type StudentsInfo = StorageMap<_, Blake2_128Concat, u32, u128, ValueQuery>; 34 | 35 | // 5. Runtime Events 36 | // Can stringify event types to metadata. 37 | #[pallet::event] 38 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 39 | pub enum Event { 40 | SetStudentInfo(u32, u128), 41 | } 42 | 43 | // 8. Runtime Errors 44 | #[pallet::error] 45 | pub enum Error { 46 | // 相同学号的只允许设置一次名字 47 | SetStudentsInfoDuplicate, 48 | } 49 | 50 | // 7. Extrinsics 51 | // Functions that are callable from outside the runtime. 52 | #[pallet::call] 53 | impl Pallet { 54 | #[pallet::weight(0)] 55 | pub fn set_student_info( 56 | origin: OriginFor, 57 | student_number: u32, 58 | student_name: u128, 59 | ) -> DispatchResultWithPostInfo { 60 | ensure_signed(origin)?; 61 | 62 | if StudentsInfo::::contains_key(student_number) { 63 | return Err(Error::::SetStudentsInfoDuplicate.into()) 64 | } 65 | 66 | StudentsInfo::::insert(&student_number, &student_name); 67 | Self::deposit_event(Event::SetStudentInfo(student_number, student_name)); 68 | 69 | Ok(().into()) 70 | } 71 | } 72 | } 73 | 74 | ``` 75 | 在上面的pallet中,我们就实现了一个功能,就是把学生的信息(学号和姓名)存储在链上。我们这里使用StorageMap来进行存储: 76 | ``` 77 | pub type StudentsInfo = StorageMap<_, Blake2_128Concat, u32, u128, ValueQuery>; 78 | ``` 79 | 这里我们定义了学号的格式是u32类型,学生的姓名是u128类型。但是其实在实际的应用中,我们学号的类型是u8类型,也可能是u16类型,还甚至是u64类型。同样学生的姓名也可能是其它类型。所以其实这里比较好的写法是下面这样: 80 | ``` 81 | pub type StudentsInfo = StorageMap<_, Blake2_128Concat, StudentNumberType, StudentNameType, ValueQuery>; 82 | ``` 83 | 然后里面的StudentNumberType, StudentNameType可以在我们真正使用的时候指定。 84 | 85 | 好了,介绍到这里,基本可以直接使用我们的Config了。 86 | 87 | # 2 在Config中定义配置类型 88 | 为了便于对比,我们拷贝上面的use-config1的代码,创建新的pallet名字叫做use-config2,use-config2/src/lib.rs中的代码如下: 89 | ``` 90 | #![cfg_attr(not(feature = "std"), no_std)] 91 | 92 | pub use pallet::*; 93 | #[frame_support::pallet] 94 | pub mod pallet { 95 | use codec::Codec; 96 | use frame_support::{ 97 | pallet_prelude::*, sp_runtime::traits::AtLeast32BitUnsigned, sp_std::fmt::Debug, 98 | }; 99 | use frame_system::pallet_prelude::*; 100 | 101 | #[pallet::pallet] 102 | #[pallet::generate_store(pub(super) trait Store)] 103 | pub struct Pallet(_); 104 | 105 | // 3. Runtime Configuration Trait 106 | #[pallet::config] 107 | pub trait Config: frame_system::Config { 108 | type Event: From> + IsType<::Event>; 109 | //(1)声明了StudentNumberType和StudentNameType 110 | //声明StudentNumber类型 111 | type StudentNumberType: Member 112 | + Parameter 113 | + AtLeast32BitUnsigned 114 | + Codec 115 | + Copy 116 | + Debug 117 | + Default 118 | + MaxEncodedLen 119 | + MaybeSerializeDeserialize; 120 | 121 | //声明StudentName类型 122 | type StudentNameType: Parameter 123 | + Member 124 | + AtLeast32BitUnsigned 125 | + Codec 126 | + Default 127 | + From 128 | + Into 129 | + Copy 130 | + MaxEncodedLen 131 | + MaybeSerializeDeserialize 132 | + Debug; 133 | } 134 | 135 | // 4. Runtime Storage 136 | // 用storageMap存储学生信息,(key, value)分别对应的是学号和姓名. 137 | // (2)使用了T::StudentNumberType, T::StudentNameType替换了原来的u32和u128 138 | #[pallet::storage] 139 | #[pallet::getter(fn students_info)] 140 | pub type StudentsInfo = 141 | StorageMap<_, Blake2_128Concat, T::StudentNumberType, T::StudentNameType, ValueQuery>; 142 | 143 | // 5. Runtime Events 144 | // Can stringify event types to metadata. 145 | #[pallet::event] 146 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 147 | pub enum Event { 148 | // (3)使用了T::StudentNumberType, T::StudentNameType替换了原来的u32和u128 149 | SetStudentInfo(T::StudentNumberType, T::StudentNameType), 150 | } 151 | 152 | // 8. Runtime Errors 153 | #[pallet::error] 154 | pub enum Error { 155 | // 相同学号的只允许设置一次名字 156 | SetStudentsInfoDuplicate, 157 | } 158 | 159 | // 7. Extrinsics 160 | // Functions that are callable from outside the runtime. 161 | #[pallet::call] 162 | impl Pallet { 163 | // (4)使用了T::StudentNumberType, T::StudentNameType替换了原来的u32和u128 164 | #[pallet::weight(0)] 165 | pub fn set_student_info( 166 | origin: OriginFor, 167 | student_number: T::StudentNumberType, 168 | student_name: T::StudentNameType, 169 | ) -> DispatchResultWithPostInfo { 170 | ensure_signed(origin)?; 171 | 172 | if StudentsInfo::::contains_key(student_number) { 173 | return Err(Error::::SetStudentsInfoDuplicate.into()) 174 | } 175 | 176 | StudentsInfo::::insert(&student_number, &student_name); 177 | Self::deposit_event(Event::SetStudentInfo(student_number, student_name)); 178 | 179 | Ok(().into()) 180 | } 181 | } 182 | } 183 | 184 | ``` 185 | 从上面的代码可以看出主要有4处修改,分别用(1)-(4)标注: 186 | 187 | (1)在Config中声明了StudentNumberType和StudentNameType,两种类型的冒号后面是对应的trait约束。 188 | 189 | (2)在StorageMap中不再使用原来的u32和u128类型,而是使用关联类型T::StudentNumberType和T::StudentNameType。 190 | 191 | (3)在Event中不再使用原来的u32和u128类型,而是使用关联类型T::StudentNumberType和T::StudentNameType。 192 | 193 | (4)在函数的输入参数中不再使用原来的u32和u128类型,而是使用关联类型T::StudentNumberType和T::StudentNameType。 194 | 195 | 196 | 至此,我们基本上把pallet中的类型换成了关联类型。但是这个关联类型具体的类型在哪里指定呢?下面我们就来揭晓。 197 | 198 | # 3 在runtime中指定具体的类型 199 | 学过前面的知识我们知道,需要在runtime/src/lib.rs将pallet加载进来才能真正的使用pallet。下面我们就把use-config2加到runtime中。首先在substrate-node-template/runtime/Cargo.toml添加依赖如下: 200 | ``` 201 | [dependencies] 202 | ... 203 | pallet-use-config2 = { version = "4.0.0-dev", default-features = false, path = "../pallets/use-config2" } 204 | ... 205 | [features] 206 | default = ["std"] 207 | std = [ 208 | "codec/std", 209 | ... 210 | "pallet-use-config2/std", 211 | ... 212 | ] 213 | ``` 214 | 215 | 接下来在substrate-node-template/runtime/src/lib.rs中添加如下代码: 216 | ``` 217 | //添加下面的5行 218 | impl pallet_use_config2::Config for Runtime { 219 | type Event = Event; 220 | type StudentNumberType = u32; //指定具体的类型 221 | type StudentNameType = u128; //指定具体的类型 222 | } 223 | 224 | construct_runtime!( 225 | pub enum Runtime where 226 | Block = Block, 227 | NodeBlock = opaque::Block, 228 | UncheckedExtrinsic = UncheckedExtrinsic 229 | { 230 | System: frame_system, 231 | ... 232 | UseConfig2: pallet_use_config2, //添加此行 233 | } 234 | ); 235 | ``` 236 | 从上面的代码可以看出,我们是在impl pallet_use_config2::Config for Runtime中为StudentNumberType和StudentNameType指定具体的类型。 237 | 238 | # 4 交互 239 | 在substrate-node-template编译: 240 | ``` 241 | cargo build 242 | ``` 243 | 运行: 244 | ``` 245 | ./target/debug/node-template --dev 246 | ``` 247 | 248 | 在浏览器输入https://polkadot.js.org/apps/ ,可以对use-config2的函数进行测试。 249 | 250 | # 5 参考文档 251 | 252 | https://docs.substrate.io/v3/runtime/macros/ 253 | 254 | # 6 完整代码地址 255 | 256 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-config2 257 | 258 | 259 | -------------------------------------------------------------------------------- /8.6在pallet中使用其它pallet.md: -------------------------------------------------------------------------------- 1 | # 在pallet中使用其它pallet 2 | 3 | 本节,我们讲讲如何在自己的pallet中使用其它的pallet。在自己的pallet中使用其它的pallet主要有以下几种情况: 4 | * 在pallet的config中定义类型,然后runtime中使用时指定这个类型为frame中指定某个现成的pallet; 5 | * 在pallet的config中定义类型,然后runtime中使用时指定这个类型为frame中指定某个自定义的pallet; 6 | * 封装和扩展现有的pallet。 7 | 8 | 本节主要介绍前两种方式,实际上第一种和第二种是同样的方式,但是我们这里分成两种情况介绍。 9 | 10 | # 1 在runtime中直接指定某个类型为其它的pallet 11 | 在上一节中我们讲解了pallet的config中定义类型,然后在runtime中指定具体的类型。此处讲的第一种使用其它pallet就是这种方式。这种方式比较常见的就是在pallet中定义currency类型,然后用指定currency类型为balances pallet。详细的可以看substrate中node中的使用,在pallet_assets中使用了pallet_balances,就是通过指定前者的currency类型为后者(详情见:https://github.com/paritytech/substrate/blob/master/bin/node/runtime/src/lib.rs#L1343) 。 12 | 13 | # 2 pallet中使用其它pallet的storage 14 | 下面我们在自定义两个pallet,分别叫做pallet-use-other-pallet1和pallet-storage-provider,然后我们在前一个pallet中读取和存储后一个pallet,下面我们看具体实现。整个部分完整的代码可以参考[这里](https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets)(本节对应的pallet是storage-provider和use-other-pallet1)。 15 | 16 | ## 2.1 pallet-storage-provider实现 17 | 学过之前的知识我们知道,pallet的config中的类型定义实际上一种trait约束,就是对应的类型需要实现冒号后的trait。为了方便演示,我们定义如下trait: 18 | ``` 19 | pub trait StorageInterface{ 20 | type Value; 21 | fn get_param() -> Self::Value; 22 | fn set_param(v: Self::Value); 23 | } 24 | ``` 25 | 这个trait可以单独定义在某个包中。这里我们为了方便,直接放在pallet-storage-provider对应的文件夹中。下面我们在看看pallet-storage-provider/src/lib.rs的代码,如下: 26 | ``` 27 | #![cfg_attr(not(feature = "std"), no_std)] 28 | 29 | pub use pallet::*; 30 | pub use traits::StorageInterface; 31 | 32 | pub mod traits; 33 | 34 | #[frame_support::pallet] 35 | pub mod pallet { 36 | use codec::Codec; 37 | use frame_support::{ 38 | pallet_prelude::*, sp_runtime::traits::AtLeast32BitUnsigned, sp_std::fmt::Debug, 39 | }; 40 | use frame_system::pallet_prelude::*; 41 | 42 | #[pallet::pallet] 43 | #[pallet::generate_store(pub(super) trait Store)] 44 | pub struct Pallet(_); 45 | 46 | #[pallet::config] 47 | pub trait Config: frame_system::Config { 48 | type Event: From> + IsType<::Event>; 49 | type Value: Member 50 | + Parameter 51 | + AtLeast32BitUnsigned 52 | + Codec 53 | + From 54 | + Into 55 | + Copy 56 | + Debug 57 | + Default 58 | + MaxEncodedLen 59 | + MaybeSerializeDeserialize; 60 | } 61 | 62 | #[pallet::storage] 63 | pub type MyValue = StorageValue<_, T::Value, ValueQuery>; 64 | 65 | #[pallet::event] 66 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 67 | pub enum Event { 68 | FunctionCall, 69 | } 70 | 71 | #[pallet::call] 72 | impl Pallet { 73 | #[pallet::weight(0)] 74 | pub fn my_function( 75 | origin: OriginFor, 76 | ) -> DispatchResultWithPostInfo { 77 | ensure_signed(origin)?; 78 | log::info!(target: "storage provider", "my function!"); 79 | Self::deposit_event(Event::FunctionCall); 80 | 81 | Ok(().into()) 82 | } 83 | } 84 | } 85 | 86 | // 注意此处:我们为pallet实现了前面定义的trait StorageInterface. 87 | impl StorageInterface for Pallet { 88 | type Value = T::Value; 89 | 90 | fn get_param() -> Self::Value { 91 | MyValue::::get() 92 | } 93 | 94 | fn set_param(v: Self::Value) { 95 | MyValue::::put(v); 96 | } 97 | } 98 | 99 | ``` 100 | 101 | ## 2.2 pallet-use-other-pallet1 102 | 下面我们在看pallet-use-other-pallet1的代码: 103 | ``` 104 | #![cfg_attr(not(feature = "std"), no_std)] 105 | 106 | pub use pallet::*; 107 | 108 | #[frame_support::pallet] 109 | pub mod pallet { 110 | use codec::Codec; 111 | use frame_support::{ 112 | pallet_prelude::*, sp_runtime::traits::AtLeast32BitUnsigned, sp_std::fmt::Debug, 113 | }; 114 | use frame_system::pallet_prelude::*; 115 | use pallet_storage_provider::traits::StorageInterface; 116 | 117 | #[pallet::pallet] 118 | #[pallet::generate_store(pub(super) trait Store)] 119 | pub struct Pallet(_); 120 | 121 | // 3. Runtime Configuration Trait 122 | #[pallet::config] 123 | pub trait Config: frame_system::Config { 124 | type Event: From> + IsType<::Event>; 125 | type Value: Member 126 | + Parameter 127 | + AtLeast32BitUnsigned 128 | + Codec 129 | + From 130 | + Into 131 | + Copy 132 | + Debug 133 | + Default 134 | + MaxEncodedLen 135 | + MaybeSerializeDeserialize; 136 | 137 | //定义了MyStorage类型,要求其实现trait StorageInterface 138 | type MyStorage: StorageInterface; 139 | } 140 | 141 | // 5. Runtime Events 142 | // Can stringify event types to metadata. 143 | #[pallet::event] 144 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 145 | pub enum Event { 146 | StoreEvent, 147 | } 148 | 149 | // 7. Extrinsics 150 | // Functions that are callable from outside the runtime. 151 | #[pallet::call] 152 | impl Pallet { 153 | #[pallet::weight(0)] 154 | pub fn storage_value( 155 | origin: OriginFor, 156 | value: T::Value, 157 | ) -> DispatchResultWithPostInfo { 158 | ensure_signed(origin)?; 159 | 160 | T::MyStorage::set_param(value); 161 | 162 | //使用trait StorageInterface中的函数 163 | let v = T::MyStorage::get_param(); 164 | log::info!(target: "other-pallet", "Value get from storage is: {:?}", v); 165 | 166 | Self::deposit_event(Event::StoreEvent); 167 | 168 | Ok(().into()) 169 | } 170 | } 171 | } 172 | ``` 173 | 174 | ## 2.3 在runtime中添加两个pallet 175 | 下面就是在runtime中添加对应的pallet。 176 | 177 | 首先当然添加依赖,在runtime/Cargo.toml中添加: 178 | ``` 179 | [dependencies] 180 | ... 181 | pallet-storage-provider ={ 182 | version = "4.0.0-dev", 183 | default-features = false, 184 | path = "../pallets/storage-provider" } 185 | pallet-use-other-pallet1 = { 186 | version = "4.0.0-dev", 187 | default-features = false, 188 | path = "../pallets/use-other-pallet1" } 189 | ... 190 | 191 | [features] 192 | default = ["std"] 193 | std = [ 194 | "codec/std", 195 | "scale-info/std", 196 | "frame-executive/std", 197 | "frame-support/std", 198 | "frame-system-rpc-runtime-api/std", 199 | "frame-system/std", 200 | "pallet-aura/std", 201 | "pallet-balances/std", 202 | "pallet-grandpa/std", 203 | "pallet-randomness-collective-flip/std", 204 | "pallet-sudo/std", 205 | "pallet-template/std", 206 | "pallet-simple-pallet/std", 207 | "pallet-use-storage/std", 208 | "pallet-use-errors/std", 209 | "pallet-ext-example/std", 210 | "pallet-use-hooks/std", 211 | "pallet-use-rpc/std", 212 | "pallet-use-config1/std", 213 | "pallet-use-config2/std", 214 | "pallet-storage-provider/std", 215 | "pallet-use-other-pallet1/std", 216 | ... 217 | ] 218 | 219 | ``` 220 | 然后在runtime/src/lib.rs中添加如下: 221 | ``` 222 | //添加下面4行 223 | impl pallet_storage_provider::Config for Runtime { 224 | type Event = Event; 225 | type Value = u32; 226 | } 227 | 228 | //添加下面5行 229 | impl pallet_use_other_pallet1::Config for Runtime { 230 | type Event = Event; 231 | type Value = u32; 232 | type MyStorage = StorageProvider; 233 | } 234 | 235 | // Create the runtime by composing the FRAME pallets that were previously configured. 236 | construct_runtime!( 237 | pub enum Runtime where 238 | Block = Block, 239 | NodeBlock = opaque::Block, 240 | UncheckedExtrinsic = UncheckedExtrinsic 241 | { 242 | System: frame_system, 243 | ... 244 | //添加下面两行 245 | StorageProvider: pallet_storage_provider, 246 | UseOtherPallet1: pallet_use_other_pallet1, 247 | } 248 | ); 249 | ``` 250 | 251 | ## 2.4 编译执行 252 | 编译命令: 253 | ``` 254 | cargo build 255 | ``` 256 | 执行命令: 257 | ``` 258 | ./target/debug/node-template --dev 259 | ``` 260 | 261 | ## 2.5 使用 262 | 在浏览器输入https://polkadot.js.org/apps/#/ ,然后按照下图可以调用pallet_use_other_pallet1中的设置存储函数。 263 | 264 | ![使用](assets/调用其它pallet.JPG) 265 | 266 | # 3 完整源码地址 267 | 268 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-other-pallet1 269 | 270 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/storage-provider 271 | 272 | https://github.com/anonymousGiga/learn-substrate-easy-source/blob/main/substrate-node-template/runtime/src/lib.rs#L308 273 | 274 | -------------------------------------------------------------------------------- /8.15在pallet中添加rpc接口.md: -------------------------------------------------------------------------------- 1 | # 在pallet中添加rpc接口 2 | 3 | # 1 pallet中的实现 4 | ## 1.1 pallet的内容 5 | 在实现rpc之前,我们先实现一个简单的pallet,名字叫做use-rpc,use-rpc/src/lib.rs中的代码如下: 6 | ``` 7 | #![cfg_attr(not(feature = "std"), no_std)] 8 | 9 | pub use pallet::*; 10 | #[frame_support::pallet] 11 | pub mod pallet { 12 | use frame_support::pallet_prelude::*; 13 | use frame_system::pallet_prelude::*; 14 | 15 | #[pallet::pallet] 16 | #[pallet::generate_store(pub(super) trait Store)] 17 | pub struct Pallet(_); 18 | 19 | #[pallet::config] 20 | pub trait Config: frame_system::Config { 21 | type Event: From> + IsType<::Event>; 22 | } 23 | 24 | #[pallet::storage] 25 | #[pallet::getter(fn my_class)] 26 | pub type Class = StorageValue<_, u32>; 27 | 28 | #[pallet::call] 29 | impl Pallet { 30 | #[pallet::weight(0)] 31 | pub fn set_class_info(origin: OriginFor, class: u32) -> DispatchResultWithPostInfo { 32 | ensure_signed(origin)?; 33 | Class::::put(class); 34 | let _c = Self::my_class(); 35 | Self::deposit_event(Event::SetClass(class)); 36 | Ok(().into()) 37 | } 38 | } 39 | } 40 | 41 | ``` 42 | 然后可以将此pallet添加到runtime中,然后编译。 43 | 44 | ## 1.2 定义runtime中rpc接口 45 | 在use-rpc目录下,创建包runtime-api,然后其Cargo.toml的内容如下: 46 | ``` 47 | [package] 48 | name = "use-rpc-runtime-api" 49 | version = "1.0.0" 50 | authors = ["linghuyichong"] 51 | edition = "2021" 52 | license = "Apache-2.0" 53 | description = "RPC runtime API for use-rpc" 54 | readme = "README.md" 55 | 56 | [package.metadata.docs.rs] 57 | targets = ["x86_64-unknown-linux-gnu"] 58 | 59 | [dependencies] 60 | sp-api = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.18" } 61 | 62 | [features] 63 | default = ["std"] 64 | std = [ 65 | "sp-api/std", 66 | ] 67 | ``` 68 | 69 | 对应src/lib.rs的内容如下: 70 | ``` 71 | #![cfg_attr(not(feature = "std"), no_std)] 72 | 73 | sp_api::decl_runtime_apis! { 74 | pub trait MyRpcRuntimeApi { 75 | //在此处添加我们需要通过runtime配置的rpc的接口,一般来说和下面的(1.3节)的rpc接口是对应的 76 | fn rpc_method(v: u32) -> bool; 77 | } 78 | } 79 | 80 | ``` 81 | 82 | ## 1.3 定义rpc接口 83 | 接下来,我们在use-rpc下创建一个rpc的包,然后其Cargo.toml的内容如下: 84 | ``` 85 | [package] 86 | name = "pallet-rpc" 87 | version = "1.0.0" 88 | edition = "2021" 89 | 90 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 91 | [package.metadata.docs.rs] 92 | targets = ["x86_64-unknown-linux-gnu"] 93 | 94 | [dependencies] 95 | jsonrpc-core = "18.0.0" 96 | jsonrpc-core-client = "18.0.0" 97 | jsonrpc-derive = "18.0.0" 98 | 99 | sp-runtime = { default-features = false, version = "6.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.18" } 100 | sp-api = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.18" } 101 | sp-blockchain = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.18" } 102 | 103 | use-rpc-runtime-api = { version = "1.0.0", default-features = false, path = "../runtime-api" } 104 | 105 | [dev-dependencies] 106 | serde_json = "1.0.74" 107 | ``` 108 | 对应的src/lib.rs的内容如下: 109 | ``` 110 | use std::sync::Arc; 111 | 112 | pub use self::gen_client::Client as UseRpcClient; 113 | use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; 114 | use jsonrpc_derive::rpc; 115 | 116 | use sp_api::ProvideRuntimeApi; 117 | use sp_blockchain::HeaderBackend; 118 | use sp_runtime::{generic::BlockId, traits::Block as BlockT}; 119 | use use_rpc_runtime_api::MyRpcRuntimeApi; 120 | 121 | pub struct UseRpc { 122 | client: Arc, 123 | _marker: std::marker::PhantomData, 124 | } 125 | 126 | impl UseRpc { 127 | pub fn new(client: Arc) -> Self { 128 | Self { client, _marker: Default::default() } 129 | } 130 | } 131 | 132 | #[rpc] 133 | pub trait MyRpcApi { 134 | //在这里添加我们提供的rpc接口 135 | #[rpc(name = "my_rpc_method")] 136 | fn rpc_method(&self, v: u32, at: Option) -> Result; 137 | } 138 | 139 | impl MyRpcApi<::Hash> for UseRpc 140 | where 141 | Block: BlockT, 142 | C: Send + Sync + 'static, 143 | C: ProvideRuntimeApi, 144 | C: HeaderBackend, 145 | C::Api: MyRpcRuntimeApi, 146 | { 147 | fn rpc_method(&self, v: u32, 148 | at: Option<::Hash>, 149 | ) -> Result { 150 | let api = self.client.runtime_api(); 151 | let at = BlockId::hash(at.unwrap_or_else(|| 152 | self.client.info().best_hash 153 | )); 154 | 155 | let runtime_api_result = api.rpc_method(&at, v); 156 | runtime_api_result.map_err(|e| RpcError{ 157 | code: ErrorCode::ServerError(9876), 158 | message: "Something wrong".into(), 159 | data: Some(format!("{:?}", e).into()), 160 | }) 161 | } 162 | } 163 | 164 | ``` 165 | 166 | ## 1.4 为pallet实现rpc的功能函数 167 | 接下来我们为use-rpc添加具体的函数,在use-rpc/sec/lib.rs中添加如下: 168 | ``` 169 | impl Pallet { 170 | pub fn rpc_method(v: u32) -> bool { 171 | if v > 100 { 172 | true 173 | } else { 174 | false 175 | } 176 | } 177 | } 178 | ``` 179 | 180 | # 2 在runtime中添加rpc的实现 181 | 接下来我们为runtime实现MyRpcRuntimeApi。首先在runtime/Cargo.toml中添加依赖: 182 | ``` 183 | [dependencies] 184 | ... 185 | use-rpc-runtime-api = { version = "1.0.0", path = "../pallets/use-rpc/runtime-api" } 186 | ... 187 | 188 | 189 | [features] 190 | default = ["std"] 191 | std = [ 192 | ... 193 | "use-rpc-runtime-api/std", 194 | ] 195 | ``` 196 | 然后在runtime/src/lib.rs中添加: 197 | ``` 198 | impl_runtime_apis! { 199 | ... 200 | impl use_rpc_runtime_api::MyRpcRuntimeApi for Runtime { 201 | fn rpc_method(v: u32) -> bool { 202 | UseRpc::rpc_method(v) 203 | } 204 | } 205 | } 206 | ``` 207 | 208 | # 3 node中添加对应rpc 209 | 接下来需要将rpc添加到node中,首先添加依赖,在node/Cargo.toml中添加如下依赖: 210 | ``` 211 | pallet-rpc = { version = "1.0.0", path = "../pallets/use-rpc/rpc"} 212 | use-rpc-runtime-api = { version = "1.0.0", path = "../pallets/use-rpc/runtime-api" } 213 | ``` 214 | 215 | 接下来修改添加代码,在node/src/rpc.rs中添加如下: 216 | ``` 217 | pub fn create_full(deps: FullDeps) -> jsonrpc_core::IoHandler 218 | where 219 | C: ProvideRuntimeApi, 220 | C: HeaderBackend + HeaderMetadata + 'static, 221 | C: Send + Sync + 'static, 222 | C::Api: substrate_frame_rpc_system::AccountNonceApi, 223 | C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, 224 | // 添加此行 225 | C::Api: use_rpc_runtime_api::MyRpcRuntimeApi, 226 | C::Api: BlockBuilder, 227 | P: TransactionPool + 'static, 228 | { 229 | use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; 230 | use substrate_frame_rpc_system::{FullSystem, SystemApi}; 231 | // 添加这一行 232 | use pallet_rpc::{MyRpcApi, UseRpc}; 233 | 234 | let mut io = jsonrpc_core::IoHandler::default(); 235 | let FullDeps { client, pool, deny_unsafe } = deps; 236 | 237 | io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))); 238 | ... 239 | 240 | //添加这一行 241 | io.extend_with(MyRpcApi::to_delegate(UseRpc::new(client.clone()))); 242 | 243 | io 244 | } 245 | ``` 246 | 247 | # 3 测试 248 | 249 | 编译: 250 | ``` 251 | cargo build 252 | ``` 253 | 执行节点: 254 | ``` 255 | ./target/debug/node-template --dev --enable-offchain-indexing=true 256 | ``` 257 | 258 | 然后另起一个terminal,输入如下命令调用rpc接口: 259 | ``` 260 | curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{ 261 | "jsonrpc":"2.0", 262 | "id":1, 263 | "method":"my_rpc_method", 264 | "params": [1] 265 | }' 266 | 267 | ``` 268 | 将会看返回: 269 | ``` 270 | {"jsonrpc":"2.0","result":false,"id":1} 271 | 272 | ``` 273 | 274 | 275 | # 4 参考文档 276 | 277 | https://docs.substrate.io/v3/runtime/custom-rpcs/ 278 | 279 | # 5 完整源码地址 280 | 281 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-rpc 282 | 283 | -------------------------------------------------------------------------------- /8.9使用OCW提交签名交易.md: -------------------------------------------------------------------------------- 1 | **说明**:此部分的内容相对之前的类容复杂一点,有兴趣的小伙伴可以结合我b站的视频学习,效果会更好。 2 | 3 | 在区块链的应用中,常见的需求是在把数据上链之前需要去查询链下数据,解决的方式通常是使用预言机,这种方法虽然可行,但在安全性、可伸缩性和基础设施效率方面仍然存在一些缺陷。所以在substrate中为我们提供了一些链下的特性来解决这些需求,分别是: 4 | * 链下工作者(offchain worker); 5 | * 链下存储(offchain storage); 6 | * 链下索引(offchain index)。 7 | 8 | 链下工作者(offchain worker)可以提交签名交易、未签名交易、具有签名负载的未签名交易等功能,本节着重教大家在ocw中使用签名交易的情况。 9 | 10 | # 1 在pallet中添加ocw 11 | 这个pallet相对于我们以前实现的pallet要复杂一些,因此我们先贴出完整代码,然后再讲解。pallet中的代码如下: 12 | ``` 13 | #![cfg_attr(not(feature = "std"), no_std)] 14 | 15 | //========================= 16 | //需要关注的第一部分 17 | //========================= 18 | use sp_core::crypto::KeyTypeId; 19 | pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo"); 20 | pub mod crypto { 21 | use super::KEY_TYPE; 22 | use sp_runtime::app_crypto::{app_crypto, sr25519}; 23 | app_crypto!(sr25519, KEY_TYPE); 24 | } 25 | pub type AuthorityId = crypto::Public; 26 | //========================== 27 | 28 | pub use pallet::*; 29 | #[frame_support::pallet] 30 | pub mod pallet { 31 | use frame_support::pallet_prelude::*; 32 | use frame_system::pallet_prelude::*; 33 | 34 | use frame_system::offchain::{ 35 | AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer, 36 | }; 37 | 38 | #[pallet::pallet] 39 | #[pallet::generate_store(pub(super) trait Store)] 40 | pub struct Pallet(_); 41 | 42 | //========================= 43 | //需要关注的第二部分 44 | #[pallet::config] 45 | pub trait Config: frame_system::Config + CreateSignedTransaction> { 46 | type AuthorityId: AppCrypto; 47 | type Event: From> + IsType<::Event>; 48 | } 49 | //========================= 50 | 51 | #[pallet::storage] 52 | pub type SomeInfo = StorageMap<_, Blake2_128Concat, u64, u64, ValueQuery>; 53 | 54 | #[pallet::event] 55 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 56 | pub enum Event { 57 | SetSomeInfo(u64, u64), 58 | } 59 | 60 | #[pallet::error] 61 | pub enum Error { 62 | OffchainSignedTxError, 63 | NoAcctForSigning, 64 | } 65 | 66 | //========================= 67 | //需要关注的第三部分 68 | //========================= 69 | #[pallet::hooks] 70 | impl Hooks> for Pallet { 71 | fn offchain_worker(block_number: T::BlockNumber) { 72 | log::info!(target: "ocw", "before offchain_worker set storage: {:?}", block_number); 73 | let result = Self::offchain_signed_tx(block_number); 74 | log::info!(target: "ocw", "after offchain_worker set storage: {:?}", block_number); 75 | 76 | if let Err(e) = result { 77 | log::error!(target:"ocw", "offchain_worker error: {:?}", e); 78 | } 79 | } 80 | } 81 | 82 | #[pallet::call] 83 | impl Pallet { 84 | #[pallet::weight(0)] 85 | pub fn submit_something_signed( 86 | origin: OriginFor, 87 | number: u64, 88 | ) -> DispatchResultWithPostInfo { 89 | log::info!(target:"ocw", "11111 +++++++++++++++++++ "); 90 | ensure_signed(origin)?; 91 | 92 | let mut cnt: u64 = 0; 93 | if number > 0 { 94 | cnt = number; 95 | } 96 | 97 | log::info!(target:"ocw", "+++++++++++++++++++ offchain_worker set storage: {:?}, cnt: {:?}", number, cnt); 98 | SomeInfo::::insert(&number, cnt); 99 | 100 | Self::deposit_event(Event::SetSomeInfo(number, cnt)); 101 | Ok(().into()) 102 | } 103 | } 104 | 105 | impl Pallet { 106 | fn offchain_signed_tx(block_number: T::BlockNumber) -> Result<(), Error> { 107 | let signer = Signer::::any_account(); 108 | log::info!(target:"ocw", "+++++++++++++++++++, can sign: {:?}", signer.can_sign()); 109 | 110 | let number: u64 = block_number.try_into().unwrap_or(0); 111 | 112 | //需要关注的3.1 113 | let result = 114 | signer.send_signed_transaction(|_acct| Call::submit_something_signed { number }); 115 | 116 | if let Some((_acc, res)) = result { 117 | if res.is_err() { 118 | return Err(>::OffchainSignedTxError) 119 | } 120 | Ok(()) 121 | } else { 122 | Err(>::NoLocalAcctForSigning) 123 | } 124 | } 125 | } 126 | } 127 | 128 | ``` 129 | 对于pallet中的代码实现,我们主要需要实现三部分,分别解释如下: 130 | * 第一部分 131 | 132 | 这部分主要是用来在offchain worker提交签名交易时的签名的子模块。在实际的开发中,这部分基本上是固定的写法。在substrate中支持ed25519和sr25519,我们此处使用的是sr29915作为例子。其中KEY_TYPE是offchain worker签名时检索key使用的类型,由开发者指定,我们这里指定为“demo”。 133 | 134 | * 第二部分 135 | 136 | 第二部分主要是支持offchain提交签名交易的config配置,需要注意两点: 137 | 138 | 1、config需要继承 CreateSignedTransaction; 139 | 140 | 2、需要定义类型type AuthorityId: AppCrypto; 141 | 142 | 143 | * 第三部分 144 | 145 | 最后是第三部分。从上面的代码可以知道,调用offchain worker是在钩子函数中实现,这个比较好理解。这里重点要提一下的是代码注释标注3.1的部分,在offchain worker中调用交易的方式是这样,```let result = signer.send_signed_transaction(|_acct| Call::submit_something_signed { number });```. 146 | 147 | 148 | # 2 在runtime中添加相关代码 149 | 150 | 然后就是在runtime中添加相关代码,主要需要添加如下代码: 151 | ``` 152 | impl frame_system::offchain::CreateSignedTransaction for Runtime 153 | where 154 | Call: From, 155 | { 156 | fn create_transaction>( 157 | call: Call, 158 | public: ::Signer, 159 | account: AccountId, 160 | nonce: Index, 161 | ) -> Option<(Call, ::SignaturePayload)> { 162 | let tip = 0; 163 | // take the biggest period possible. 164 | let period = 1 << 7; 165 | // BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; 166 | 167 | let current_block = System::block_number() 168 | .saturated_into::() 169 | // The `System::block_number` is initialized with `n+1`, 170 | // so the actual block number is `n`. 171 | .saturating_sub(1); 172 | let era = Era::mortal(period, current_block); 173 | let extra = ( 174 | frame_system::CheckNonZeroSender::::new(), 175 | frame_system::CheckSpecVersion::::new(), 176 | frame_system::CheckTxVersion::::new(), 177 | frame_system::CheckGenesis::::new(), 178 | frame_system::CheckEra::::from(era), 179 | frame_system::CheckNonce::::from(nonce), 180 | frame_system::CheckWeight::::new(), 181 | pallet_transaction_payment::ChargeTransactionPayment::::from(tip), 182 | ); 183 | let raw_payload = SignedPayload::new(call, extra) 184 | .map_err(|e| { 185 | log::warn!("Unable to create signed payload: {:?}", e); 186 | }) 187 | .ok()?; 188 | let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; 189 | let address = ::Lookup::unlookup(account); 190 | let (call, extra, _) = raw_payload.deconstruct(); 191 | Some((call, (address, signature.into(), extra))) 192 | } 193 | } 194 | 195 | impl frame_system::offchain::SigningTypes for Runtime { 196 | type Public = ::Signer; 197 | type Signature = Signature; 198 | } 199 | 200 | impl frame_system::offchain::SendTransactionTypes for Runtime 201 | where 202 | Call: From, 203 | { 204 | type OverarchingCall = Call; 205 | type Extrinsic = UncheckedExtrinsic; 206 | } 207 | ``` 208 | 上面这部分代码基本为固定的写法,在大多数情况下直接这样使用就好了。下面还需要为runtime添加pallet_ocw_sigtx::Config: 209 | 210 | ``` 211 | pub struct MyAuthorityId; 212 | 213 | impl frame_system::offchain::AppCrypto<::Signer, Signature> for MyAuthorityId { 214 | type RuntimeAppPublic = pallet_ocw_sigtx::crypto::Public; 215 | type GenericSignature = sp_core::sr25519::Signature; 216 | type GenericPublic = sp_core::sr25519::Public; 217 | } 218 | 219 | impl pallet_ocw_sigtx::Config for Runtime { 220 | type AuthorityId = MyAuthorityId; 221 | type Event = Event; 222 | } 223 | 224 | construct_runtime!( 225 | pub enum Runtime where 226 | Block = Block, 227 | NodeBlock = opaque::Block, 228 | UncheckedExtrinsic = UncheckedExtrinsic 229 | { 230 | System: frame_system, 231 | ... 232 | 233 | OcwSigtx: pallet_ocw_sigtx, 234 | } 235 | ``` 236 | 237 | # 3 调试 238 | 接下来我们编译执行: 239 | ``` 240 | cargo build 241 | ./target/debug/node-template --dev 242 | ``` 243 | 244 | 节点起起来后,还需要进行insert key才能在offchainworker中提交交易。insert key有两种方法,第一种是使用polkadot-js-app,第二种是使用curl发送rpc请求,分别如下: 245 | 246 | * 在浏览器输入https://polkadot.js.org/apps,然后输入对应的key : 247 | ![insertkey1](./assets/insertkey1.JPG) 248 | 249 | (1) 1的位置输入key的类型,为demo; 250 | (2) 2的位置输入助记词; 251 | (3) 3的位置输入公钥。 252 | 253 | * 另外一种是使用curl命令,如下: 254 | ``` 255 | curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{ "jsonrpc":"2.0", "id":1, "method":"author_insertKey", "params": ["demo", "此处换成私钥", "此处换成公钥"] }' 256 | ``` 257 | 258 | 当insert key完成后,offchain worker就可以成功提交签名交易到链上。 259 | 260 | # 4 参考资料 261 | https://github.com/JoshOrndorff/recipes/blob/master/text/off-chain-workers/transactions.md 262 | 263 | # 5 完整代码地址 264 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/ocw-sigtx/src 265 | -------------------------------------------------------------------------------- /6编写简单的pallet.md: -------------------------------------------------------------------------------- 1 | # 编写简单的pallet 2 | ## 1 node-template的结构 3 | 我们下载node-template(地址:https://github.com/substrate-developer-hub/substrate-node-template), 然后进入到node-template查看目录结构: 4 | ``` 5 | ~/Source/learn/substrate-node-template$ tree -L 3 6 | . 7 | ├── Cargo.lock 8 | ├── Cargo.toml 9 | ├── docker-compose.yml 10 | ├── docs 11 | │   └── rust-setup.md 12 | ├── LICENSE 13 | ├── node 14 | │   ├── build.rs 15 | │   ├── Cargo.toml 16 | │   └── src 17 | │   ├── chain_spec.rs 18 | │   ├── cli.rs 19 | │   ├── command.rs 20 | │   ├── lib.rs 21 | │   ├── main.rs 22 | │   ├── rpc.rs 23 | │   └── service.rs 24 | ├── pallets 25 | │   └── template 26 | │   ├── Cargo.toml 27 | │   ├── README.md 28 | │   └── src 29 | ├── README.md 30 | ├── runtime 31 | │   ├── build.rs 32 | │   ├── Cargo.toml 33 | │   └── src 34 | │   └── lib.rs 35 | ├── rustfmt.toml 36 | ├── scripts 37 | │   ├── docker_run.sh 38 | │   └── init.sh 39 | └── shell.nix 40 | ``` 41 | 在上述的目录结构中,node目录中是链的一些基础功能的实现(或者说比较底层的实现,如网络、rpc,搭建链的最基础的code); pallet目录中放置的就是各个pallet,也就是业务相关的模块; runtime目录中可以简单理解为把所有pallet组合到一起,也就是业务相关的逻辑,这部分和pallet目录中是我们开发中经常要动到的部分,而node中则相对来说动的少一点。 42 | 43 | 如果用一张图来展示它们之间的关系的话,可能是这样(不太准确,但大体是这么个意思): 44 | ![node-template关系](assets/node-template关系.PNG) 45 | 46 | 当然,对于pallets来说,在runtime中使用的pallet,有些是我们自己开发的pallet,有些是substrate中已经开发好的pallet,甚至还有些是pallet是第三方开发的pallet。 47 | 48 | ## 2 编写pallet 49 | 下面我们就开始写一个简单的pallet。 50 | 51 | ### 2.1 编写pallet的一般格式 52 | 53 | 写pallet的基本格式如下: 54 | ``` 55 | // 1. Imports and Dependencies 56 | pub use pallet::*; 57 | #[frame_support::pallet] 58 | pub mod pallet { 59 | use frame_support::pallet_prelude::*; 60 | use frame_system::pallet_prelude::*; 61 | 62 | // 2. Declaration of the Pallet type 63 | // This is a placeholder to implement traits and methods. 64 | #[pallet::pallet] 65 | #[pallet::generate_store(pub(super) trait Store)] 66 | pub struct Pallet(_); 67 | 68 | // 3. Runtime Configuration Trait 69 | // All types and constants go here. 70 | // Use #[pallet::constant] and #[pallet::extra_constants] 71 | // to pass in values to metadata. 72 | #[pallet::config] 73 | pub trait Config: frame_system::Config { ... } 74 | 75 | // 4. Runtime Storage 76 | // Use to declare storage items. 77 | #[pallet::storage] 78 | #[pallet::getter(fn something)] 79 | pub MyStorage = StorageValue<_, u32>; 80 | 81 | // 5. Runtime Events 82 | // Can stringify event types to metadata. 83 | #[pallet::event] 84 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 85 | pub enum Event { ... } 86 | 87 | // 6. Hooks 88 | // Define some logic that should be executed 89 | // regularly in some context, for e.g. on_initialize. 90 | #[pallet::hooks] 91 | impl Hooks> for Pallet { ... } 92 | 93 | // 7. Extrinsics 94 | // Functions that are callable from outside the runtime. 95 | #[pallet::call] 96 | impl Pallet { ... } 97 | 98 | } 99 | ``` 100 | 在我们开始写一个pallet的时候,首先先把这个模板贴到编辑器里面,然后再针对我们具体的需求进行修改。所以从这里可以看出,一个pallet,如果所有功能都包括的话,基本上分为这几大部分(对应上面代码注释中的1-7): 101 | ``` 102 | 1. 依赖; 103 | 2. pallet类型声明; 104 | 3. config trait; 105 | 4. 定义要使用的链上存储; 106 | 5. 事件; 107 | 6. 钩子函数; 108 | 7. 交易调用函数; 109 | ``` 110 | 1和2基本上是固定的写法,而对于后面的3-7部分,则是根据实际需要写或者不写。关于模板中每部分的解释,可以参考[文档](https://docs.substrate.io/v3/runtime/frame/#pallets). 111 | 112 | ### 2.2 编写pallet 113 | 接下来我们将编写一个simple-pallet. 114 | 115 | #### 2.2.1 simple-pallet功能介绍 116 | simple-pallet是一个存证的pallet,简单说就是提供一个存取一段hash到链上的功能,和从链上读取的功能。 117 | 118 | #### 2.2.2 创建目录 119 | 进去到我们前面下载的substrate-node-template中,进入到目录pallets中,我们可以创建我们自己的simple-pallet(一般都是在template基础上进行修改): 120 | ``` 121 | #先进入到substrate-node-template目录,然后执行如下 122 | cd pallets 123 | cp template/ simple-pallet -rf 124 | cd simple-pallet/src/ 125 | rm benchmarking.rs mock.rs tests.rs 126 | ``` 127 | 接下来修改Cargo.toml,打开substrate-node-template/pallets/simple-pallet目录下的Cargo.toml文件,然后进行修改,主要修改内容如下: 128 | ``` 129 | [package] 130 | name = "pallet-simple-pallet" #需要修改成自己的名字,这里我们叫做pallet-simple-pallet 131 | ... 132 | description = 修改成自己的 133 | authors = 修改成自己的 134 | ... 135 | repository = "https://github.com/substrate-developer-hub/substrate-node-template/" 136 | ``` 137 | 对于这个文件中的其它的依赖我们可以暂时先不修改,等代码写完可以再回来删除多余的依赖。 138 | 139 | #### 2.2.3 编写代码 140 | 141 | 删除substrate-node-template/pallets/simple-pallet/src/lib.rs中的代码,然后将上面2.1节中pallet的一般格式的代码拷贝到这个文件中。接下来我们开始写代码,首先对于注释中1和2的部分,我们开始不用修改,对于注释6的部分我们需要删除掉(这个例子中使用不到)。 142 | 143 | 那么接下来,我们需要修改的就是里面的3、4、5、7的部分,其实对于很多其它的pallet来说,主要也只是修改这几部分。 144 | 145 | 首先,我们将注释3所在部分confit修改成如下: 146 | ``` 147 | #[pallet::config] 148 | pub trait Config: frame_system::Config { 149 | type Event: From> + IsType<::Event>; 150 | } 151 | ``` 152 | 这里其实就是定义了一个关联类型,这个关联类型需要满足后面的类型约束(From> + IsType<::Event>)。至于为什么是这样的约束,我们其实可以从字面意思进行理解,一个是可以转换成Event,另外一个就是它是frame_system::Config的Event类型。**对于大部分pallet来说, 如果需要使用到Event,那么都需要在这个Config中进行定义,定义的方式基本一样.** 153 | 154 | 接下来,我们修改注释4的部分如下: 155 | ``` 156 | #[pallet::storage] 157 | pub type Proofs = 158 | StorageMap<_, Blake2_128Concat, u32, u128>; 159 | ``` 160 | 关于substrate中的存储,更详细的资料可以参考[文档](https://docs.substrate.io/v3/runtime/storage/)。这里我们简单解释一下,这部分就是在链上定义了一个存储,是一个key-value方式的存储结构,用于存储我们后面要使用的存证,key是u32格式,value也是u128格式。 161 | 162 | 163 | 再接下来,我们修改注释5的部分如下: 164 | ``` 165 | #[pallet::event] 166 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 167 | pub enum Event { 168 | ClaimCreated(u32, u128), 169 | } 170 | ``` 171 | 这里的Event是用来在我们具体的函数中做完动作之后发出的,一般用来通知前端做一些处理。这里我们在Event中定义了一个事件,就是创建存证。 172 | 173 | 最后,我们修改注释7的部分来实现前面说的创建存证的逻辑,如下: 174 | ``` 175 | #[pallet::call] 176 | impl Pallet { 177 | #[pallet::weight(0)] 178 | pub fn create_claim(origin: OriginFor, id: u32, claim: u128) -> DispatchResultWithPostInfo { 179 | ensure_signed(origin)?; 180 | 181 | Proofs::::insert( 182 | &id, 183 | &claim, 184 | ); 185 | 186 | Self::deposit_event(Event::ClaimCreated(id, claim)); 187 | 188 | Ok(().into()) 189 | } 190 | } 191 | ``` 192 | 193 | 至此,我们pallet部分的代码基本上就写完了。 194 | 195 | 196 | ## 3 将pallet添加到runtime中 197 | 198 | 如果用开发一个程序来类别的话,上面写完我们的pallet就类似于我们开发好了一个库(或者说模块),但是这个库还没有真正的用在我们的程序中(链)。接下来就是要在链上使用,就要将pallet添加到runtime中。添加的过程也比较简单,这里我们分两步进行,分别是修改Cargo.toml中和runtime/src/lib.rs中。 199 | 200 | ### 3.1 修改Cargo.toml 201 | 要在runtime中使用我们上面编写的pallet,需要修改substrate-node-template/runtime/Cargo.toml,在其中添加依赖如下: 202 | ``` 203 | ... 204 | [dependencies] 205 | ... 206 | pallet-simple-pallet = { version = "4.0.0-dev", default-features = false, path = "../pallets/simple-pallet" } #我们上面编写的pallet 207 | ... 208 | 209 | [features] 210 | default = ["std"] 211 | std = [ 212 | ... 213 | "pallet-template/std", 214 | "pallet-simple-pallet/std", #我们上面编写的pallet 215 | ... 216 | ] 217 | ``` 218 | 219 | ### 3.2 修改runtime/src/lib.rs 220 | 221 | 在runtime/src/lib.rs中来使用pallet。首先我们需要添加pallet的配置,其实就是指定pallet中Congfig中的关联类型,所以在substrate-node-template/runtime/src/lib.rs中添加如下代码: 222 | ``` 223 | impl pallet_simple_pallet::Config for Runtime { 224 | type Event = Event; //我们上面的定义中只有一个关联类型Event,在此处进行指定,等好右边的Event实际上是frame system中的Event,此处不需要深究, 225 | //可以理解为在runtime中已经定义好的一种具体的类型。 226 | } 227 | ``` 228 | 接下来就是把simple_pallet加入到runtime中,修改如下代码: 229 | ``` 230 | construct_runtime!( 231 | pub enum Runtime where 232 | Block = Block, 233 | NodeBlock = opaque::Block, 234 | UncheckedExtrinsic = UncheckedExtrinsic 235 | { 236 | System: frame_system, 237 | RandomnessCollectiveFlip: pallet_randomness_collective_flip, 238 | Timestamp: pallet_timestamp, 239 | Aura: pallet_aura, 240 | Grandpa: pallet_grandpa, 241 | Balances: pallet_balances, 242 | TransactionPayment: pallet_transaction_payment, 243 | Sudo: pallet_sudo, 244 | // Include the custom logic from the pallet-template in the runtime. 245 | TemplateModule: pallet_template, 246 | SimplePallet: pallet_simple_pallet, //添加这一行,这里可以看出,实际上我们前面实现的simple-pallet可以理解为一种类型, 247 | //然后这里在runtime中定义了一个变量,该变量是这个pallet_simple_pallet类型 248 | } 249 | ); 250 | ``` 251 | 至此,我们就将pallet加入到我们的runtime中了。 252 | 253 | ### 3.3 编译运行 254 | 接下来,我们可以进行编译运行我们的链了。回到substrate-node-template目录,运行如下命令编译: 255 | ``` 256 | cargo build 257 | ``` 258 | 运行如下命令启动节点: 259 | ``` 260 | ./target/debug/node-template --tmp --dev 261 | ``` 262 | 263 | ## 4 调试使用pallet中的功能 264 | 此处我们使用polkadot-js-apps和我们刚才运行的节点进行交互。步骤如下: 265 | ``` 266 | 1、在浏览器中输入https://polkadot.js.org/apps; 267 | 2、点击左上角会展开; 268 | 3、在展开的菜单中点击DEVELOPMENT; 269 | 4、点击Local Node; 270 | 5、点击switch。 271 | ``` 272 | 273 | 接下来我们创建存证: 274 | ``` 275 | 1、选择Developer->Extrinsics->Submission; 276 | 2、然后使用Alice账户,选择simplePallet,选择createClaim,输入对应的参数,然后点击右下角的提交即完成了存在的创建。 277 | ``` 278 | 上述过程如下图: 279 | ![创建存证](./assets/创建存证.PNG) 280 | 281 | 最后我们可以来读取刚才创建的存证: 282 | ``` 283 | 1、选择Developer->Chain State; 284 | 2、选择simplePallet,选择proofs,然后点击提交即可。 285 | ``` 286 | 上述过程如下图: 287 | ![查看存证](./assets/查询存证.PNG) 288 | 289 | 290 | ## 5 小结 291 | 学到这里,我们基本上就走完了整个pallet开发的流程,你已经可以开发一个简单的pallet了。是不是并没有想想中的那么难? 292 | 后续我们再学习学习其它相关的知识,相信你很快就能完全掌握pallet开发了。 293 | 294 | ## 6 参考文档 295 | https://docs.substrate.io/v3/runtime/frame/#pallets 296 | 297 | ## 7 完整源码地址 298 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/simple-pallet 299 | -------------------------------------------------------------------------------- /10编写benchmarking.md: -------------------------------------------------------------------------------- 1 | # 编写benchmarking1:编写简单的benchmarking 2 | 3 | 编写benchmarking分两种情况,如下: 4 | 5 | * 对函数进行性能测试时需要的构造条件不会涉及到本pallet以外的其它pallet; 6 | * 在对函数进行性能测试时需要先使用其它的pallet构造测试的先决条件。 7 | 8 | 第一种情况相对来说比较简单,这个也比较好找到例子。第二种情况则比较复杂,写起来也比较麻烦。不过在我们的开发中,大部分都是第一种情况。 9 | 10 | 针对这两种情况,我们分别来讲解如何编写代码。本节,主要讲第一种情况。 11 | 12 | # 1 编写pallet业务代码 13 | 我们创建一个pallet,名字叫做use-benchmarking,对应的use-benchmarking/src/lib.rs的代码如下: 14 | ``` 15 | #![cfg_attr(not(feature = "std"), no_std)] 16 | 17 | pub use pallet::*; 18 | #[frame_support::pallet] 19 | pub mod pallet { 20 | use codec::Codec; 21 | use frame_support::{ 22 | pallet_prelude::*, sp_runtime::traits::AtLeast32BitUnsigned, sp_std::fmt::Debug, 23 | }; 24 | use frame_system::pallet_prelude::*; 25 | use scale_info::prelude::vec::Vec; 26 | 27 | use sp_io::hashing::{blake2_128, twox_128}; 28 | 29 | #[pallet::pallet] 30 | #[pallet::generate_store(pub(super) trait Store)] 31 | pub struct Pallet(_); 32 | 33 | // 3. Runtime Configuration Trait 34 | #[pallet::config] 35 | pub trait Config: frame_system::Config { 36 | type Event: From> + IsType<::Event>; 37 | 38 | //声明StudentNumber类型 39 | type StudentNumberType: Member 40 | + Parameter 41 | + AtLeast32BitUnsigned 42 | + Codec 43 | + Copy 44 | + Debug 45 | + Default 46 | + MaxEncodedLen 47 | + MaybeSerializeDeserialize; 48 | 49 | //声明StudentName类型 50 | type StudentNameType: Parameter 51 | + Member 52 | + AtLeast32BitUnsigned 53 | + Codec 54 | + Default 55 | + From 56 | + Into 57 | + Copy 58 | + MaxEncodedLen 59 | + MaybeSerializeDeserialize 60 | + Debug; 61 | } 62 | 63 | // 4. Runtime Storage 64 | // 用storageMap存储学生信息,(key, value)分别对应的是学号和姓名. 65 | #[pallet::storage] 66 | #[pallet::getter(fn students_info)] 67 | pub type StudentsInfo = 68 | StorageMap<_, Blake2_128Concat, T::StudentNumberType, T::StudentNameType, ValueQuery>; 69 | 70 | // 5. Runtime Events 71 | // Can stringify event types to metadata. 72 | #[pallet::event] 73 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 74 | pub enum Event { 75 | SetStudentInfo(T::StudentNumberType, T::StudentNameType), 76 | } 77 | 78 | // 8. Runtime Errors 79 | #[pallet::error] 80 | pub enum Error { 81 | // 相同学号的只允许设置一次名字 82 | SetStudentsInfoDuplicate, 83 | } 84 | 85 | // 7. Extrinsics 86 | // Functions that are callable from outside the runtime. 87 | #[pallet::call] 88 | impl Pallet { 89 | #[pallet::weight(100)] 90 | pub fn set_student_info( 91 | origin: OriginFor, 92 | student_number: T::StudentNumberType, 93 | student_name: T::StudentNameType, 94 | ) -> DispatchResultWithPostInfo { 95 | ensure_signed(origin)?; 96 | 97 | if StudentsInfo::::contains_key(student_number) { 98 | return Err(Error::::SetStudentsInfoDuplicate.into()) 99 | } 100 | 101 | StudentsInfo::::insert(&student_number, &student_name); 102 | Self::deposit_event(Event::SetStudentInfo(student_number, student_name)); 103 | 104 | Self::generate_key(); 105 | 106 | Ok(().into()) 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | # 2 编写mock代码 113 | 编写mock,编写benchmarking时需要的mock和之前在use-tests中编写的差不多,我们这里直接贴代码,如下: 114 | ``` 115 | use crate as pallet_template; 116 | use frame_support::traits::{ConstU16, ConstU64}; 117 | use frame_system as system; 118 | use sp_core::H256; 119 | use sp_runtime::{ 120 | testing::Header, 121 | traits::{BlakeTwo256, IdentityLookup}, 122 | }; 123 | 124 | type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; 125 | type Block = frame_system::mocking::MockBlock; 126 | 127 | // Configure a mock runtime to test the pallet. 128 | frame_support::construct_runtime!( 129 | pub enum Test where 130 | Block = Block, 131 | NodeBlock = Block, 132 | UncheckedExtrinsic = UncheckedExtrinsic, 133 | { 134 | System: frame_system::{Pallet, Call, Config, Storage, Event}, 135 | TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, 136 | } 137 | ); 138 | 139 | impl system::Config for Test { 140 | type BaseCallFilter = frame_support::traits::Everything; 141 | type BlockWeights = (); 142 | type BlockLength = (); 143 | type DbWeight = (); 144 | type Origin = Origin; 145 | type Call = Call; 146 | type Index = u64; 147 | type BlockNumber = u64; 148 | type Hash = H256; 149 | type Hashing = BlakeTwo256; 150 | type AccountId = u64; 151 | type Lookup = IdentityLookup; 152 | type Header = Header; 153 | type Event = Event; 154 | type BlockHashCount = ConstU64<250>; 155 | type Version = (); 156 | type PalletInfo = PalletInfo; 157 | type AccountData = (); 158 | type OnNewAccount = (); 159 | type OnKilledAccount = (); 160 | type SystemWeightInfo = (); 161 | type SS58Prefix = ConstU16<42>; 162 | type OnSetCode = (); 163 | type MaxConsumers = frame_support::traits::ConstU32<16>; 164 | } 165 | 166 | impl pallet_template::Config for Test { 167 | type Event = Event; 168 | } 169 | 170 | // Build genesis storage according to the mock runtime. 171 | pub fn new_test_ext() -> sp_io::TestExternalities { 172 | system::GenesisConfig::default().build_storage::().unwrap().into() 173 | } 174 | ``` 175 | 176 | 这里需要注意的是,编写好mock后,我们对其导出,也就是在use-benchmarking/src/lib.rs添加如下代码: 177 | ``` 178 | #[cfg(test)] 179 | mod mock; 180 | ``` 181 | 182 | # 3 编写benchmarking 183 | 184 | 编写benchmarking的目的主要是为调度函数生成对应的权重计算函数,对应到本例子中就主要是use-benchmarking的set_student_info函数生成对应的权重计算函数。我们首先在use-benchmarking/src目录下创建一个文件,名为benchmarking.rs,编写内容如下: 185 | ``` 186 | use super::*; 187 | 188 | #[allow(unused)] 189 | use crate::Pallet as UseBenchmarkingDemo; 190 | use frame_benchmarking::{benchmarks, whitelisted_caller}; 191 | use frame_system::RawOrigin; 192 | 193 | benchmarks! { 194 | set_student_info { //1、准备条件 195 | let s in 0 .. 100; 196 | let caller: T::AccountId = whitelisted_caller(); 197 | }:{ //2、调用调度函数 198 | let _ = UseBenchmarkingDemo::::set_student_info(RawOrigin::Signed(caller).into(), s.into(), Default::default()); 199 | } 200 | verify {//3、进行验证 201 | assert_eq!(>::get::<::StudentNumberType>(s.into()), Default::default()); 202 | } 203 | 204 | // 使用mock中的new_test_ext 205 | impl_benchmark_test_suite!(UseBenchmarkingDemo, crate::mock::new_test_ext(), crate::mock::Test); 206 | } 207 | ``` 208 | 209 | 下面我们就来逐步讲解。 210 | 211 | 编写benchmark以宏benchmarks!包含,里面的内容主要分三部分,做的事情分别是准备条件、调用调度函数、对执行结果验证。对于第一步,其实就是对我们调度函数中的变量进行赋值,详情可以参考https://docs.substrate.io/reference/how-to-guides/weights/add-benchmarks/ 212 | 213 | # 4 添加到runtime中 214 | 首先,还是需要把use-benchmarking这个pallet加到runtime的依赖中,所以在runtime/Cargo.toml中添加如下: 215 | ``` 216 | [dependencies] 217 | ... 218 | pallet-use-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../pallets/use-benchmarking" } 219 | ... 220 | 221 | [features] 222 | default = ["std"] 223 | std = [ 224 | ... 225 | "pallet-use-benchmarking/std", 226 | ... 227 | ] 228 | 229 | runtime-benchmarks = [ 230 | ... 231 | "pallet-use-benchmarking/runtime-benchmarks", 232 | ... 233 | ] 234 | 235 | ``` 236 | 237 | 接下来,则是在runtime/src/lib.rs的代码中添加,我们先将use-benchmarking添加到runtime中,添加如下代码: 238 | ``` 239 | impl pallet_use_benchmarking::Config for Runtime { 240 | type Event = Event; 241 | type StudentNumberType = u32; 242 | type StudentNameType = u128; 243 | } 244 | 245 | construct_runtime!( 246 | pub enum Runtime where 247 | Block = Block, 248 | NodeBlock = opaque::Block, 249 | UncheckedExtrinsic = UncheckedExtrinsic 250 | { 251 | System: frame_system, 252 | RandomnessCollectiveFlip: pallet_randomness_collective_flip, 253 | Timestamp: pallet_timestamp, 254 | Aura: pallet_aura, 255 | Grandpa: pallet_grandpa, 256 | Balances: pallet_balances, 257 | TransactionPayment: pallet_transaction_payment, 258 | Sudo: pallet_sudo, 259 | ... 260 | //添加下面这行 261 | UseBenchmarkingDemo: pallet_use_benchmarking, 262 | } 263 | ); 264 | ``` 265 | 然后就是将use-benchmarking pallet添加到对应的benchmark宏中,需要添加的代码如下: 266 | ``` 267 | #[cfg(feature = "runtime-benchmarks")] 268 | mod benches { 269 | define_benchmarks!( 270 | [frame_benchmarking, BaselineBench::] 271 | [frame_system, SystemBench::] 272 | [pallet_balances, Balances] 273 | [pallet_timestamp, Timestamp] 274 | [pallet_template, TemplateModule] 275 | //这行为添加的代码 276 | [pallet_use_benchmarking, UseBenchmarkingDemo] 277 | ); 278 | } 279 | ``` 280 | 281 | # 5 编译&生成weights.rs文件 282 | 首先时编译,编译的命令需要带上```--features runtime-benchmarks```, 完整的编译命令如下: 283 | ``` 284 | cargo build --features runtime-benchmarks 285 | ``` 286 | 287 | 然后就是生成对应的weights文件,生成之前,我们需要从substrate的repo中拷贝模板放到我们的substrate-node-template目录下: 288 | ``` 289 | mkdir .maintain 290 | cd .maintain 291 | cp substrate/.maintain/frame-weight-template.hbs . #此处的substrate同官方的repo仓库 292 | ``` 293 | 294 | 然后就是生成weights文件的命令,如下: 295 | ``` 296 | ./target/debug/node-template benchmark --chain dev --execution wasm --wasm-execution compiled --pallet pallet_use_benchmarking --extrinsic "*" --steps 20 --repeat 10 --output ./pallets/use-benchmarking/src/weights.rs --template ./.maintain/frame-weight-template.hbs 297 | ``` 298 | 299 | 执行完后就会在```./pallets/use-benchmarking/src/```目录下生成对应的weights.rs文件。 300 | 301 | 至此,我们生成了weights.rs文件,但是这仅仅只是生成,还需要把生成的权重函数用到pallet中,演示demo对应的修改为这个提交(https://github.com/anonymousGiga/learn-substrate-easy-source/commit/8a4131c5f18c3bb769618265dddb41652d89a70b) 302 | 303 | # 6 将生成的权重函数应用到pallet中 304 | 接下来我们将权重函数添加到pallet中,演示demo中对应的提交为(https://github.com/anonymousGiga/learn-substrate-easy-source/commit/ce698a63939a8ad1e004c5ced2a1c9222178fb93)。 305 | 306 | 首先,我们需要在pallets/use-benchmarking/Cargo.toml中添加如下: 307 | ``` 308 | ... 309 | [dependencies] 310 | ... 311 | //添加下面这行 312 | sp-std = { default-features = false, version = "4.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.18" } 313 | 314 | [features] 315 | default = ["std"] 316 | std = [ 317 | ... 318 | //添加下面这行 319 | "sp-std/std", 320 | ] 321 | ``` 322 | 323 | 接着我们需要在pallets/use-benchmarking/src/lib.rs添加: 324 | ``` 325 | pub mod weights; 326 | pub use weights::WeightInfo; 327 | ``` 328 | 329 | 然后需要在mod内部添加引入: 330 | ``` 331 | #[frame_support::pallet] 332 | pub mod pallet { 333 | ... 334 | //添加这行 335 | use crate::WeightInfo; 336 | ... 337 | } 338 | ``` 339 | 340 | 另外,我们还需要在pallets/use-benchmarking/src/lib.rs中的Config中添加WeightInfo类型,如下: 341 | ``` 342 | pub trait Config: frame_system::Config { 343 | ... 344 | // 添加这里 345 | type WeightInfo: WeightInfo; 346 | } 347 | ``` 348 | 然后我们需要把调度函数```set_student_info```上方的```#[pallet::weight(100)]```修改成```#[pallet::weight(::WeightInfo::set_student_info((*student_number).into() ))]```。 349 | 350 | 最后,我们还需要回到runtime/src/lib.rs文件,修改pallet_use_benchmarking的配置,如下: 351 | ``` 352 | impl pallet_use_benchmarking::Config for Runtime { 353 | type Event = Event; 354 | type StudentNumberType = u32; 355 | type StudentNameType = u128; 356 | //添加这一行 357 | type WeightInfo = pallet_use_benchmarking::weights::SubstrateWeight; 358 | } 359 | ``` 360 | 361 | 重新编译,pallet的对应的权重就会在新的执行文件中生效,编译命令如下: 362 | ``` 363 | cargo build 364 | ``` 365 | 366 | 367 | # 7 参考文档 368 | https://docs.substrate.io/v3/runtime/benchmarking/ 369 | 370 | # 8 完整源码参考 371 | 372 | https://github.com/anonymousGiga/learn-substrate-easy-source/tree/main/substrate-node-template/pallets/use-benchmarking 373 | --------------------------------------------------------------------------------