Substrate代码导读:node-template

栏目: IT技术 · 发布时间: 4年前

内容简介:通过本文,你会了解到,1\Substrate node-template的组成部分,及各部分的功能简介;2\参数如何配置本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

通过本文,你会了解到,1\Substrate node-template的组成部分,及各部分的功能简介;2\参数如何配置

通过本文,你会了解到,

  • Substrate node-template的组成部分,及各部分的功能简介
  • 参数如何配置

Substrate作为一个标准的区块链开发框架,不仅提供了必备的底层公共组件(如数据库、共识、P2P、交易池)和通用的runtime模块(如资产相关的balances,治理相关的democracy等),还提供了将各个功能组件连接起来的节点模板程序( node-template )和节点程序( node )。本文主要介绍node-template中各个代码块的功能。

文件目录

在使用类Unix操作系统的情况下,进入node-template文件目录,执行 tree -I target 命令,获取详细的文件信息如下:

Substrate代码导读:node-template

这里我们忽略了target文件目录下的内容,来较少干扰性的输出。

workspace cargo.toml

node-template是一个标准的 Rust workspace 项目,当项目比较复杂时,使用workspace可以清晰地管理组件库(library)和可执行程序(binary)。在项目根目录的cargo.toml文件里有:

[workspace]
members = [
    'node',
    'pallets/template',
    'runtime',
]

这个workspace的成员有node、pallets/template、runtime,其中node是可执行程序,在对应的src/main.rs文件内拥有一个可执行的main函数入口;pallets/template和runtime是组件库,在src/lib.rs定义了可被外部使用的函数和数据结构。

细心的同学会注意到cargo.toml里还有下面两行配置:

[profile.release]
panic = 'unwind'

它和 catch_unwind 一起使用可以捕获某个线程内panic抛出的异常,常用的场景有:

  • 在其它编程语言中嵌入Rust;
  • 自定义线程处理的逻辑;
  • 测试框架,因为测试用例可以panic,但是不能中断测试的运行。

具体请参考 Controlling panics with std::panic

workspace build.rs

自定义的构建脚本放置在项目的build.rs文件内,可以在编译构建项目之前,让Cargo去编译和执行该脚本,使用场景有:

  • 编译、连接第三方的非Rust代码;
  • 构建之前的代码生成功能。

node-template根目录下的build.rs的具体功能,参考下面的注释,

use vergen::{ConstantsFlags, generate_cargo_keys};

const ERROR_MSG: &str = "Failed to generate metadata files";

fn main() {
    // 使用vergen生成环境变量,供项目中的env!宏获取
  // 这里设置了VERGEN_SHA_SHORT为Git最新的commit id的缩写
    generate_cargo_keys(ConstantsFlags::SHA_SHORT).expect(ERROR_MSG);

  // 当.git/HEAD文件改变即切换Git分支时,重新执行这一脚本
    build_script_utils::rerun_if_git_head_changed();
}

scripts/init.sh

初始化编译环境,包括升级Rust的版本,包括nightly和stable两个 发布渠道

rustup update nightly
rustup update stable

并且添加构建WebAssembly的支持工具:

rustup target add wasm32-unknown-unknown --toolchain nightly

定期执行本脚本,可以解决一些常见的编译问题如某个依赖安装失败。

pallets

包含了自定义的runtime模块,默认只有一个template模块,以此模块为例:

cargo.toml 包含 :

  • package的基本信息如name,version,authors等。

  • package所依赖的第三方库,以 frame-support 为例,来源是Github上该代码仓库的某个commit id,并将default-features设置为false(即不使用默认的feature进行编译)。

[dependencies.frame-support]
  default-features = false
  git = 'https://github.com/paritytech/substrate.git'
  rev = '013c1ee167354a08283fb69915fda56a62fee943'
  version = '2.0.0-alpha.3'
  • 通过 feature 进行条件编译,当使用Cargo进行构建时,下面的配置表示默认使用std feature,当编译依赖库如frame-support也默认使用std feature。这样的配置保证了runtime模块既可以编译为Native执行版本(使用std feature),也可以编译为Wasm执行版本(使用no_std feature,并由 WasmBuilder 进行编译,后面会详细介绍)。
[features]
default = ['std']
std = [
    'codec/std',
    'frame-support/std',
    'safe-mix/std',
    'system/std',
]

说明:Substrate为了保证应用的安全和稳定,对runtime有意地添加了一个约束,也就是在runtime代码里只能使用Rust的核心库及一些辅助库,而不能使用 标准库 。使用标准库会导致Wasm执行版本编译失败。

src/lib.rs是runtime模块的具体功能实现:

  • #![cfg_attr(not(feature = "std"), no_std)] 表示编译时如果feature不是std,那么必须是no_std。

  • mock和test模块只在运行测试时进行编译;

  • 定义了模块的接口,继承自system模块的接口,并添加了一个关联类型Event,这个Event类型可以转换成system模块下的Event,也可以由当前的template模块定义的的Event转换而来;

pub trait Trait: system::Trait {
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
  • 使用decl_storage宏定义模块的存储单元;
  • 使用decl_event宏定义模块可能触发的事件;
  • 使用decl_error宏定义模块可以返回的错误种类;
  • 最后通过decl_module宏定义了本模块的核心逻辑即可调用函数(Dispatchable Call),并初始化Error类型和Event的默认触发方式。

src/mock.rs是为测试用例服务的,

  • pub struct Test; 创建了一个测试用的runtime结构体;
  • 通过 impl_outer_origin 宏为runtime构造了一个Origin类型,用来标识交易的来源;
  • 通过 parameter_types 宏生成一些后面功能模块所需的满足 Get 接口的数据类型;
  • 为runtime实现各个功能模块接口,这里使用了大量的 () 来mock不关心的数据类型;
impl Trait for Test {
    type Event = ();
}
pub type TemplateModule = Module<Test>;
new_test_ext

src/tests.rs包含了所有的测试用例,

  • 引入mock数据和断言;
  • 通过 #[test] 来标识测试函数;
  • 调用 new_test_ext ,并通过 execute_with 执行closure内的代码;
  • 调用template模块的可调用函数,并返回执行结果 TemplateModule::do_something(Origin::signed(1)
  • assert_ok 断言结果是Ok, assert_eq 断言结果等于预期, assert_noop 断言结果为Error并且不修改链上存储状态。

runtime

cargo.toml除了上面提到的某个pallet对应的cargo.toml里的类似内容外,还添加了,

  • 构建脚本即build.rs的依赖 wasm-builder-runner

  • Substrate内置和开发者自定义的runtime模块(也叫做pallet),

[dependencies.sudo]
default-features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'pallet-sudo'
rev = '013c1ee167354a08283fb69915fda56a62fee943'
version = '2.0.0-alpha.3'

[dependencies.template]
default-features = false
package = 'pallet-template'
path = '../pallets/template'
version = '2.0.0-alpha.3'

build.rs使用 wasm-builder-runner 将当前的runtime项目编译为Wasm,编译后的文件位于 target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm

src/lib.rs就是构造我们链上runtime的入口,

  • #![cfg_attr(not(feature = "std"), no_std)] 表示编译时如果feature不是std,那么必须是no_std;
  • #![recursion_limit="256"] 用来设置编译期间可能出现无限递归操作(如宏展开)的最大阈值,默认是128,这里我们提高到256,来满足 consturct_runtime 宏的需求;
  • 使用std featue编译时,将生成的Wasm二进制内容通过常量的方式引入到当前runtime代码中。
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
  • 引入依赖的模块,以及为了下游模块方便调用而暴露上游模块的部分函数和数据类型,如 pub use balances::Call as BalancesCall
  • 引入template模块 pub use template
  • 给runtime所需的基础类型起别名,原则是和模块中的associate type名称一致,如 pub type BlockNumber = u32
  • opaque模块封装了一些用于CLI初始化时的类型,这些类型和runtime的具体信息;
  • 指定了runtime版本信息,当runtime协议修改之后,需要将 spec_version 加1; impl_version 是协议的实现版本,用来表示节点运行的代码是不同的,仅当非共识相关的优化发生时才可能修改这个值; RUNTIME_API_VERSIONS 包含已实现的runtime api的所有版本信息,由 impl_runtime_apis 宏生成,后面会进一步介绍。
pub const VERSION: RuntimeVersion = RuntimeVersion {
    spec_name: create_runtime_str!("node-template"),
    impl_name: create_runtime_str!("node-template"),
    authoring_version: 1,
    spec_version: 1,
    impl_version: 1,
    apis: RUNTIME_API_VERSIONS,
};
  • 定义区块时间相关的常量,如 pub const MILLISECS_PER_BLOCK: u64 = 6000 ,即每个区块是6秒,可以根据需要修改配置;

  • 指定当前的NativeVersion,在执行交易时会把NativeVersion和链上的RuntimeVersion进行比较,如果不一致,通常情况下会使用Wasm执行交易。

  • 使用 parameter_types 宏生成一些后面功能模块所需的满足 Get 接口的数据类型;

  • 为runtime的实现各个功能模块的接口,runtime由 construct_runtime 宏生成,

impl template::Trait for Runtime {
    type Event = Event;
}
  • construct_runtime 宏根据名称(如 TemplateModule )和所用的模块内的组件(如 template::{Module, Call, Storage, Event<T>} )来构造runtime,从而使模块中的信息通过metadata暴露出来,并且使该模块在runtime中可用。构造时,是按照顺序加载初始存储的,所以当B模块依赖A模块时,应当将A模块放在B之前。

  • 通过 impl_runtime_apis 宏实现runtime api定义的接口,这些接口需要通过 decl_runtime_apis 宏进行定义。

node

cargo.toml使用 [[bin]] 表示这个包是可执行的,通过 build-dependencies 引入编译时的依赖,在build.rs中使用,其它内容在前面的章节已经介绍过了。

build.rs的内容和workspace根目录下的build.rs相同。

src/main.rs是node-template编译成可执行程序的入口文件,

  • #![warn(missing_docs)] 在编译时,当模块缺少文档时会打印warnning;

  • 引入了当前目录下的其它代码模块,如 mod chain_spec

  • #[macro_use] 会加载引入的模块下的所有宏;

  • main函数是程序的入口,它返回一个 自定义的Result类型 ,在函数内首先构造了一个 VersionInfo 的结构体,用来保存可执行程序的版本信息,其中 VERGEN_SHA_SHORT 是在编译时由build.rs产生的,然后执行command模块提供的run函数。

src/command.rs提供了main所需的run函数,

  • 通过 from_args 解析命令行的执行参数,返回一个 Cli 结构体,具体参考下面src/cli.rs的内容;

  • 创建一个默认的 Substrate服务配置 ,这些服务包含启动线程运行网络、客户端和交易池等;

  • 如果返回的 Cli 实例里存在子命令,则执行子命令,执行子命令时,

  • 首先进行初始化,如设置panic的异常处理机制,日志等;

  • 通过 chain_spec::load_spec 获取chain的配置,来更新前面构造的Substrate服务配置;

  • 调用子命令的 run 函数来执行该命令,run函数依赖src/service.rs模块里的 new_full_start 宏来返回ServiceBuilder,包含了构建Substrate服务的多种组件。

  • 如果返回的 Cli 实例里没有子命令,则执行当前命令,

  • 首先初始化,和子命令的初始化功能一样;

  • 更新Substrate服务配置,比子命令的更新操作更全面,配置的所有属性都会更新;

  • 调用 run 来启动节点,需要传入全节点客户端的服务实例和轻节点客户端的服务实例,根据服务配置中的节点角色进行选择,启动完成后,保持运行直到接收到退出信号 SIGINT (即Ctrl+C)。

src/cli.rs借助 StructOpt 库将命令行参数解析为Cli结构体,包含:

  • 可选的子命令,如 purge-chain 清空本地存储, build-spec 创建一个spec.json的初始文件, revert 回滚链上状态等;
  • 命令行参数,如 --validator 开启验证人模式, --light 以轻客户端方式运行, --ws-port 9944 指定WebSocket监听的TCP端口,等等。编译node-template之后,可以通过 ./target/release/node-template -h 获取所有可用的子命令和参数,及其帮助信息。
#[derive(Debug, StructOpt)]
pub struct Cli {
    #[structopt(subcommand)]
    pub subcommand: Option<Subcommand>,

    #[structopt(flatten)]
    pub run: RunCmd,
}

chain_spec.rs构造了 ChainSpec ,它定义了链的可用配置,用来构造初始区块,

  • node-template提供了两种模式,通过命令行参数 --dev 指定开发者网络(Development),只有Alice是验证人; --local 指定本地测试网络(LocalTestnet),Alice和Bob是验证人;
  • 调用 ChainSpec::from_genesis 创建硬编码的ChainSpec;
  • 定义了 testnet_genesis 函数,传入验证人列表、root账户、存有余额的账户列表,构造出GenesisConfig。

service.rs提供了构造Substrate服务的帮助方法,

  • 使用 native_executor_instance 宏定义了一个结构体 Executor ,并且实现了 NativeExecutionDispatch 接口,即可以通过函数名称来调用该函数;

  • new_full_start 宏构建了一个ServiceBuilder,用来构造全节点服务,过程如下:

  • 调用 with_select_chain 设置链的生成策略,也就是当链出现分叉的时候,选择哪个链继续工作,这里使用最长链原则;

  • 调用 with_transaction_pool 设置交易池类型,这里使用BasicPool;

  • 调用 with_import_queue 设置了导入区块所需的队列,这里使用BasicQueue,可以顺序地导入block。

  • 使用 new_full 构建一个全节点服务,

  • 使用 new_full_start 宏构建了一个ServiceBuilder;

  • 调用 with_finality_proof_provider 设置使用何种策略提供最终性验证;

  • 调用 build 构建真正的 Substrate Service

  • 如果是验证人并且不是哨兵模式,调用service的 spawn_essential_task 函数,启动用于生成区块的后台任务,使用的是Aura算法;

  • 如果GRANDPA功能没有关闭,调用 spawn_essential_task 开启后台运行的投票任务。

  • 使用 new_light 构建一个轻节点服务,

  • 调用 with_select_chain 设置跟随最长链;

  • 调用 with_transaction_pool 设置BasicPool交易池类型;

  • 调用 with_import_queue_and_fprb 设置区块导入时所用的队列,以及用来构建最终性验证请求的 FinalityProofRequestBuilder

  • 调用 with_finality_proof_provider 设置使用何种策略提供最终性验证;

  • 调用 build 构建真正的 Substrate Service

总结

通过本文,我相信你已经对node-template项目的代码有了比较深入的了解,现在你可以试着调节runtime代码中的一些配置参数如出块时间,自定义一条区块链。

更多

Substrate官方文档: https://substrate.dev/​substrate.dev

Parity介绍: https://www.parity.io/​www.parity.io

Substrate源码: https://github.com/paritytech/substrate​github.com

Polkadot源码: https://github.com/paritytech/polkadot​github.com

通过本文,你会了解到,

  • Substrate node-template的组成部分,及各部分的功能简介
  • 参数如何配置

Substrate作为一个标准的区块链开发框架,不仅提供了必备的底层公共组件(如数据库、共识、P2P、交易池)和通用的runtime模块(如资产相关的balances,治理相关的democracy等),还提供了将各个功能组件连接起来的节点模板程序( node-template )和节点程序( node )。本文主要介绍node-template中各个代码块的功能。

文件目录

在使用类Unix操作系统的情况下,进入node-template文件目录,执行 tree -I target 命令,获取详细的文件信息如下:

Substrate代码导读:node-template

这里我们忽略了target文件目录下的内容,来较少干扰性的输出。

workspace cargo.toml

node-template是一个标准的 Rust workspace 项目,当项目比较复杂时,使用workspace可以清晰地管理组件库(library)和可执行程序(binary)。在项目根目录的cargo.toml文件里有:

[workspace]
members = [
    'node',
    'pallets/template',
    'runtime',
]

这个workspace的成员有node、pallets/template、runtime,其中node是可执行程序,在对应的src/main.rs文件内拥有一个可执行的main函数入口;pallets/template和runtime是组件库,在src/lib.rs定义了可被外部使用的函数和数据结构。

细心的同学会注意到cargo.toml里还有下面两行配置:

[profile.release]
panic = 'unwind'

它和 catch_unwind 一起使用可以捕获某个线程内panic抛出的异常,常用的场景有:

  • 在其它编程语言中嵌入Rust;
  • 自定义线程处理的逻辑;
  • 测试框架,因为测试用例可以panic,但是不能中断测试的运行。

具体请参考 Controlling panics with std::panic

workspace build.rs

自定义的构建脚本放置在项目的build.rs文件内,可以在编译构建项目之前,让Cargo去编译和执行该脚本,使用场景有:

  • 编译、连接第三方的非Rust代码;
  • 构建之前的代码生成功能。

node-template根目录下的build.rs的具体功能,参考下面的注释,

use vergen::{ConstantsFlags, generate_cargo_keys};

const ERROR_MSG: &str = "Failed to generate metadata files";

fn main() {
    // 使用vergen生成环境变量,供项目中的env!宏获取
  // 这里设置了VERGEN_SHA_SHORT为Git最新的commit id的缩写
    generate_cargo_keys(ConstantsFlags::SHA_SHORT).expect(ERROR_MSG);

  // 当.git/HEAD文件改变即切换Git分支时,重新执行这一脚本
    build_script_utils::rerun_if_git_head_changed();
}

scripts/init.sh

初始化编译环境,包括升级Rust的版本,包括nightly和stable两个 发布渠道

rustup update nightly
rustup update stable

并且添加构建WebAssembly的支持工具:

rustup target add wasm32-unknown-unknown --toolchain nightly

定期执行本脚本,可以解决一些常见的编译问题如某个依赖安装失败。

pallets

包含了自定义的runtime模块,默认只有一个template模块,以此模块为例:

cargo.toml 包含 :

  • package的基本信息如name,version,authors等。

  • package所依赖的第三方库,以 frame-support 为例,来源是Github上该代码仓库的某个commit id,并将default-features设置为false(即不使用默认的feature进行编译)。

[dependencies.frame-support]
  default-features = false
  git = 'https://github.com/paritytech/substrate.git'
  rev = '013c1ee167354a08283fb69915fda56a62fee943'
  version = '2.0.0-alpha.3'
  • 通过 feature 进行条件编译,当使用Cargo进行构建时,下面的配置表示默认使用std feature,当编译依赖库如frame-support也默认使用std feature。这样的配置保证了runtime模块既可以编译为Native执行版本(使用std feature),也可以编译为Wasm执行版本(使用no_std feature,并由 WasmBuilder 进行编译,后面会详细介绍)。
[features]
default = ['std']
std = [
    'codec/std',
    'frame-support/std',
    'safe-mix/std',
    'system/std',
]

说明:Substrate为了保证应用的安全和稳定,对runtime有意地添加了一个约束,也就是在runtime代码里只能使用Rust的核心库及一些辅助库,而不能使用 标准库 。使用标准库会导致Wasm执行版本编译失败。

src/lib.rs是runtime模块的具体功能实现:

  • #![cfg_attr(not(feature = "std"), no_std)] 表示编译时如果feature不是std,那么必须是no_std。

  • mock和test模块只在运行测试时进行编译;

  • 定义了模块的接口,继承自system模块的接口,并添加了一个关联类型Event,这个Event类型可以转换成system模块下的Event,也可以由当前的template模块定义的的Event转换而来;

pub trait Trait: system::Trait {
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
  • 使用decl_storage宏定义模块的存储单元;
  • 使用decl_event宏定义模块可能触发的事件;
  • 使用decl_error宏定义模块可以返回的错误种类;
  • 最后通过decl_module宏定义了本模块的核心逻辑即可调用函数(Dispatchable Call),并初始化Error类型和Event的默认触发方式。

src/mock.rs是为测试用例服务的,

  • pub struct Test; 创建了一个测试用的runtime结构体;
  • 通过 impl_outer_origin 宏为runtime构造了一个Origin类型,用来标识交易的来源;
  • 通过 parameter_types 宏生成一些后面功能模块所需的满足 Get 接口的数据类型;
  • 为runtime实现各个功能模块接口,这里使用了大量的 () 来mock不关心的数据类型;
impl Trait for Test {
    type Event = ();
}
pub type TemplateModule = Module<Test>;
new_test_ext

src/tests.rs包含了所有的测试用例,

  • 引入mock数据和断言;
  • 通过 #[test] 来标识测试函数;
  • 调用 new_test_ext ,并通过 execute_with 执行closure内的代码;
  • 调用template模块的可调用函数,并返回执行结果 TemplateModule::do_something(Origin::signed(1)
  • assert_ok 断言结果是Ok, assert_eq 断言结果等于预期, assert_noop 断言结果为Error并且不修改链上存储状态。

runtime

cargo.toml除了上面提到的某个pallet对应的cargo.toml里的类似内容外,还添加了,

  • 构建脚本即build.rs的依赖 wasm-builder-runner

  • Substrate内置和开发者自定义的runtime模块(也叫做pallet),

[dependencies.sudo]
default-features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'pallet-sudo'
rev = '013c1ee167354a08283fb69915fda56a62fee943'
version = '2.0.0-alpha.3'

[dependencies.template]
default-features = false
package = 'pallet-template'
path = '../pallets/template'
version = '2.0.0-alpha.3'

build.rs使用 wasm-builder-runner 将当前的runtime项目编译为Wasm,编译后的文件位于 target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm

src/lib.rs就是构造我们链上runtime的入口,

  • #![cfg_attr(not(feature = "std"), no_std)] 表示编译时如果feature不是std,那么必须是no_std;
  • #![recursion_limit="256"] 用来设置编译期间可能出现无限递归操作(如宏展开)的最大阈值,默认是128,这里我们提高到256,来满足 consturct_runtime 宏的需求;
  • 使用std featue编译时,将生成的Wasm二进制内容通过常量的方式引入到当前runtime代码中。
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
  • 引入依赖的模块,以及为了下游模块方便调用而暴露上游模块的部分函数和数据类型,如 pub use balances::Call as BalancesCall
  • 引入template模块 pub use template
  • 给runtime所需的基础类型起别名,原则是和模块中的associate type名称一致,如 pub type BlockNumber = u32
  • opaque模块封装了一些用于CLI初始化时的类型,这些类型和runtime的具体信息;
  • 指定了runtime版本信息,当runtime协议修改之后,需要将 spec_version 加1; impl_version 是协议的实现版本,用来表示节点运行的代码是不同的,仅当非共识相关的优化发生时才可能修改这个值; RUNTIME_API_VERSIONS 包含已实现的runtime api的所有版本信息,由 impl_runtime_apis 宏生成,后面会进一步介绍。
pub const VERSION: RuntimeVersion = RuntimeVersion {
    spec_name: create_runtime_str!("node-template"),
    impl_name: create_runtime_str!("node-template"),
    authoring_version: 1,
    spec_version: 1,
    impl_version: 1,
    apis: RUNTIME_API_VERSIONS,
};
  • 定义区块时间相关的常量,如 pub const MILLISECS_PER_BLOCK: u64 = 6000 ,即每个区块是6秒,可以根据需要修改配置;

  • 指定当前的NativeVersion,在执行交易时会把NativeVersion和链上的RuntimeVersion进行比较,如果不一致,通常情况下会使用Wasm执行交易。

  • 使用 parameter_types 宏生成一些后面功能模块所需的满足 Get 接口的数据类型;

  • 为runtime的实现各个功能模块的接口,runtime由 construct_runtime 宏生成,

impl template::Trait for Runtime {
    type Event = Event;
}
  • construct_runtime 宏根据名称(如 TemplateModule )和所用的模块内的组件(如 template::{Module, Call, Storage, Event<T>} )来构造runtime,从而使模块中的信息通过metadata暴露出来,并且使该模块在runtime中可用。构造时,是按照顺序加载初始存储的,所以当B模块依赖A模块时,应当将A模块放在B之前。

  • 通过 impl_runtime_apis 宏实现runtime api定义的接口,这些接口需要通过 decl_runtime_apis 宏进行定义。

node

cargo.toml使用 [[bin]] 表示这个包是可执行的,通过 build-dependencies 引入编译时的依赖,在build.rs中使用,其它内容在前面的章节已经介绍过了。

build.rs的内容和workspace根目录下的build.rs相同。

src/main.rs是node-template编译成可执行程序的入口文件,

  • #![warn(missing_docs)] 在编译时,当模块缺少文档时会打印warnning;

  • 引入了当前目录下的其它代码模块,如 mod chain_spec

  • #[macro_use] 会加载引入的模块下的所有宏;

  • main函数是程序的入口,它返回一个 自定义的Result类型 ,在函数内首先构造了一个 VersionInfo 的结构体,用来保存可执行程序的版本信息,其中 VERGEN_SHA_SHORT 是在编译时由build.rs产生的,然后执行command模块提供的run函数。

src/command.rs提供了main所需的run函数,

  • 通过 from_args 解析命令行的执行参数,返回一个 Cli 结构体,具体参考下面src/cli.rs的内容;

  • 创建一个默认的 Substrate服务配置 ,这些服务包含启动线程运行网络、客户端和交易池等;

  • 如果返回的 Cli 实例里存在子命令,则执行子命令,执行子命令时,

  • 首先进行初始化,如设置panic的异常处理机制,日志等;

  • 通过 chain_spec::load_spec 获取chain的配置,来更新前面构造的Substrate服务配置;

  • 调用子命令的 run 函数来执行该命令,run函数依赖src/service.rs模块里的 new_full_start 宏来返回ServiceBuilder,包含了构建Substrate服务的多种组件。

  • 如果返回的 Cli 实例里没有子命令,则执行当前命令,

  • 首先初始化,和子命令的初始化功能一样;

  • 更新Substrate服务配置,比子命令的更新操作更全面,配置的所有属性都会更新;

  • 调用 run 来启动节点,需要传入全节点客户端的服务实例和轻节点客户端的服务实例,根据服务配置中的节点角色进行选择,启动完成后,保持运行直到接收到退出信号 SIGINT (即Ctrl+C)。

src/cli.rs借助 StructOpt 库将命令行参数解析为Cli结构体,包含:

  • 可选的子命令,如 purge-chain 清空本地存储, build-spec 创建一个spec.json的初始文件, revert 回滚链上状态等;
  • 命令行参数,如 --validator 开启验证人模式, --light 以轻客户端方式运行, --ws-port 9944 指定WebSocket监听的TCP端口,等等。编译node-template之后,可以通过 ./target/release/node-template -h 获取所有可用的子命令和参数,及其帮助信息。
#[derive(Debug, StructOpt)]
pub struct Cli {
    #[structopt(subcommand)]
    pub subcommand: Option<Subcommand>,

    #[structopt(flatten)]
    pub run: RunCmd,
}

chain_spec.rs构造了 ChainSpec ,它定义了链的可用配置,用来构造初始区块,

  • node-template提供了两种模式,通过命令行参数 --dev 指定开发者网络(Development),只有Alice是验证人; --local 指定本地测试网络(LocalTestnet),Alice和Bob是验证人;
  • 调用 ChainSpec::from_genesis 创建硬编码的ChainSpec;
  • 定义了 testnet_genesis 函数,传入验证人列表、root账户、存有余额的账户列表,构造出GenesisConfig。

service.rs提供了构造Substrate服务的帮助方法,

  • 使用 native_executor_instance 宏定义了一个结构体 Executor ,并且实现了 NativeExecutionDispatch 接口,即可以通过函数名称来调用该函数;

  • new_full_start 宏构建了一个ServiceBuilder,用来构造全节点服务,过程如下:

  • 调用 with_select_chain 设置链的生成策略,也就是当链出现分叉的时候,选择哪个链继续工作,这里使用最长链原则;

  • 调用 with_transaction_pool 设置交易池类型,这里使用BasicPool;

  • 调用 with_import_queue 设置了导入区块所需的队列,这里使用BasicQueue,可以顺序地导入block。

  • 使用 new_full 构建一个全节点服务,

  • 使用 new_full_start 宏构建了一个ServiceBuilder;

  • 调用 with_finality_proof_provider 设置使用何种策略提供最终性验证;

  • 调用 build 构建真正的 Substrate Service

  • 如果是验证人并且不是哨兵模式,调用service的 spawn_essential_task 函数,启动用于生成区块的后台任务,使用的是Aura算法;

  • 如果GRANDPA功能没有关闭,调用 spawn_essential_task 开启后台运行的投票任务。

  • 使用 new_light 构建一个轻节点服务,

  • 调用 with_select_chain 设置跟随最长链;

  • 调用 with_transaction_pool 设置BasicPool交易池类型;

  • 调用 with_import_queue_and_fprb 设置区块导入时所用的队列,以及用来构建最终性验证请求的 FinalityProofRequestBuilder

  • 调用 with_finality_proof_provider 设置使用何种策略提供最终性验证;

  • 调用 build 构建真正的 Substrate Service

总结

通过本文,我相信你已经对node-template项目的代码有了比较深入的了解,现在你可以试着调节runtime代码中的一些配置参数如出块时间,自定义一条区块链。

更多

Substrate官方文档: https://substrate.dev/​substrate.dev

Parity介绍: https://www.parity.io/​www.parity.io

Substrate源码: https://github.com/paritytech/substrate​github.com

Polkadot源码: https://github.com/paritytech/polkadot​github.com

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 1天前
  • 阅读 ( 19 )
  • 学分 ( 5 )
  • 分类:Polkadot

以上所述就是小编给大家介绍的《Substrate代码导读:node-template》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Rails

Head First Rails

David Griffiths / O'Reilly Media / 2008-12-30 / USD 49.99

Figure its about time that you hop on the Ruby on Rails bandwagon? You've heard that it'll increase your productivity exponentially, and allow you to created full fledged web applications with minimal......一起来看看 《Head First Rails》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换