md node 输出_玩转substrate-node-template(续)

b22a32583f07042923db38bad6869ce6.png

1、目录结构

使用命令行在工程目录下输入 tree -I target 命令,可以获取下详细的目录结构,makefile 是我自己创建的,为了方便调试

➜ substrate-drill git:(master) ✗ tree -I target.├── Cargo.lock├── Cargo.toml├── LICENSE├── Makefile├── README.md├── node│ ├── Cargo.toml│ ├── build.rs│ └── src│ ├── chain_spec.rs│ ├── cli.rs│ ├── command.rs│ ├── main.rs│ └── service.rs├── pallets│ ├── contracts│ └── template│ ├── Cargo.toml│ └── src│ ├── lib.rs│ ├── mock.rs│ └── tests.rs├── runtime│ ├── Cargo.toml│ ├── build.rs│ └── src│ └── lib.rs└── scripts└── init.sh9 directories, 20 files

2、Cargo

substrate-node-template 是一个Rust workspace 项目,workspace为了开发大型程序,分治crate用,一个workspace是一个共享公共依赖项解析 (具有共享 Cargo.lock),输出目录和各种设置,如配置文件,的一个或多个包的集合。

➜ substrate-drill git:(master) ✗ cat Cargo.toml[profile.release]panic = 'unwind'[workspace]members = ['node','pallets/template','runtime',]

这里指定了workspace有三个成员:node、pallets/template、runtime,其中node是可执行程序(node/src/main.rs), pallets/template和runtime是lib,在src/lib.rs定义了可被外部使用的函数和数据结构。

[profile.release]panic = 'unwind'

cargo 可以通过配置profile 去配置rustc编译选项(Profiles provide a way to alter the compiler settings, influencing things like optimizations and debugging symbols)

profile内置了4种:

  • dev
  • release
  • test
  • bench

这里设定了当运行 cargo build --release 时 rustc会自动附上 -C panic flag , 'unwind'的意思是 Unwind the stack upon panic, 这样就可以用 std::panic::catch_unwind 捕捉 panic,类似Go里的recover

详细的文档可以查看这里:

  • https://doc.rust-lang.org/beta/cargo/reference/profiles.html
  • https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html
  • https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#panic

3、Build.rs

在node和runtime目录下都能发现有一个build.rs文件,这个是一个构建脚本,构建脚本只是另一个 Rust 文件,此文件将在编译包中的任何其他内容之前,优先进行编译和调用。

node/build.rs

// 主要是用了一个第三方lib -- vergen // vergen是利用build scripts脚本,在编译期间生成环境变量的包。 我们可以通过env!宏使用vergen生成的如下变量:// VERGEN_BUILD_TIMESTAMP// VERGEN_BUILD_DATE// VERGEN_SHA// VERGEN_SHA_SHORT// VERGEN_TARGET_TRIPLE// VERGEN_SEMVER// VERGEN_SEMVER_LIGHTWEIGHT// 提供了git版本、编译时间、运行机器信息、软件版本use vergen::{ConstantsFlags, generate_cargo_keys};const ERROR_MSG: &str = "Failed to generate metadata files";fn main() {// 这里设置了VERGEN_SHA_SHORT为Git最新的commit id的缩写generate_cargo_keys(ConstantsFlags::SHA_SHORT).expect(ERROR_MSG);// 如果.git/HEAD文件发生改变(比如切分支),重新触发build.rs脚本build_script_utils::rerun_if_git_head_changed();}

这里比较好玩的是: 构建脚本可以通过打印到标准输出流(stdout)里是可以和cargo 进行“交流”

rerun_if_git_head_changed 源码:

pub fn rerun_if_git_head_changed() {let mut manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is always set by cargo."));let manifest_dir_copy = manifest_dir.clone();while manifest_dir.parent().is_some() {if manifest_dir.join(".git/HEAD").exists() {println!("cargo:rerun-if-changed={}", manifest_dir.join(".git/HEAD").display()); // 这里会告诉cargo 如果.git/HEAD发生改变,就重新执行构建脚本return}manifest_dir.pop();}println!("cargo:warning=Could not find `.git/HEAD` searching from `{}` upwards!", manifest_dir_copy.display(),); // 这里告诉cargo 输出警告}

当执行构建的时候会在 target/<profile>/build/<pkg>/output 保存这些“交流“

target/release/build/node-template-0cf8392394c631fa/output

cargo:rustc-env=VERGEN_SHA_SHORT=c18df68cargo:rerun-if-changed=/Users/xib/Desktop/workspaces/rust/substrate-driil/.git/HEAD

cargo:rerun-if-changed:

cargo仅使用文件系统上次修改的“ mtime”时间戳来确定文件是否已更改,如果给定路径下的文件已更改,则重新运行构建脚本

相关文档可以可以查看:https://doc.rust-lang.org/beta/cargo/reference/build-scripts.html

runtime/build.rs

use wasm_builder_runner::WasmBuilder;fn main() {WasmBuilder::new().with_current_project().with_wasm_builder_from_crates("1.0.9").export_heap_base().import_memory().build()}

将 runtime编译成 WASM binary

编译后会输出到 target/release/wbuild,wasm的文件会在target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm

4、pallets

这个文件夹下主要是放一些自定义的runtime module

Substrate runtime 是可以由好多runtime module组成,每一个runtime module都可以是独立个功能点

这里以pallets/template为例, 目录结构为:

➜ pallets git:(master) ✗ cd template➜ template git:(master) ✗ tree -I target.├── Cargo.toml└── src├── lib.rs├── mock.rs└── tests.rs1 directory, 4 files

查看下cargo会发现有一些依赖配置:

➜ template git:(master) ✗ cat Cargo.toml# ....[dependencies.frame-support]default-features = falseversion = '2.0.0-alpha.5'# ....[dev-dependencies.sp-runtime]default-features = false #### 不使用默认的feature进行编译version = '2.0.0-alpha.5'[features]default = ['std']std = ['codec/std','frame-support/std','safe-mix/std','system/std',]

通过feature进行条件编译,当使用Cargo进行构建时,features default = ['std']配置表示默认使用std feature,当编译依赖库如frame-support也默认使用std feature。这样的配置保证了runtime模块既可以编译为Native执行版本(使用std feature),也可以编译为wasm执行版本(使用no_std feature,并由WasmBuilder进行编译)。

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

5、Runtime简介

写一个runtime module可以分为6个步骤:

  1. Imports 导入外部依赖
  2. Pallet Configuration 定义Pallet trait
  3. Pallet Events 定义Pallet Event类型
  4. Pallet Errors 定义错误类型
  5. Pallet Storage Items 定义存储单元
  6. Callable Pallet Functions 定义可以调用的函数,初始化Error和Event

pallets/template/src/lib.rs 是runtime module组的具体功能实现:

Imports 导入外部依赖

#![cfg_attr(not(feature = "std"), no_std)] // 表示编译时如果feature不是std,那么必须是no_std。// 1. Importsuse frame_support::{decl_module, decl_storage, decl_event, decl_error, dispatch};use system::ensure_signed;

Pallet Configuration 定义Pallet trait

/// The pallet's configuration trait.pub trait Trait: system::Trait {// Add other types and constants required to configure this pallet./// The overarching event type.type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;}

Pallet Events 定义Pallet Event类型

// The pallet's eventsdecl_event!(pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {/// Just a dummy event./// Event `Something` is declared with a parameter of the type `u32` and `AccountId`/// To emit this event, we call the deposit function, from our runtime functionsSomethingStored(u32, AccountId),});

熟悉Rust中宏的概念都应该知道:在这里decl_event!宏中的代码,有些不是标准的Rust语法,而是Substrade扩展后的语法, 我们可以展开这个宏,查看最终生成的标准生成的Rust代码,我们可以发现,会生成RawEvent枚举,然后使用宏中指定的特征trait将事件event类型生成为RawEvent的具体实现

这里宏展开后生成的RawEventEvent类型:

pub type Event<T> = RawEvent<<T as system::Trait>::AccountId>;/// Events for this module.///pub enum RawEvent<AccountId> {#[doc = r" Just a dummy event."]#[doc =r" Event `Something` is declared with a parameter of the type `u32` and `AccountId`"]#[doc =r" To emit this event, we call the deposit function, from our runtime functions"]SomethingStored(u32, AccountId),}

Pallet Errors 定义错误类型

// The pallet's errorsdecl_error! {pub enum Error for Module<T: Trait> {/// Value was NoneNoneValue,/// Value reached maximum and cannot be incremented furtherStorageOverflow,}}

这个也是和上述Event类似,展开宏后可以看到:

pub enum Error<T: Trait> {#[doc(hidden)]__Ignore(::frame_support::sp_std::marker::PhantomData<(T,)>,::frame_support::dispatch::Never),#[doc = r" Value was None"]NoneValue,#[doc = r" Value reached maximum and cannot be incremented further"]StorageOverflow,}

Pallet Storage Items 定义存储单元

// This pallet's storage items.decl_storage! {// It is important to update your storage name so that your pallet's// storage items are isolated from other pallets.// ---------------------------------vvvvvvvvvvvvvvtrait Store for Module<T: Trait> as TemplateModule {// Just a dummy storage item.// Here we are declaring a StorageValue, `Something` as a Option<u32>// `get(fn something)` is the default getter which returns either the stored `u32` or `None` if nothing storedSomething get(fn something): Option<u32>;}}

宏展开后:

#[doc(hidden)]mod sp_api_hidden_includes_decl_storage {pub extern crate frame_support as hidden_include;// It is important to update your storage name so that your pallet's// storage items are isolated from other pallets.// ---------------------------------vvvvvvvvvvvvvv// Just a dummy storage item.// Here we are declaring a StorageValue, `Something` as a Option<u32>// `get(fn something)` is the default getter which returns either the stored `u32` or `None` if nothing stored}trait Store {type Something;}impl <T: Trait + 'static> Store for Module<T> {type Something = Something<>;}impl <T: Trait + 'static> Module<T> {pub fn something() -> Option<u32> {<Something<> asself::sp_api_hidden_includes_decl_storage::hidden_include::storage::StorageValue<u32>>::get()}}

6、Storage 简介

在Substrate Runtime里主要 Storage Items 主要有四种:

  • Storage Value ---- 单一值 Implements the StorageValue trait using the StorageValue generator
  • Storage Map ---- KV hash map Implements the StorageMap trait using the StorageMap generator. And StoragePrefixedMap
  • Storage Double Map ----- 有两个key的map Implements the StorageDoubleMap trait using the StorageDoubleMap generator. And StoragePrefixedMap

举个栗子:

decl_storage! {trait Store for Module<T: Trait> as Example {SomeValue get(fn something): Option<u32>;;SomeMap: map hasher($hash) type => type;SomeDoubleMap: double_map hasher($hash1) u32, hasher($hash2) u32 => u32;Foo get(fn foo) config(): u32=12;}}

$hash 代表Hashable中可用的哈希算法选择,需要选择一种:

  • blake2_128_concat
  • twox_64_concat
  • identity

上述的storage 可以像这样扩展:

#vis #name get(fn #getter) config(#field_name) build(#closure): #type = #default;

  • #vis: Set the visibility of the structure. pub or nothing.
  • #name: Name of the storage item, used as a prefix in storage.
  • [optional] get(fn #getter): Implements the function #getter to Module.
  • [optional] config(#field_name): field_name is optional if get is set. Will include the item in GenesisConfig.
  • [optional] build(#closure): Closure called with storage overlays.
  • #type: Storage type.
  • [optional] #default: Value returned when none

7、Module定义方法

Callable Pallet Functions

// The pallet's dispatchable functions.decl_module! {/// The module declaration.pub struct Module<T: Trait> for enum Call where origin: T::Origin {// ....}}

Module结构体是每个Substrate runtime module 的主干,由Substrate提供的宏decl_module!生成。同时开发人员在编写自己的runtime module时,可以为Module定义跟自己业务相关的函数和实现。

宏展开后,最终生成标准Rust语法的Module结构体定义如下:

#[doc = r" The module declaration."]pub struct Module<T: Trait>(::frame_support::sp_std::marker::PhantomData<(T,)>);

最后,Substrate在runtime/lib.rs中使用construct_runtime!宏将整个Module结构体导入区块链的运行时。这个宏将自定义的模块和所有其他模块包含在一个名为AllModules的元组中。运行时的Executive模块,使用此元组来处理执行这些模块的编排

8、runtime

这个是链的运行时,集成pallets下的runtime module和外部runtime module

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

使用std featue编译时,将生成的Wasm二进制内容通过常量的方式引入到当前runtime代码中

// ...// Make the WASM binary available.#[cfg(feature = "std")]include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));// ...

引入依赖的模块,以及为了下游模块方便调用而暴露上游模块的部分函数和数据类型

// ...// A few exports that help ease life for downstream crates.#[cfg(any(feature = "std", test))]pub use sp_runtime::BuildStorage;pub use timestamp::Call as TimestampCall;pub use balances::Call as BalancesCall;pub use sp_runtime::{Permill, Perbill};// -----------------/// add Gas typepub use contracts::Gas; // 这个是我自己加的// -----------------// ..../// Importing a template pallet //引入template模块pub use template;// ....

Substrate中,Call枚举列出运行时模块公开的可分派函数。每个模块都有自己的Call枚举,其中包含该模块的函数名称和参数。然后,在构造运行时,会生成一个外部Call枚举,作为每个模块特定Call的聚合

运行时中每个模块生成的Call枚举,Substrate会将此枚举传递给construct_runtime!宏用于生成外部Call枚举,该枚举列出所有运行时模块并引用了它们各自的Call对象。示例如下:

construct_runtime!(pub enum Runtime whereBlock = Block,NodeBlock = opaque::Block,UncheckedExtrinsic = UncheckedExtrinsic{System: system::{Module, Call, Config, Storage, Event<T>},RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage},Timestamp: timestamp::{Module, Call, Storage, Inherent},Aura: aura::{Module, Config<T>, Inherent(Timestamp)},Grandpa: grandpa::{Module, Call, Storage, Config, Event},Balances: balances::{Module, Call, Storage, Config<T>, Event<T>},TransactionPayment: transaction_payment::{Module, Storage},Sudo: sudo::{Module, Call, Config<T>, Storage, Event<T>},// Used for the module template in `./template.rs`TemplateModule: template::{Module, Call, Storage, Event<T>},/*** Add This Line ***/Contracts: contracts::{Module, Call, Config<T>, Storage, Event<T>},});

展开宏后会:

pub enum Call {System(::frame_support::dispatch::CallableCallFor<System, Runtime>),RandomnessCollectiveFlip(::frame_support::dispatch::CallableCallFor<RandomnessCollectiveFlip,Runtime>),Timestamp(::frame_support::dispatch::CallableCallFor<Timestamp, Runtime>),Grandpa(::frame_support::dispatch::CallableCallFor<Grandpa, Runtime>),Balances(::frame_support::dispatch::CallableCallFor<Balances, Runtime>),Sudo(::frame_support::dispatch::CallableCallFor<Sudo, Runtime>),TemplateModule(::frame_support::dispatch::CallableCallFor<TemplateModule,Runtime>),Contracts(::frame_support::dispatch::CallableCallFor<Contracts, Runtime>),}

外部Call枚举收集了construct_runtime!宏中的所有模块暴露的Call枚举,因此,它定义了substrate中完整的公开可调度函数集

最后,当运行Substrate节点时,它将自动生成一个getMetadata API,其中包含运行时生成的对象。这可以用于生成JavaScript函数,允许将调用分派给运行时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值