之前写的很多文章的模块代码都是基于substrate-node-template开发的,它是一个节点模板程序,这篇文章介绍substrate-node-template的代码结构和各个代码块的功能。 目录结构 定位到substrate-node-template目录,使用tree -I target命令,可以查看substrate-node-template项目的目录结构: 这里使用-I选项省略掉了target目录,下面依次分析这些目录和文件。 Cargo.lock & Cargo.toml Cargo是rust的包管理器,相当于nodejs的npm或yarn,但cargo具有更多功能,还充当rust的代码组织管理工具,cargo提供了从项目的建立、构建到测试、运行直至部署的一系列工具,为rust项目的管理提供尽可能完整的手段。 Cargo.lock包含依赖项的确切信息,由Cargo自动生成,无需手动编辑,而Cargo.toml需要手动配置依赖。 Cargo.toml存放项目信息[package]和依赖库[dependencies]等,相当于cargo构建项目的指南。 根目录下的Cargo.toml内容如下: [profile.release] panic = 'unwind' [workspace] members = [ 'node', 'pallets/template', 'runtime', ] substrate-node-template是一个Rust workspace项目,可以清晰地管理组件库(library)和可执行程序(binary)。 这个[workspace]的成员有: node:可执行程序,在node/src/main.rs中有可执行的main函数入口;pallets/template:模块代码,在pallets/template/src/lib.rs中定义了可被外部调用的函数和数据结构;runtime:组件库,在runtime/src/lib.rs中定义了运行时逻辑;[profile.release]配置的panic='unwind'表示和catch_unwind一起使用捕获某个线程内panic抛出的异常。 scripts目录 scripts目录下包含两个Shell脚本: docker_run.sh:使用Docker启动substrate-node-template的脚本;init.sh:初始化WASM构建环境的脚本;init.sh脚本的内容包括升级Rust版本: rustup update nightly rustup update stable 和添加构建WebAssembly工具链: rustup target add wasm32-unknown-unknown --toolchain nightly node目录 node目录包含以下文件: build.rs:自定义的构建脚本;Cargo.toml:node包构建指南;src/chain_spec.rs:构造ChainSpec(链规范文件);src/cli.rs:声明客户端结构体和子命令;src/command.rs:提供客户端相关命令的实现函数;src/lib.rs:引入库模块;src/main.rs:substrate-node-template编译成可执行程序的入口文件;src/rpc.rs:节点指定的RPC方法的集合;src/service.rs:提供构造Substrate服务的工具方法;1、Cargo.toml是node包构建指南,使用[[bin]]表示这个包是可执行的: [[bin]] name = 'node-template' 通过[build-dependencies]引入编译时的依赖,在build.rs中使用: [build-dependencies] substrate-build-script-utils = '2.0.0' 其他信息还有[package]、[package.metadata.docs.rs]、[dependencies]、[features]等,和一般Cargo.toml相同。 2、 build.rs是自定义的构建脚本,内容如下: use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; fn main() { generate_cargo_keys(); rerun_if_git_head_changed(); } 作用是让Cargo编译和执行该脚本。 3、src/main.rs是substrate-node-template编译成可执行程序的入口文件,内容如下: #![warn(missing_docs)] mod chain_spec; #[macro_use] mod service; mod cli; mod command; mod rpc; fn main() -> sc_cli::Result<()> { command::run() } #![warn(missing_docs)]注解表示在编译时,如果模块缺少文档会打印警告信息。 mod chain_spec、mod service、mod cli、mod command、mod rpc引入当前目录下的其他模块。 #[macro_use]加载引入的模块下的所有宏。 main()函数是应用程序入口,返回的sc_cli::Result<()>是一个自定义Result类型: pub type Result<T> = std::result::Result<T, Error>; main()函数内部执行command模块提供的run()函数。 4、src/command.rs提供客户端相关命令的实现函数,创建了Cli的实现SubstrateCli,定义了run()函数。 run()函数内部先通过from_args()解析命令行参数,返回一个Cli结构体,该结构体在cli.rs中定义,然后匹配参数中的子命令(subcommand),如果存在子命令则执行它。 执行子命令时先调用cli.create_runner(cmd)?创建runner,再调用async_run()异步执行该子命令。 如果命令行参数中没有子命令,则调用run_node_until_exit()启动节点。 5、src/cli.rs声明客户端结构体和子命令。 Cli结构体声明如下: #[derive(Debug, StructOpt)] pub struct Cli { #[structopt(subcommand)] pub subcommand: Option<Subcommand>, #[structopt(flatten)] pub run: RunCmd, } 包含可选的子命令和命令行选项,编译后的substrate-node-template可以通过 ./target/release/node-template -h 获得可用的子命令和选项的使用说明。 具体的子命令使用枚举声明: #[derive(Debug, StructOpt)] pub enum Subcommand { BuildSpec(sc_cli::BuildSpecCmd), CheckBlock(sc_cli::CheckBlockCmd), ExportBlocks(sc_cli::ExportBlocksCmd), ExportState(sc_cli::ExportStateCmd), ImportBlocks(sc_cli::ImportBlocksCmd), PurgeChain(sc_cli::PurgeChainCmd), Revert(sc_cli::RevertCmd), #[structopt(name = "benchmark", about = "Benchmark runtime pallets.")] Benchmark(frame_benchmarking_cli::BenchmarkCmd), } 6、src/chain_spec.rs构造ChainSpec(链规范文件),ChainSpec定义了链的可用配置,用来构造初始区块。 src/chain_spec.rs中定义了两个函数: pub fn development_config() -> Result<ChainSpec, String>pub fn local_testnet_config() -> Result<ChainSpec, String>表示substrate-node-template提供的两种模式: 通过命令行选项--dev指定的开发网络(development),只有一个验证人Alice;本地测试网络(local_testnet),有两个验证人Alice和Bob;接下来调用ChainSpec::from_genesis创建链规范文件。 7、src/service.rs提供构造Substrate服务的工具方法。 src/service.rs先使用native_executor_instance!宏定义了一个结构体Executor,并实现NativeExecutionDispatch接口,即可以通过函数名来调用该函数。 src/service.rs的工具方法包括: 8、src/rpc.rs提供节点指定的RPC方法的集合。 rpc.rs提供create_full()方法,用于实例化所有完整的RPC扩展。 9、src/lib.rs用于引入库模块,内容如下: pub mod chain_spec; pub mod service; pub mod rpc; runtime目录 runtime目录包含以下文件: build.rs:自定义的构建脚本;Cargo.toml:runtime包构建指南;src/lib.rs:链上runtime入口文件;1、Cargo.toml是runtime包的构建指南,除了常见的配置项外,还有[build-dependencies]: [build-dependencies] wasm-builder-runner = { package = 'substrate-wasm-builder-runner', version = '2.0.0' } 添加了构建脚本build.rs所依赖的wasm-builder-runner。 2、build.rs是自定义的构建脚本,内容如下: use wasm_builder_runner::WasmBuilder; fn main() { WasmBuilder::new() .with_current_project() .with_wasm_builder_from_crates("2.0.0") .export_heap_base() .import_memory() .build() } 使用wasm-builder-runner将当前的runtime项目编译为Wasm二进制文件,该文件位于target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm。 3、src/lib.rs是链上runtime入口文件。 #![cfg_attr(not(feature = "std"), no_std)]表示编译时如果feature不是std(Rust标准库),那么必须是no_std(编译为Wasm)。 #![recursion_limit="256"]设置编译时可能出现的无限递归操作的最大数量。 下面的代码: #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); 表示当使用Rust标准库编译时,将生成的Wasm二进制内容通过常量的方式引入到当前runtime代码中。 引入依赖模块和template模块: pub use pallet_template; 接下来是一些runtime所需的基础类型的别名,和模块中的相关类型名称一致。 opaque模块封装了一些用于CLI初始化时的类型。 定义区块时间相关的常量: pub const MILLISECS_PER_BLOCK: u64 = 6000; 即每个区块的生产时间是6秒,可以根据需要修改配置。 接下来使用parameter_types!宏生成一些功能模块所需的满足Get接口的数据类型。 然后为runtime实现各个功能模块的接口: impl frame_system::Trait for Runtime {...} impl pallet_aura::Trait for Runtime {...} impl pallet_grandpa::Trait for Runtime {...} impl pallet_timestamp::Trait for Runtime {...} impl pallet_balances::Trait for Runtime {...} impl pallet_transaction_payment::Trait for Runtime {...} impl pallet_sudo::Trait for Runtime {...} impl pallet_template::Trait for Runtime {...} runtime由construct_runtime!宏构建: construct_runtime!( pub enum Runtime where Block = Block, NodeBlock = opaque::Block, UncheckedExtrinsic = UncheckedExtrinsic { System: frame_system::{Module, Call, Config, Storage, Event<T>}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage}, Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent}, Aura: pallet_aura::{Module, Config<T>, Inherent}, Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event}, Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>}, TransactionPayment: pallet_transaction_payment::{Module, Storage}, Sudo: pallet_sudo::{Module, Call, Config<T>, Storage, Event<T>}, // Include the custom logic from the template pallet in the runtime. TemplateModule: pallet_template::{Module, Call, Storage, Event<T>}, } ); construct_runtime!宏根据模块名称和所用的模块内的组件来构造runtime,构造时按照顺序加载初始存储,所以当B模块依赖A模块时,应当将A模块放在B之前。 最后使用impl_runtime_apis!宏实现runtime api定义的接口,这些接口通过decl_runtime_apis!宏进行定义。 pallets/template目录 pallets目录下可以包含多个pallet(模块),template就是一个pallet。 pallets/template目录包含以下文件: Cargo.toml:template模块构建指南;src/lib.rs:模块的具体功能实现代码;src/mock.rs:测试用例服务代码;src/tests.rs:测试用例;1、Cargo.toml是template模块的构建指南,根据需求主要配置[dependencies]和[features]。 [dependencies]是模块的依赖库,[features]默认使用std feature,保证runtime既可以编译为native版本(使用std feature),也可以编译为wasm版本(使用no_std feature)。 2、src/lib.rs是模块的具体功能实现代码。 mock和tests只在运行测试时进行编译。 然后是类型声明: pub trait Trait: frame_system::Trait { type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>; } 以及和业务逻辑代码相关的四个宏: decl_storage!:定义存储;decl_event!:定义事件;decl_error!:定义错误处理机制;decl_module!:定义业务逻辑代码;—- 编译者/作者:松果 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
【Substrate开发教程】24 - substrate-node-template项目结构详解
2020-11-08 松果 来源:区块链网络
LOADING...
相关阅读:
- 欧科云链OKLink:以太坊区块链区块高度11211395记录美国大选结果2020-11-08
- DAF今天18点二期预售截止,9号可以挖葡萄和碎片,糖果还是很甜的|5002020-11-08
- 引力观察2020年11月8日星期日2020-11-08
- ConsenSys创始人Lubin追梦去中心化生态为以太坊打下半壁江山2020-11-08
- BTC+ETH+DEFI?BTC大哥,请放缓一下脚步,等等其他的弟弟。主要是给我们2020-11-08