本文首发于知乎专栏 “金狗喵喵喵的区块链研习”,版权属于作者 @金晓,PolkaWorld 经作者授权转载。 本文承接本专栏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=releasecargo 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 Node2019-11-03 16:32:26 version 2.0.0-ec7c6cf17-x86_64-linux-gnu2019-11-03 16:32:26 by Parity Technologies, 2017-20192019-11-03 16:32:26 Chain specification: Development2019-11-03 16:32:26 Node name: delightful-planes-87972019-11-03 16:32:26 Roles: AUTHORITY2019-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-launch2019-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 #02019-11-03 16:32:43 Using default protocol ID "sup" because none is configured in the chain specs2019-11-03 16:32:43 Local node identity is: QmRX26NAABDXQHGxVESibyrAnciBQdpgwdX2ZRngh5vCUt2019-11-03 16:32:43 Starting BABE Authorship worker2019-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 Node2019-11-03 21:37:04 version 2.0.0-d6eba14a5-x86_64-linux-gnu2019-11-03 21:37:04 by Parity Technologies, 2017-20192019-11-03 21:37:04 Chain specification: Development2019-11-03 21:37:04 Node name: able-thumb-37592019-11-03 21:37:04 Roles: AUTHORITY2019-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-launch2019-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 #02019-11-03 21:37:08 Using default protocol ID "sup" because none is configured in the chain specs2019-11-03 21:37:08 Local node identity is: QmbeniX5UtetYpk8i6NdLvuvtWhnymz6R5tCdK91pgaL4d2019-11-03 21:37:08 Libp2p => Random Kademlia query has yielded empty results2019-11-03 21:37:08 Listening for new connections on 127.0.0.1:9944.2019-11-03 21:37:08 Using authority key 5CLFVjc5C8A5vwwuHaYgxHDsamotvEsaWE7D7jJR7SsDbeWm2019-11-03 21:37:08 Running Grandpa session as Authority 5CLFVjc5C8A5vwwuHaYgxHDsamotvEsaWE7D7jJR7SsDbeWm2019-11-03 21:37:13 Idle (0 peers), best: #0 (0x9dd9…1463), finalized #0 (0x9dd9…1463), ? 0 ? 02019-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 <[email protected]>"]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-SRuntime 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 —- 编译者/作者:PolkaWorld 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
Substrate 设计总览(三)—— Substrate 入门参考
2019-11-04 PolkaWorld 来源:区块链网络
LOADING...
相关阅读:
- Peertec,hiblocks加入Kakao GroundX区块链平台2020-08-05
- 麦客存储赞助《COINTELEGRAPH中文站》大湾区国际区块链周盛大开幕2020-08-05
- 意见:不完善的法律“关于CFA”的通过胜过缺乏法规2020-08-05
- 智合云汇时代新风向让更多人认知IPFS星际文件系统2020-08-05
- Litecoin计划与Cardano进行跨链通信并进行大量更新2020-08-05