Substrate 总览(3) – 入门参考
Substrate设计总览入门介绍系列第三篇
已经鸽了好久了,现在开始补文章。 本文承接Substrate设计总览入门介绍系列第三篇,介绍Substrate的入门参考。三篇文章为: * [Substrate的总体设计](https://learnblockchain.cn/2019/03/02/substrate-project/ "Substrate的总体设计") * [Substrate的项目结构](https://learnblockchain.cn/2019/03/10/substrate-structure/ "Substrate的项目结构") * [入门参考](https://learnblockchain.cn/2019/11/05/substrate-start/ "入门参考") 本文重点从运行Substrate的node节点介绍如何入门。 **目前Substrate的文档十分缺乏,本文的介绍相当于是Substrate的一种文档。** 自本系列第一篇文章至本文半年的时间,Substrate虽然整体框架上变动不大,但是其中很多细节已经有了很大的变化,因此对于入学者来说需要对照起前两篇文章介绍时的substrate与当前的substrate,否则在一些概念上无法联系起来。 对于新的Substrate,本文定master分支上的提交`ec7c6cf1779b88e75137ef6f6f7bf67ecd0f75a5`(11月2日),后文简称为`new-S` 对于前两篇文章提到的Substrate,本文定master分支上的提交`d6eba14a55be26e1a4e24a882ff574aa0190aff6` (5月22日),后文简称为`old-S` ## 运行node 根据(二)中介绍的项目结构,Substrate自身已经提供了一个node节点用于运行Substrate。也就是说node就是使用Substrate框架的模板。 **因此若使用Substrate开发区块链,就是仿照node进行项目组织** ### 准备 因为Substrate在第一次启动的时候需要加载一些环境,而且若加载初始环境时间过久会导致无法出块,因此**建议大家选用cpu性能好一些的电脑,或者根据后文更改出块间隔时间**。 另一方面若能够灵活使用gdb或者IDE的debug工具将会对学习substrate节省相当大量的时间,因此建议大家首先**想办法配置并熟悉如何debug一个rust项目,再继续后续文章。** Substrate项目当前已经十分复杂了,很多情况下仅仅看可能无法在脑中记住代码逻辑,因此推荐大家采用一些IDE进行辅助。这里推荐IntelliJ系列的IDE: 1. IntelliJ IDEA ,安装上rust插件后即可使用。但是无法与gdb进行联通,进行debug。不过这个有社区版,免费。 2. Clion,安装上rust插件后即可使用,可以使用gdb进行联通调试,不过这个收费。 个人推荐Clion。 至于vscode加上rust插件和rls,个人不是很推荐,因为substrate太庞大了,经常让rls崩溃。。而IntelliJ 这边的rust插件的智能化是重新写的,没有用rls。 对于Rust而言,需要事先**熟悉好“关联属性”**相关的部分。 ### 运行node 这里建议不用参照substrate的README进行操作,而是自己编译substrate的源码进行调试。 ### new-S 对于`new-S`而言,首先**参照README中的6.1章节,根据自己的操作系统配置好环境**。 切换到Substrate的根目录下,执行以下命令: ``` # 建议首先设置下面这个环境变量(到当前shell环境,到.bashrc 等等,总之就是在执行cargo build/run 的时候,上下文要有这个环境变量) export WASM_BUILD_TYPE=release cargo run -- --dev -d .sub --execution=NativeElseWasm # 若电脑性能不够,建议编译成release,否则无法出块。 # cargo run --release -- --dev -d .sub --execution=NativeElseWasm ``` 即可直接运行node节点。这里`--dev`是指定为dev模式,并配置好默认的Alice私钥运行单节点。 命令行参数说明如下: * `--dev`差不多等价为`---chain=dev --validator --key=//Alice`,再加上其他的一些rpc,telemtry相关的。这里只是强调会以Alice的私钥启动验证者模式。 * `-d .sub` 用于指定该链生成数据(区块,状态等)的数据根目录,我一般习惯用`.sub`,大家可以设置成自己希望的路径。 * `--execution=Native`将会指定执行方式为`NativeElseWasm`,因为只有在Native环境下才可下断点调试Runtime,否则默认以wasm执行是无法调试Runtime的。 ``` 2019-11-03 16:32:26 Running in --dev mode, RPC CORS has been disabled. 2019-11-03 16:32:26 Substrate Node 2019-11-03 16:32:26 version 2.0.0-ec7c6cf17-x86_64-linux-gnu 2019-11-03 16:32:26 by Parity Technologies, 2017-2019 2019-11-03 16:32:26 Chain specification: Development 2019-11-03 16:32:26 Node name: delightful-planes-8797 2019-11-03 16:32:26 Roles: AUTHORITY 2019-11-03 16:32:29 Initializing Genesis block/state (state: 0x0ec9…c1dd, header-hash: 0x8762…41c3) 2019-11-03 16:32:29 Loading GRANDPA authority set from genesis on what appears to be first startup. 2019-11-03 16:32:40 Loaded block-time = BabeConfiguration { slot_duration: 3000, epoch_length: 200, c: (1, 4), genesis_authorities: [(Public(d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d (5GrwvaEF...)), 1)], randomness: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], secondary_slots: true } seconds from genesis on first-launch 2019-11-03 16:32:40 Creating empty BABE epoch changes on what appears to be first startup. 2019-11-03 16:32:43 Highest known block at #0 2019-11-03 16:32:43 Using default protocol ID "sup" because none is configured in the chain specs 2019-11-03 16:32:43 Local node identity is: QmRX26NAABDXQHGxVESibyrAnciBQdpgwdX2ZRngh5vCUt 2019-11-03 16:32:43 Starting BABE Authorship worker 2019-11-03 16:32:45 Starting consensus session on top of parent 0x8762ac86a1f1723f4b6659c2f5b0c848c1f1ec3f65f1fb6ef37e903a72ec41c3 ``` 其他命令具体执行`--help`看描述就好。 ### old-S 这里同样不建议参照这个版本下的README,而是按照以下操作: 首先参照该版本README的6.1章节配置好环境,注意这个版本的下的windows似乎支持还不完善(不确定)。 然后切换以下目录执行: ``` cd node/runtime/wasm ./build.sh ``` 这个行为会编译node的节点的Runtime的WASM文件于路径下 ``` <substrate>/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm ``` 简单浏览`build.sh`文件可知,实际上这个步骤是把Runtime编译了一份`--target=wasm32-unknown-unknown`目标的wasm文件,然后使用`wasm-gc`对生成的wasm进行压缩。 注意这个文件后续将要在其他文件中获取到(主要是genesis),编译进入node的二进制中。 然后切换回substrate根目录上,执行 ``` cargo run -- --dev -d .sub --block-construction-execution=NativeElseWasm --other-execution=NativeElseWasm ``` 即可和`new-S`一样运行起以Alice为私钥启动的单验证者节点。 这里提一下,在这个版本的substrate里面存在一个私钥推断的bug,如果一定要严格按照Alice私钥生成规则生成的话(如涉及到subkey)建议切换到提交`498452517f95d399ed1b422ea5097d2aa984fd02`,或者把这个提交cherry pick过来,否则只要把Alice私钥导出来使用即可,其他部分不影响。 和`new-S`相比`execution`部分在`old-S`中还没有统一成一个指令,而另两个`execution`不重要,因此只需要指定`--block-construction-execution`和`--other-execution`是native即可。 运行起来后可看到 ``` 2019-11-03 21:37:04 Running in --dev mode, RPC CORS has been disabled. 2019-11-03 21:37:04 Substrate Node 2019-11-03 21:37:04 version 2.0.0-d6eba14a5-x86_64-linux-gnu 2019-11-03 21:37:04 by Parity Technologies, 2017-2019 2019-11-03 21:37:04 Chain specification: Development 2019-11-03 21:37:04 Node name: able-thumb-3759 2019-11-03 21:37:04 Roles: AUTHORITY 2019-11-03 21:37:07 Initializing Genesis block/state (state: 0xd4a3…d6cd, header-hash: 0x9dd9…1463) 2019-11-03 21:37:07 Loaded block-time = 4 seconds from genesis on first-launch 2019-11-03 21:37:07 Loading GRANDPA authority set from genesis on what appears to be first startup. 2019-11-03 21:37:08 Highest known block at #0 2019-11-03 21:37:08 Using default protocol ID "sup" because none is configured in the chain specs 2019-11-03 21:37:08 Local node identity is: QmbeniX5UtetYpk8i6NdLvuvtWhnymz6R5tCdK91pgaL4d 2019-11-03 21:37:08 Libp2p => Random Kademlia query has yielded empty results 2019-11-03 21:37:08 Listening for new connections on 127.0.0.1:9944. 2019-11-03 21:37:08 Using authority key 5CLFVjc5C8A5vwwuHaYgxHDsamotvEsaWE7D7jJR7SsDbeWm 2019-11-03 21:37:08 Running Grandpa session as Authority 5CLFVjc5C8A5vwwuHaYgxHDsamotvEsaWE7D7jJR7SsDbeWm 2019-11-03 21:37:13 Idle (0 peers), best: #0 (0x9dd9…1463), finalized #0 (0x9dd9…1463), ⬇ 0 ⬆ 0 2019-11-03 21:37:13 Prepared block for proposing at 1 [hash: 0x8df244b6e236c6b224a2b0399151455ac34a5a6498f47cae6ea2b9282d9abf45; parent_hash: 0x9dd9…1463; extrinsics: [0x6a28…491a]] 2019-11-03 21:37:13 Pre-sealed block for proposal at 1. Hash now 0x6c70e1e55e2f44962b3ddbac078c7c7d7dbd3a0c108a9af011585de55580041a, previously 0x8df244b6e236c6b224a2b0399151455ac34a5a6498f47cae6ea2b9282d9abf45. ``` 和`new-S`一样,当看到`Pre-sealed block for proposal`时,表示已经在正常出块了。 ### new-S和old-S 启动变化的原因 从前文可以看出,`old-S`需要手动执行`build.sh`再编译节点,而`new-S`只需要设置一个环境变量,直接编译就好。 这是因为实际上执行`build.sh`的时候,就是在把Runtime部分编译成wasm的过程,而在`new-S`已经把这个集成到了编译命令里,因此直接编辑即可。 这两者是有显著区别的: 对于`old-S`而言,需要显式的存在wasm的这个包(crate),即位于`/node/runtime/wasm/`,因此 ``` |-- node/runtime # 代表整个Runtime部分 |--/wasm # 代表对上级目录的Runtime编译成WASM形式 ``` 我们查看`/wasm`目录下的`lib.rs`和`Cargo.toml`可以看到: ``` [package] name = "node-runtime-wasm" version = "2.0.0" authors = ["Parity Technologies <admin@parity.io>"] edition = "2018" [lib] name = "node_runtime" crate-type = ["cdylib"] [dependencies] node-runtime = { path = "..", default-features = false } # 注意这一行,表示对于编译WASM而言,引用的源文件为上级的Runtime ``` 而`node/wasm/src/lib.rs`文件中只有一行: ``` #![cfg_attr(not(feature = "std"), no_std)] pub use node_runtime::*; ``` 即表示引用Runtime的所有东西。 而编译出来的wasm文件即显式的位于:`/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm` 对于`new-S`而言,这个wasm包**已经不显式的存在了**,它的整个存在及编译产物都融合在了一个编译指令中(这是由于cargo可以自定义编译过程,类似cmake一些脚本)。 在`new-S`中,在编译中**直接生成**了类似`old-S`中的wasm包,位于目录:`<substrate>/target/debug(或者release)/wbuild`,注意`target`目录即是编译产物。所以实际上在`new-S`中,wasm包成变为了自动生成而不需要显示存在了。 在`wbuild`目录下,我们可以看到一个目录`node-runtime`,这个目录下的的`Cargo.toml`文件是这样的: ``` [package] name = "node-runtime-wasm" version = "1.0.0" edition = "2018" [lib] name = "node_runtime" crate-type = ["cdylib"] [dependencies] wasm_project = { package = "node-runtime", path = "/你的路径/substrate/node/runtime", default-features = false } ``` 将其和`old-S`的`Cargo.toml`一比较就很明显了,这个目录即是原来`old-S`那个需要显式存在的`wasm`目录。 而编译所产生的wasm文件也位于该目录下:`<substrate>/target/debug(或者release)/wbuild/node-runtime/node_runtime.compact.wasm` 而在`old-S`中,这个wasm文件根据`build.sh`脚本只能编译release,而在`new-S`中这个文件就是根据环境变量`WASM_BUILD_TYPE`来决定是release或者debug。虽然我觉得wasm编译成debug一点用都没有。 ### node项目结构 介绍了`old-S`和`new-S`Runtime wasm的差别后,现在介绍node的项目结构,即应该如何使用Substrat框架。 由前2篇的文章可知,实际上使用Substrate构建的链分为2部分: * Runtime 层,实际上代表链的业务逻辑 * 除Runtime以外的其他,代表了链应该具备的基础功能,如共识,交易池,p2p等。 其中前者就是链的开发者需要做的,而后者Substrate已经做了绝大部分并留出了接口,因此node的作用就是去调用这些接口,并和Runtime层结合起来。 对于`new-S`而言,项目结构是: * `cli`,该目录实际上是**连接Substrate的核心,并且是项目的入口**,包含了以下3个核心 * `chain_spec.rs`:表示链的描述(名字,网络协议号等),genesis的配置与生成 * `cli.rs`:启动入口,配置自有的命令参数 * `service.rs`:启动服务,也就是一个完整区块链中网络服务,交易池,执行线程,等等服务线程的配置与启动点。 * `executor`:提供执行器宏`native_executor_instance!`的配置,自己的项目照抄即可 * `primitive`:node项目中一些类型信息的原语,比如定义区块头,定义交易,定义签名类型,定义区块高度等等,自己的项目的通用类型可以定义于此,注意这些类型定义是对于区块链基础层的,不是Runtime层的基础类型 * `rpc`和`rpc-client`:在`new-S`留出了rpc的扩展接口,在`old-S`没有。如果需要添加针对自己项目的rpc,就可以参照rpc定义自己需要的rpc接口。`rpc-client`只是一个客户端,不重要 * `runtime`:链的Runtime,即本链的核心,后文进行介绍。 * `testing`(不做介绍) 对于`old-S`而言,项目结构与`new-S`基本一致。只是由于rpc没有扩展接口,所以`old-S`除非更改Substrate源码,否则无法扩展rpc。 这里重点介绍一下`cli` 新老在管理线程方式有比较大的区别,在`new-S`已经全套用了Future管理,在`old-S`中主要还是使用Signal管理。不过这些其实并不重要,若不是需要把老Substrate移植到新的Substrate上保持兼容,这些直接选用新的Substrate的管理方式即可。因此这里只简单介绍一下`new-S` 无论运行新老,它们的主要入口都是`run_until_exit`。该处就是启动所有服务的地方。在`new-S`中,其方式为: 1. 首先在`parse_and_prepare`中,解析各种输入参数,成为config 2. 根据config对Service进行配置(`service::new_light/service::new_full`),生成一个实例,因此Service实例,也就是`core/service/src/lib.rs`中的`pub struct Service`,持有了所有关键服务的引用。在new_full中启动了这些服务。 3. 把Service实例传入`run_until_exit`中运行,并block住,等待退出的kill信号。 具体需要更改就依据源码即可,这里就不详细介绍了。 ### node的Runtime 这里就是核心部分。这个部分`new-S`和`old-S`差别也不是很大。Runtime的核心其实就是`node/runtime/src/lib.rs`文件,这个文件大体可分为一下几部分: 1. `VERSION`的定义。这个很关键,其控制着Runtime的版本(Runtime版本和链版本不是一个东西),这个版本将会控制者执行代码的时候Native版本和Wasm版本的比较,从而比如在`NativeElseWasm`模式下选择正确版本的代码执行 2. 很大一串 Runtime Module 的 trait的实现,这里的Runtime Module 即为实现Runtime的模块,比如`balances`控制资产模块,`staking`控制权益模块等等。而Substrate预先实现的一系列模块叫做 Substrate Runtime Module Libary,其缩写也就是`srml`,也就是Substrate目录下的`srml`目录下的文件。使用Substrate的链的实现者可以引用这些Substrate提供的库,也可以自己重新编写符合自己业务需求的。 3. `construct_runtime!`宏,这个宏就是构建Runtime最核心的部分,也就是说这条链的Runtime由那些模块组成。在srml中编写的模块,或者开发者自己编写的Runtime Module,最后需要写到这个宏里才会真正在这条链里存在并生效,也就是Runtime中的各个模块的总开关。 4. 地址,区块头,区块,交易体等的定义,注意这里和Runtime的定义,和前文提到的`primitives`中的定义不一样。 5. `impl_runtime_apis!`宏,Runtime层的api的实现宏,通过这个宏可以实现一些让Runtime层对外暴露的接口,大部分用于重新组织对外暴露的数据,少部分如`initialize_block`,`execute_block`会在一些关键地方被调用去执行Runtime。可以简单的理解为这个宏实现的部分就是外界和Runtime层范围的接口。 开发者开发自己的Runtime层时,应自己研究该部分的组成形式。以后的文章再细说 ### Runtime Module 的构成 Substrate提供了一个Runtime Module构成的example:位于`srml/example`。 这个模块中文档也很长,写清楚了一个模块的基本构成。总体来说说,一个模块由3个宏构成: 1. `decl_module!`。这个宏即是这个**模块对外的Call,也就是交易中能够调用的function**。在这个宏中每定义一个函数,最后在宏展开的结果都是一个对外的Call,也是发送到链上的交易能够调用入口,类似于以太坊合约中写成external的对外接口,供用户发送交易调用。注意**这个宏同时对模块内部生成一个`Module`的数据结构,对外及对内都代表了这个模块实例** 2. `decl_storage!`。这个宏即是这个**模块定义的k-v存储**。注意该定义的存储最后将会进入状态树,也就是说定义在这里的存储即是“链上数据”。相对应的,由于区块链的特性,这里定义的存储应该经过仔细权衡和设计,否则后续会带来很多的坑!今后的文章会说一些。Substrate提供了2中基础的存储定义模式: 1. value 2. map/linked_map 3. `decl_event!`。这个宏即是类似以太坊合约中的`event`的概念,用于记录一些关键信息供链外进行解析查看。这个event也是进入状态树的,因此也需要小心设计避免给状态树带来过大的负担,也要考虑兼容问题。个人认为这种设计不好。 4. `trait`。这个trait是这个模块在`runtime/src/lib.rs`中需要实现的接口。一般情况下这个trait有2个作用: 1. 用于类型的通用化 2. **用于在Runtime Module 继承关系中,父模块调用子模块的接口**(类似于多态的接口定义,实现在对应子模块中,而`runtime/src/lib.rs`的配置类似虚函数表的指向) Runtime Module 就是链的开发者需要做的事情,这块部分讲深了需要花费大量的篇幅,后续的文章会简单进行剖析 ## 总结 以上即是Substrate的入门参考,简单的说能做到以下就可以进行代码剖析了: 1. 以native的形式运行起node,并正常出块 2. 参考以上入门介绍 3. 能够下断点debug *本文首发于知乎专栏* *[金狗喵喵喵的区块链研习](https://zhuanlan.zhihu.com/c_74315572),版权属于* [@金晓](https://www.zhihu.com/people/cb3b97fbae2e613fba0f76e7bd23fc76) *如需转载,需取得同意并标明出处,并涵盖版权信息!*
已经鸽了好久了,现在开始补文章。
本文承接Substrate设计总览入门介绍系列第三篇,介绍Substrate的入门参考。三篇文章为:
- Substrate的总体设计
- Substrate的项目结构
- 入门参考
本文重点从运行Substrate的node节点介绍如何入门。
目前Substrate的文档十分缺乏,本文的介绍相当于是Substrate的一种文档。
自本系列第一篇文章至本文半年的时间,Substrate虽然整体框架上变动不大,但是其中很多细节已经有了很大的变化,因此对于入学者来说需要对照起前两篇文章介绍时的substrate与当前的substrate,否则在一些概念上无法联系起来。
对于新的Substrate,本文定master分支上的提交ec7c6cf1779b88e75137ef6f6f7bf67ecd0f75a5
(11月2日),后文简称为new-S
对于前两篇文章提到的Substrate,本文定master分支上的提交d6eba14a55be26e1a4e24a882ff574aa0190aff6
(5月22日),后文简称为old-S
运行node
根据(二)中介绍的项目结构,Substrate自身已经提供了一个node节点用于运行Substrate。也就是说node就是使用Substrate框架的模板。
因此若使用Substrate开发区块链,就是仿照node进行项目组织
准备
因为Substrate在第一次启动的时候需要加载一些环境,而且若加载初始环境时间过久会导致无法出块,因此建议大家选用cpu性能好一些的电脑,或者根据后文更改出块间隔时间。
另一方面若能够灵活使用gdb或者IDE的debug工具将会对学习substrate节省相当大量的时间,因此建议大家首先想办法配置并熟悉如何debug一个rust项目,再继续后续文章。
Substrate项目当前已经十分复杂了,很多情况下仅仅看可能无法在脑中记住代码逻辑,因此推荐大家采用一些IDE进行辅助。这里推荐IntelliJ系列的IDE:
- IntelliJ IDEA ,安装上rust插件后即可使用。但是无法与gdb进行联通,进行debug。不过这个有社区版,免费。
- Clion,安装上rust插件后即可使用,可以使用gdb进行联通调试,不过这个收费。
个人推荐Clion。
至于vscode加上rust插件和rls,个人不是很推荐,因为substrate太庞大了,经常让rls崩溃。。而IntelliJ 这边的rust插件的智能化是重新写的,没有用rls。
对于Rust而言,需要事先熟悉好“关联属性”相关的部分。
运行node
这里建议不用参照substrate的README进行操作,而是自己编译substrate的源码进行调试。
new-S
对于new-S
而言,首先参照README中的6.1章节,根据自己的操作系统配置好环境。
切换到Substrate的根目录下,执行以下命令:
# 建议首先设置下面这个环境变量(到当前shell环境,到.bashrc 等等,总之就是在执行cargo build/run 的时候,上下文要有这个环境变量)
export WASM_BUILD_TYPE=release
cargo run -- --dev -d .sub --execution=NativeElseWasm
# 若电脑性能不够,建议编译成release,否则无法出块。
# cargo run --release -- --dev -d .sub --execution=NativeElseWasm
即可直接运行node节点。这里--dev
是指定为dev模式,并配置好默认的Alice私钥运行单节点。
命令行参数说明如下:
--dev
差不多等价为---chain=dev --validator --key=//Alice
,再加上其他的一些rpc,telemtry相关的。这里只是强调会以Alice的私钥启动验证者模式。-d .sub
用于指定该链生成数据(区块,状态等)的数据根目录,我一般习惯用.sub
,大家可以设置成自己希望的路径。--execution=Native
将会指定执行方式为NativeElseWasm
,因为只有在Native环境下才可下断点调试Runtime,否则默认以wasm执行是无法调试Runtime的。
2019-11-03 16:32:26 Running in --dev mode, RPC CORS has been disabled.
2019-11-03 16:32:26 Substrate Node
2019-11-03 16:32:26 version 2.0.0-ec7c6cf17-x86_64-linux-gnu
2019-11-03 16:32:26 by Parity Technologies, 2017-2019
2019-11-03 16:32:26 Chain specification: Development
2019-11-03 16:32:26 Node name: delightful-planes-8797
2019-11-03 16:32:26 Roles: AUTHORITY
2019-11-03 16:32:29 Initializing Genesis block/state (state: 0x0ec9…c1dd, header-hash: 0x8762…41c3)
2019-11-03 16:32:29 Loading GRANDPA authority set from genesis on what appears to be first startup.
2019-11-03 16:32:40 Loaded block-time = BabeConfiguration { slot_duration: 3000, epoch_length: 200, c: (1, 4), genesis_authorities: [(Public(d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d (5GrwvaEF...)), 1)], randomness: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], secondary_slots: true } seconds from genesis on first-launch
2019-11-03 16:32:40 Creating empty BABE epoch changes on what appears to be first startup.
2019-11-03 16:32:43 Highest known block at #0
2019-11-03 16:32:43 Using default protocol ID "sup" because none is configured in the chain specs
2019-11-03 16:32:43 Local node identity is: QmRX26NAABDXQHGxVESibyrAnciBQdpgwdX2ZRngh5vCUt
2019-11-03 16:32:43 Starting BABE Authorship worker
2019-11-03 16:32:45 Starting consensus session on top of parent 0x8762ac86a1f1723f4b6659c2f5b0c848c1f1ec3f65f1fb6ef37e903a72ec41c3
其他命令具体执行--help
看描述就好。
old-S
这里同样不建议参照这个版本下的README,而是按照以下操作:
首先参照该版本README的6.1章节配置好环境,注意这个版本的下的windows似乎支持还不完善(不确定)。
然后切换以下目录执行:
cd node/runtime/wasm
./build.sh
这个行为会编译node的节点的Runtime的WASM文件于路径下
<substrate>/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm
简单浏览build.sh
文件可知,实际上这个步骤是把Runtime编译了一份--target=wasm32-unknown-unknown
目标的wasm文件,然后使用wasm-gc
对生成的wasm进行压缩。
注意这个文件后续将要在其他文件中获取到(主要是genesis),编译进入node的二进制中。
然后切换回substrate根目录上,执行
cargo run -- --dev -d .sub --block-construction-execution=NativeElseWasm --other-execution=NativeElseWasm
即可和new-S
一样运行起以Alice为私钥启动的单验证者节点。
这里提一下,在这个版本的substrate里面存在一个私钥推断的bug,如果一定要严格按照Alice私钥生成规则生成的话(如涉及到subkey)建议切换到提交498452517f95d399ed1b422ea5097d2aa984fd02
,或者把这个提交cherry pick过来,否则只要把Alice私钥导出来使用即可,其他部分不影响。
和new-S
相比execution
部分在old-S
中还没有统一成一个指令,而另两个execution
不重要,因此只需要指定--block-construction-execution
和--other-execution
是native即可。
运行起来后可看到
2019-11-03 21:37:04 Running in --dev mode, RPC CORS has been disabled.
2019-11-03 21:37:04 Substrate Node
2019-11-03 21:37:04 version 2.0.0-d6eba14a5-x86_64-linux-gnu
2019-11-03 21:37:04 by Parity Technologies, 2017-2019
2019-11-03 21:37:04 Chain specification: Development
2019-11-03 21:37:04 Node name: able-thumb-3759
2019-11-03 21:37:04 Roles: AUTHORITY
2019-11-03 21:37:07 Initializing Genesis block/state (state: 0xd4a3…d6cd, header-hash: 0x9dd9…1463)
2019-11-03 21:37:07 Loaded block-time = 4 seconds from genesis on first-launch
2019-11-03 21:37:07 Loading GRANDPA authority set from genesis on what appears to be first startup.
2019-11-03 21:37:08 Highest known block at #0
2019-11-03 21:37:08 Using default protocol ID "sup" because none is configured in the chain specs
2019-11-03 21:37:08 Local node identity is: QmbeniX5UtetYpk8i6NdLvuvtWhnymz6R5tCdK91pgaL4d
2019-11-03 21:37:08 Libp2p => Random Kademlia query has yielded empty results
2019-11-03 21:37:08 Listening for new connections on 127.0.0.1:9944.
2019-11-03 21:37:08 Using authority key 5CLFVjc5C8A5vwwuHaYgxHDsamotvEsaWE7D7jJR7SsDbeWm
2019-11-03 21:37:08 Running Grandpa session as Authority 5CLFVjc5C8A5vwwuHaYgxHDsamotvEsaWE7D7jJR7SsDbeWm
2019-11-03 21:37:13 Idle (0 peers), best: #0 (0x9dd9…1463), finalized #0 (0x9dd9…1463), ⬇ 0 ⬆ 0
2019-11-03 21:37:13 Prepared block for proposing at 1 [hash: 0x8df244b6e236c6b224a2b0399151455ac34a5a6498f47cae6ea2b9282d9abf45; parent_hash: 0x9dd9…1463; extrinsics: [0x6a28…491a]]
2019-11-03 21:37:13 Pre-sealed block for proposal at 1. Hash now 0x6c70e1e55e2f44962b3ddbac078c7c7d7dbd3a0c108a9af011585de55580041a, previously 0x8df244b6e236c6b224a2b0399151455ac34a5a6498f47cae6ea2b9282d9abf45.
和new-S
一样,当看到Pre-sealed block for proposal
时,表示已经在正常出块了。
new-S和old-S 启动变化的原因
从前文可以看出,old-S
需要手动执行build.sh
再编译节点,而new-S
只需要设置一个环境变量,直接编译就好。
这是因为实际上执行build.sh
的时候,就是在把Runtime部分编译成wasm的过程,而在new-S
已经把这个集成到了编译命令里,因此直接编辑即可。
这两者是有显著区别的:
对于old-S
而言,需要显式的存在wasm的这个包(crate),即位于/node/runtime/wasm/
,因此
|-- node/runtime # 代表整个Runtime部分
|--/wasm # 代表对上级目录的Runtime编译成WASM形式
我们查看/wasm
目录下的lib.rs
和Cargo.toml
可以看到:
[package]
name = "node-runtime-wasm"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[lib]
name = "node_runtime"
crate-type = ["cdylib"]
[dependencies]
node-runtime = { path = "..", default-features = false } # 注意这一行,表示对于编译WASM而言,引用的源文件为上级的Runtime
而node/wasm/src/lib.rs
文件中只有一行:
#![cfg_attr(not(feature = "std"), no_std)]
pub use node_runtime::*;
即表示引用Runtime的所有东西。
而编译出来的wasm文件即显式的位于:/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm
对于new-S
而言,这个wasm包已经不显式的存在了,它的整个存在及编译产物都融合在了一个编译指令中(这是由于cargo可以自定义编译过程,类似cmake一些脚本)。
在new-S
中,在编译中直接生成了类似old-S
中的wasm包,位于目录:<substrate>/target/debug(或者release)/wbuild
,注意target
目录即是编译产物。所以实际上在new-S
中,wasm包成变为了自动生成而不需要显示存在了。
在wbuild
目录下,我们可以看到一个目录node-runtime
,这个目录下的的Cargo.toml
文件是这样的:
[package]
name = "node-runtime-wasm"
version = "1.0.0"
edition = "2018"
[lib]
name = "node_runtime"
crate-type = ["cdylib"]
[dependencies]
wasm_project = { package = "node-runtime", path = "/你的路径/substrate/node/runtime", default-features = false }
将其和old-S
的Cargo.toml
一比较就很明显了,这个目录即是原来old-S
那个需要显式存在的wasm
目录。
而编译所产生的wasm文件也位于该目录下:<substrate>/target/debug(或者release)/wbuild/node-runtime/node_runtime.compact.wasm
而在old-S
中,这个wasm文件根据build.sh
脚本只能编译release,而在new-S
中这个文件就是根据环境变量WASM_BUILD_TYPE
来决定是release或者debug。虽然我觉得wasm编译成debug一点用都没有。
node项目结构
介绍了old-S
和new-S
Runtime wasm的差别后,现在介绍node的项目结构,即应该如何使用Substrat框架。
由前2篇的文章可知,实际上使用Substrate构建的链分为2部分:
- Runtime 层,实际上代表链的业务逻辑
- 除Runtime以外的其他,代表了链应该具备的基础功能,如共识,交易池,p2p等。
其中前者就是链的开发者需要做的,而后者Substrate已经做了绝大部分并留出了接口,因此node的作用就是去调用这些接口,并和Runtime层结合起来。
对于new-S
而言,项目结构是:
-
cli
,该目录实际上是连接Substrate的核心,并且是项目的入口,包含了以下3个核心chain_spec.rs
:表示链的描述(名字,网络协议号等),genesis的配置与生成cli.rs
:启动入口,配置自有的命令参数service.rs
:启动服务,也就是一个完整区块链中网络服务,交易池,执行线程,等等服务线程的配置与启动点。
-
executor
:提供执行器宏native_executor_instance!
的配置,自己的项目照抄即可 -
primitive
:node项目中一些类型信息的原语,比如定义区块头,定义交易,定义签名类型,定义区块高度等等,自己的项目的通用类型可以定义于此,注意这些类型定义是对于区块链基础层的,不是Runtime层的基础类型 -
rpc
和rpc-client
:在new-S
留出了rpc的扩展接口,在old-S
没有。如果需要添加针对自己项目的rpc,就可以参照rpc定义自己需要的rpc接口。rpc-client
只是一个客户端,不重要 -
runtime
:链的Runtime,即本链的核心,后文进行介绍。 -
testing
(不做介绍)
对于old-S
而言,项目结构与new-S
基本一致。只是由于rpc没有扩展接口,所以old-S
除非更改Substrate源码,否则无法扩展rpc。
这里重点介绍一下cli
新老在管理线程方式有比较大的区别,在new-S
已经全套用了Future管理,在old-S
中主要还是使用Signal管理。不过这些其实并不重要,若不是需要把老Substrate移植到新的Substrate上保持兼容,这些直接选用新的Substrate的管理方式即可。因此这里只简单介绍一下new-S
无论运行新老,它们的主要入口都是run_until_exit
。该处就是启动所有服务的地方。在new-S
中,其方式为:
- 首先在
parse_and_prepare
中,解析各种输入参数,成为config - 根据config对Service进行配置(
service::new_light/service::new_full
),生成一个实例,因此Service实例,也就是core/service/src/lib.rs
中的pub struct Service
,持有了所有关键服务的引用。在new_full中启动了这些服务。 - 把Service实例传入
run_until_exit
中运行,并block住,等待退出的kill信号。
具体需要更改就依据源码即可,这里就不详细介绍了。
node的Runtime
这里就是核心部分。这个部分new-S
和old-S
差别也不是很大。Runtime的核心其实就是node/runtime/src/lib.rs
文件,这个文件大体可分为一下几部分:
VERSION
的定义。这个很关键,其控制着Runtime的版本(Runtime版本和链版本不是一个东西),这个版本将会控制者执行代码的时候Native版本和Wasm版本的比较,从而比如在NativeElseWasm
模式下选择正确版本的代码执行- 很大一串 Runtime Module 的 trait的实现,这里的Runtime Module 即为实现Runtime的模块,比如
balances
控制资产模块,staking
控制权益模块等等。而Substrate预先实现的一系列模块叫做 Substrate Runtime Module Libary,其缩写也就是srml
,也就是Substrate目录下的srml
目录下的文件。使用Substrate的链的实现者可以引用这些Substrate提供的库,也可以自己重新编写符合自己业务需求的。 construct_runtime!
宏,这个宏就是构建Runtime最核心的部分,也就是说这条链的Runtime由那些模块组成。在srml中编写的模块,或者开发者自己编写的Runtime Module,最后需要写到这个宏里才会真正在这条链里存在并生效,也就是Runtime中的各个模块的总开关。- 地址,区块头,区块,交易体等的定义,注意这里和Runtime的定义,和前文提到的
primitives
中的定义不一样。 impl_runtime_apis!
宏,Runtime层的api的实现宏,通过这个宏可以实现一些让Runtime层对外暴露的接口,大部分用于重新组织对外暴露的数据,少部分如initialize_block
,execute_block
会在一些关键地方被调用去执行Runtime。可以简单的理解为这个宏实现的部分就是外界和Runtime层范围的接口。
开发者开发自己的Runtime层时,应自己研究该部分的组成形式。以后的文章再细说
Runtime Module 的构成
Substrate提供了一个Runtime Module构成的example:位于srml/example
。
这个模块中文档也很长,写清楚了一个模块的基本构成。总体来说说,一个模块由3个宏构成:
-
decl_module!
。这个宏即是这个模块对外的Call,也就是交易中能够调用的function。在这个宏中每定义一个函数,最后在宏展开的结果都是一个对外的Call,也是发送到链上的交易能够调用入口,类似于以太坊合约中写成external的对外接口,供用户发送交易调用。注意这个宏同时对模块内部生成一个Module
的数据结构,对外及对内都代表了这个模块实例 -
decl_storage!
。这个宏即是这个模块定义的k-v存储。注意该定义的存储最后将会进入状态树,也就是说定义在这里的存储即是“链上数据”。相对应的,由于区块链的特性,这里定义的存储应该经过仔细权衡和设计,否则后续会带来很多的坑!今后的文章会说一些。Substrate提供了2中基础的存储定义模式:- value
- map/linked_map
-
decl_event!
。这个宏即是类似以太坊合约中的event
的概念,用于记录一些关键信息供链外进行解析查看。这个event也是进入状态树的,因此也需要小心设计避免给状态树带来过大的负担,也要考虑兼容问题。个人认为这种设计不好。 -
trait
。这个trait是这个模块在runtime/src/lib.rs
中需要实现的接口。一般情况下这个trait有2个作用:- 用于类型的通用化
- 用于在Runtime Module 继承关系中,父模块调用子模块的接口(类似于多态的接口定义,实现在对应子模块中,而
runtime/src/lib.rs
的配置类似虚函数表的指向)
Runtime Module 就是链的开发者需要做的事情,这块部分讲深了需要花费大量的篇幅,后续的文章会简单进行剖析
总结
以上即是Substrate的入门参考,简单的说能做到以下就可以进行代码剖析了:
- 以native的形式运行起node,并正常出块
- 参考以上入门介绍
- 能够下断点debug
本文首发于知乎专栏 金狗喵喵喵的区块链研习,版权属于 @金晓
如需转载,需取得同意并标明出处,并涵盖版权信息!
区块链技术网。
- 发表于 2019-11-05 19:42
- 阅读 ( 3770 )
- 学分 ( 27 )
- 分类:Polkadot
评论