Substrate设计总览入门介绍系列第三篇
已经鸽了好久了,现在开始补文章。
本文承接Substrate设计总览入门介绍系列第三篇,介绍Substrate的入门参考。三篇文章为:
本文重点从运行Substrate的node节点介绍如何入门。
目前Substrate的文档十分缺乏,本文的介绍相当于是Substrate的一种文档。
自本系列第一篇文章至本文半年的时间,Substrate虽然整体框架上变动不大,但是其中很多细节已经有了很大的变化,因此对于入学者来说需要对照起前两篇文章介绍时的substrate与当前的substrate,否则在一些概念上无法联系起来。
对于新的Substrate,本文定master分支上的提交ec7c6cf1779b88e75137ef6f6f7bf67ecd0f75a5
(11月2日),后文简称为new-S
对于前两篇文章提到的Substrate,本文定master分支上的提交d6eba14a55be26e1a4e24a882ff574aa0190aff6
(5月22日),后文简称为old-S
根据(二)中介绍的项目结构,Substrate自身已经提供了一个node节点用于运行Substrate。也就是说node就是使用Substrate框架的模板。
因此若使用Substrate开发区块链,就是仿照node进行项目组织
因为Substrate在第一次启动的时候需要加载一些环境,而且若加载初始环境时间过久会导致无法出块,因此建议大家选用cpu性能好一些的电脑,或者根据后文更改出块间隔时间。
另一方面若能够灵活使用gdb或者IDE的debug工具将会对学习substrate节省相当大量的时间,因此建议大家首先想办法配置并熟悉如何debug一个rust项目,再继续后续文章。
Substrate项目当前已经十分复杂了,很多情况下仅仅看可能无法在脑中记住代码逻辑,因此推荐大家采用一些IDE进行辅助。这里推荐IntelliJ系列的IDE:
个人推荐Clion。
至于vscode加上rust插件和rls,个人不是很推荐,因为substrate太庞大了,经常让rls崩溃。。而IntelliJ 这边的rust插件的智能化是重新写的,没有用rls。
对于Rust而言,需要事先熟悉好“关联属性”相关的部分。
这里建议不用参照substrate的README进行操作,而是自己编译substrate的源码进行调试。
对于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
看描述就好。
这里同样不建议参照这个版本下的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
时,表示已经在正常出块了。
从前文可以看出,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一点用都没有。
介绍了old-S
和new-S
Runtime wasm的差别后,现在介绍node的项目结构,即应该如何使用Substrat框架。
由前2篇的文章可知,实际上使用Substrate构建的链分为2部分:
其中前者就是链的开发者需要做的,而后者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
中,解析各种输入参数,成为configservice::new_light/service::new_full
),生成一个实例,因此Service实例,也就是core/service/src/lib.rs
中的pub struct Service
,持有了所有关键服务的引用。在new_full中启动了这些服务。run_until_exit
中运行,并block住,等待退出的kill信号。具体需要更改就依据源码即可,这里就不详细介绍了。
这里就是核心部分。这个部分new-S
和old-S
差别也不是很大。Runtime的核心其实就是node/runtime/src/lib.rs
文件,这个文件大体可分为一下几部分:
VERSION
的定义。这个很关键,其控制着Runtime的版本(Runtime版本和链版本不是一个东西),这个版本将会控制者执行代码的时候Native版本和Wasm版本的比较,从而比如在NativeElseWasm
模式下选择正确版本的代码执行balances
控制资产模块,staking
控制权益模块等等。而Substrate预先实现的一系列模块叫做 Substrate Runtime Module Libary,其缩写也就是srml
,也就是Substrate目录下的srml
目录下的文件。使用Substrate的链的实现者可以引用这些Substrate提供的库,也可以自己重新编写符合自己业务需求的。construct_runtime!
宏,这个宏就是构建Runtime最核心的部分,也就是说这条链的Runtime由那些模块组成。在srml中编写的模块,或者开发者自己编写的Runtime Module,最后需要写到这个宏里才会真正在这条链里存在并生效,也就是Runtime中的各个模块的总开关。primitives
中的定义不一样。impl_runtime_apis!
宏,Runtime层的api的实现宏,通过这个宏可以实现一些让Runtime层对外暴露的接口,大部分用于重新组织对外暴露的数据,少部分如initialize_block
,execute_block
会在一些关键地方被调用去执行Runtime。可以简单的理解为这个宏实现的部分就是外界和Runtime层范围的接口。开发者开发自己的Runtime层时,应自己研究该部分的组成形式。以后的文章再细说
Substrate提供了一个Runtime Module构成的example:位于srml/example
。
这个模块中文档也很长,写清楚了一个模块的基本构成。总体来说说,一个模块由3个宏构成:
decl_module!
。这个宏即是这个模块对外的Call,也就是交易中能够调用的function。在这个宏中每定义一个函数,最后在宏展开的结果都是一个对外的Call,也是发送到链上的交易能够调用入口,类似于以太坊合约中写成external的对外接口,供用户发送交易调用。注意这个宏同时对模块内部生成一个Module
的数据结构,对外及对内都代表了这个模块实例
decl_storage!
。这个宏即是这个模块定义的k-v存储。注意该定义的存储最后将会进入状态树,也就是说定义在这里的存储即是“链上数据”。相对应的,由于区块链的特性,这里定义的存储应该经过仔细权衡和设计,否则后续会带来很多的坑!今后的文章会说一些。Substrate提供了2中基础的存储定义模式:
decl_event!
。这个宏即是类似以太坊合约中的event
的概念,用于记录一些关键信息供链外进行解析查看。这个event也是进入状态树的,因此也需要小心设计避免给状态树带来过大的负担,也要考虑兼容问题。个人认为这种设计不好。
trait
。这个trait是这个模块在runtime/src/lib.rs
中需要实现的接口。一般情况下这个trait有2个作用:
runtime/src/lib.rs
的配置类似虚函数表的指向)Runtime Module 就是链的开发者需要做的事情,这块部分讲深了需要花费大量的篇幅,后续的文章会简单进行剖析
以上即是Substrate的入门参考,简单的说能做到以下就可以进行代码剖析了:
本文首发于知乎专栏 金狗喵喵喵的区块链研习,版权属于 @金晓
如需转载,需取得同意并标明出处,并涵盖版权信息!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!