LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 新闻观点 > 【Substrate开发教程】24 - substrate-node-template项目结构详解

【Substrate开发教程】24 - substrate-node-template项目结构详解

2020-11-08 松果 来源:区块链网络


之前写的很多文章的模块代码都是基于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的工具方法包括:

new_partial:构建一个局部节点服务;new_full:构建一个全节点服务;new_light:构建一个轻节点服务;

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!:定义业务逻辑代码;

—-

编译者/作者:松果

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...