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

  • A+
所属分类:技术

chatGPT账号

之前写的很多文章的模块代码都是基于substrate-node-template开发的,它是一个节点模板程序,这篇文章介绍substrate-node-template的代码结构和各个代码块的功能。

目录结构

定位到substrate-node-template目录,使用tree -I target命令,可以查看substrate-node-template项目的目录结构:

【Substrate开发教程】24 - 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!:定义业务逻辑代码;
  • 免责声明

    发文时比特币价格:$15778

    当前比特币价格:[crypto coins=”BTC” type=”text” show=”price”]

    当前比特币涨幅:[crypto coins=”BTC” type=”text” show=”percent”]

    免责声明:

    本文不代表路远网立场,且不构成投资建议,请谨慎对待。用户由此造成的损失由用户自行承担,与路远网没有任何关系;

    路远网不对网站所发布内容的准确性,真实性等任何方面做任何形式的承诺和保障;

    网站内所有涉及到的区块链(衍生)项目,路远网对项目的真实性,准确性等任何方面均不做任何形式的承诺和保障;

    网站内所有涉及到的区块链(衍生)项目,路远网不对其构成任何投资建议,用户由此造成的损失由用户自行承担,与路远网没有任何关系;

    路远区块链研究院声明:路远区块链研究院内容由路远网发布,部分来源于互联网和行业分析师投稿收录,内容为路远区块链研究院加盟专职分析师独立观点,不代表路远网立场。

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的电报
  • 这是我的电报扫一扫
  • weinxin
chatGPT账号
路远

发表评论

您必须登录才能发表评论!