SETP5,Substrate Runtime 开发简述

简介

https://substrate.dev/recipes/runtime-api.html
https://crates.parity.io/sp_api/macro.decl_runtime_apis.html
https://crates.parity.io/sp_api/macro.impl_runtime_apis.html

Substrate runtime 宏

Runtime 模块开发常用的宏

  • frame_support::pallet 定义功能模块
  • pallet::config 定义配置接口
  • pallet::storage 存存储单元
  • pallet::event 事件
  • pallet::error 错误信息
  • pallet::call 包含可调用函数
  • pallet::hooks 区块不同时期的执行逻辑。
  • construct_runtime 添加到模块Runtime

常见的数据类型

  • StorageValue 单值类型
  • StorageMap 映射类型
  • StorageDoubleMap 双键映射
  • 举例:
#[pallet::storage]
#[pallet::getter(fn something)]
pub type Something<T> = StorageValue<_, u32>;

Call宏举例

  • Call 宏用来定义一个可调用的函数
#[pallet::call]
impl<T:Config> Pallet<T> {
    #[pallet::weight(10_000)]
    pub fn do_something(origin: OriginFor<T>, something: u32) -> DispathResultWithPostInfo {
        let who = ensure_signed(origin)?;
        
        // Update storage.
        <Something<T>>::pub(something);
        
        // Emit an event.
        Self::deposit_event(Event::SomethingStored(something, who));

        Ok(().into())
    }
}

event 宏

  • 区块链是一个异步系统,runtime 通过触发事件通知交易执行结果。
  • 举例:
#[pallet::event]
#[pallet::metadata(T::AccountId="AccountId")]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
    SomethingStored(u32, T::AccountId),
}

// -- snippet --
Self::deposit_event(Event::SomethingStored(something, who));

error 宏

  • 不能给他们添加数据。
  • 通过emtadata 暴露给客户端。
  • 错误发生时触发 system.ExtrinsicFailed 事件,包含了对应错误的索引信息。
  • 当可调用函数执行过程中发生错误是,通过error信息通知客户端。
  • 举例
#[pallet::error]
pub enum Error <T> {
    /// Error names should be descriptive.
    NoneValue,
    /// Error should have helpful documentation associated with them.
    StorageOverflow,
}

hooks 宏

  • Runtime 模块里存在保留函数,用于执行特定的逻辑:

on_initialize,在每个区块的开头执行。
on_finalize,在每个区块结束时执行。
offchain_worker 开头且是链外执行,不占用链上的资源。
on_runtime_upgrade 当有runtime 升级时才会执行,用来迁移数据。

construct_runtime 加载模块

举例:

impl pallet_template::Config for Runtime {
    type Event = Event;
}
construct_runtime! {
    pub enum Runtime where 
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic {
            // -- snippet --
            TemplateModule: pallet_template:: { Module, Call, Storage, Event<T> },
        }
}

区块链存储

区块链存储的特点

  • 开源可查,对等节点,引入延迟和随机来达到共识。
  • 链式、增量地存储数据。
  • 区块链应用的节点依赖于高效的键值对数据库,比如LevelDBRocksDb等。

区块链存储的限制

  • 大文件直接存储在链上的成本很高。
  • 链式区块存储结构,不利于对历史数据的索引(这也催生了区块链浏览器这种应用的存在)。
  • 另外一个约束是,在进行数值运算时不能使用浮点数,因为浮点数在不同编译环境下是不一致的。

开发链上存储单元的特点:

  • Rust 原生数据类型的子集,定义在核心库和 alloc 库中。
  • 原生类型构成的映射类型。
  • 满足一定的编解码条件。
  • 分为3大类“单值”、“简单映射”、“双键映射”

单值类型

  • 存储某种单一类型的值,入布尔、数值、枚举、结构体等。
  • 数值:u8, i8, u32, i32, u64, i64, u128, i128
  • 大整数:U128, U256, U512
  • 布尔:bool
  • 集合:Vec<T>, BTreeMap, BTreeSet
  • 定点小数:Percent, Permill, Perbill, FixedU128 他们的主要目的是取代浮点数。
  • 定长哈希:H128, H256, H512
  • 其他复杂类型:Option<T>, tuple, enum, struct
  • 内置自定义类型:Moment, AccountId (连上时间戳类型、账户ID类型)

数值类型u8的定义

  • 例子:
#[pallet::storage]
#[pallet::getter(fn my_unsigned_number)]
pub type MyUnsignedNumber<T> = StorageValue<_, u8>;

#[pallet::storage]
#[pallet::getter(fn my_signed_number)]
pub type MySignedNumber<T> = StorageValue<_, i8, ValueQuery>;

// ValueQuery 表示使用默认查询值,如果不填写这个值则使用 OptionQuery 那么如果为空会返回一个 Null
  • 可以使用的方法

增:MyUnsignedNumber::<T>::pub(number);
查:MyUnsignedNumber::<T>::get();
改:MyUnsignedNumber::<T>::mutate(|v|v+1);
删:MyUnsignedNumber::<T>::kill();

返回Result 类型:checked_add、checked_sub、checked_mul、checked_div

// 举例
my_unsigned_num.checked_add(10)?;

溢出返回饱和值:saturating_add, saturating_sub, saturating_mul

// 举例
my_unsigned_num.saturating_add(10000);

大整数 U256,U512类型定义:

use sp_core::U256;

#[pallet::storage]
#[pallet::getter(fn my_big_integer)]
pub type MyBigInteger<T> = StorageValue<_, 256>;

bool 类型定义

#[pallet::storage]
#[pallet::getter(fn my_bool)]
pub type MyBool<T> = StorageValue<_, bool>;
  • 对于 ValueQuery,默认值为 false

Vec<T> 类型定义:

use sp_std::prelude::* ;

#[pallet::storage]
#[pallet::getter(fn my_string)]
// 这种定义方式通常用于字符串,因为链上实际没有字符串。
pub type MyString<T> = StorageValue<_, Vec<u8>>; 

Percent, Permill, Perbill 类型定义

use sp_runtime::Permill;

#[pallet::storage]
#[pallet::getter(fn my_permill)]
pub type MyPermill<T> = StorageValue<_, Permil>; 
  • 构造方式

Permill::from_percent(value);
Permill::from_parts(value);
Permill::from_rational(p, q);

  • 计算

permill_one.saturating_mul(permill_two);
my_permill * 20000 as u32

Moment 时间类型定义

#[pallet::config] // 别忘了加上 pallet_timestamp::Config 约束
pub trait Config: pallet_timestamp::Config + frame_system::Config {
    // -- snippet --
}

#[pallet::storage]
#[pallet::getter(fn my_time)]
pub type MyTime<T: Config> = StorageValue<_, T::Moment>;
  • Moment 是 u64 的类型别名
  • 获取链上时间的方法:pallet_timestamp::Pallet::<T>::get()

AccountId 账户类型定义

#[pallet::storage]
#[pallet::getter(fn my_account_id)]
pub type MyAccountId<T: Config> = StorageValue<_, T::AccountId>;

  • 定义在 frame_system 中,通常是 Public key
  • 获取AccountId:`let sender = ensure_signed(origin)?

struct 类型定义

  • 这个需要注意的是,结构类型必须实现 Clone ,Encode, Decode , Default 接口。
  • 可以通过 dervie 属性宏实现如上的接口,例如:
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
pub struct People {
    name: Vec<u8>, 
    age: u8,
}

#[pallet::storage]
#[pallet::getter(fn my_struct)]
pub type MyStruct<T: Config> = StorageValue<_, People>;

enum 类型

简单映射类型 StorageMap

  • 用来保存键值对,单值类型都可以用作key或者value。
#[pallet::storage]
#[pallet::getter(fn my_map)]
pub type MyMap<T> = StorageMap<
    _,
    Blake2_128Concat,
    u8,
    Vec<u8>,
>
  • 其中第二个参数Blake2_128Concat 是key 的哈希算法。
  • Blake2_128Concat 密码学安全
  • Twox64Concat 非密码学安全,但是相对较快。
  • Identity 如果一个值已经是加密值,那么它本身就很安全,这是后Identity 可以避免无必要的计算从而让效率更高。
  • 基础操作包括

插入一个元素:MyMap::<T>::insert(key, value);
通过key获取value:MyMap::<T>::get(key);
删除某个key对应的元素:MyMap::remove(key);
覆盖或者修改某个key对应的元素 MyMap::insert(key, new_value); MyMap::mutate(key, |old_val| old_val+1);

双键映射类型 StorageDoubleMap

  • 使用两个key来索引value,用于快速删除key1对应的任意记录,也可遍历key1对应的所有记录,定义:
#[pallet::storage]
#[pallet::getter(fn my_double_map)]
pub type MyDoubleMap<T: Config> = StorageDoubleMap<
    _,
    Blake2_128Concat, // key1加密算法
    T::AccountId,   //key1
    Blake2_128Concat, // key2 加密算法
    u32,   //key2
    Vec<u8>, //存贮值。
>
  • 基础用法:

插入一个元素:MyDoubleMap::<T>::insert(key1,key2,value);
获取某一元素:MyDoubleMap::<T>::get(key1, key2);
删除某一元素:MyDoubleMap::<T>::remove(key1, key2);
删除 key1 对应的所有元素,MyDoubleMap::<T>::remove_prefix(key1)

存储的初始化

  • 创世区块的数据初始化
#[pallet::genesis_config]
pub struct GenesisConfig {
    pub value: u8,
}

#[cfg(feature = "std")]
impl Default for GenesisConifg<T> {
    fn default() -> Self {
        Self {value: Default::default() }
    }
}

#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
    fn build(&self) {
        MyValue::<T>::put(&self.value);
    }
}

开发中需要注意的事儿

1、最小化链上存储。(存储哈希值、设置列表容量等)
2、先校对在存储 Verify First, Write Last
3、事务管理 Transactional macro

结束

  • 感谢阅读
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容