簡(jiǎn)介
- cargo expand 將宏里的代碼展開(kāi),得到Rust 的標(biāo)準(zhǔn)與法可以參考 https://github.com/dtolnay/cargo-expand
- pallet 屬性宏參考文檔 https://crates.parity.io/frame_support/attr.pallet.html
- decl_runtime_apis & impl_runtime_apis,定義參考文檔
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 模塊開(kāi)發(fā)常用的宏
- frame_support::pallet 定義功能模塊
- pallet::config 定義配置接口
- pallet::storage 存存儲(chǔ)單元
- pallet::event 事件
- pallet::error 錯(cuò)誤信息
- pallet::call 包含可調(diào)用函數(shù)
- pallet::hooks 區(qū)塊不同時(shí)期的執(zhí)行邏輯油猫。
- construct_runtime 添加到模塊Runtime
常見(jiàn)的數(shù)據(jù)類(lèi)型
- StorageValue 單值類(lèi)型
- StorageMap 映射類(lèi)型
- StorageDoubleMap 雙鍵映射
- 舉例:
#[pallet::storage]
#[pallet::getter(fn something)]
pub type Something<T> = StorageValue<_, u32>;
Call宏舉例
- Call 宏用來(lái)定義一個(gè)可調(diào)用的函數(shù)
#[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 宏
- 區(qū)塊鏈?zhǔn)且粋€(gè)異步系統(tǒng),runtime 通過(guò)觸發(fā)事件通知交易執(zhí)行結(jié)果朵夏。
- 舉例:
#[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 宏
- 不能給他們添加數(shù)據(jù)。
- 通過(guò)emtadata 暴露給客戶端。
- 錯(cuò)誤發(fā)生時(shí)觸發(fā) system.ExtrinsicFailed 事件,包含了對(duì)應(yīng)錯(cuò)誤的索引信息白修。
- 當(dāng)可調(diào)用函數(shù)執(zhí)行過(guò)程中發(fā)生錯(cuò)誤是,通過(guò)error信息通知客戶端重斑。
- 舉例
#[pallet::error]
pub enum Error <T> {
/// Error names should be descriptive.
NoneValue,
/// Error should have helpful documentation associated with them.
StorageOverflow,
}
hooks 宏
- Runtime 模塊里存在保留函數(shù)兵睛,用于執(zhí)行特定的邏輯:
on_initialize,在每個(gè)區(qū)塊的開(kāi)頭執(zhí)行绸狐。
on_finalize卤恳,在每個(gè)區(qū)塊結(jié)束時(shí)執(zhí)行累盗。
offchain_worker 開(kāi)頭且是鏈外執(zhí)行寒矿,不占用鏈上的資源。
on_runtime_upgrade 當(dāng)有runtime 升級(jí)時(shí)才會(huì)執(zhí)行若债,用來(lái)遷移數(shù)據(jù)符相。
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> },
}
}
區(qū)塊鏈存儲(chǔ)
區(qū)塊鏈存儲(chǔ)的特點(diǎn)
- 開(kāi)源可查,對(duì)等節(jié)點(diǎn)蠢琳,引入延遲和隨機(jī)來(lái)達(dá)到共識(shí)啊终。
- 鏈?zhǔn)健⒃隽康卮鎯?chǔ)數(shù)據(jù)傲须。
- 區(qū)塊鏈應(yīng)用的節(jié)點(diǎn)依賴于高效的鍵值對(duì)數(shù)據(jù)庫(kù)蓝牲,比如
LevelDB
,RocksDb
等泰讽。
區(qū)塊鏈存儲(chǔ)的限制
- 大文件直接存儲(chǔ)在鏈上的成本很高例衍。
- 鏈?zhǔn)絽^(qū)塊存儲(chǔ)結(jié)構(gòu)昔期,不利于對(duì)歷史數(shù)據(jù)的索引(這也催生了區(qū)塊鏈瀏覽器這種應(yīng)用的存在)。
- 另外一個(gè)約束是佛玄,在進(jìn)行數(shù)值運(yùn)算時(shí)不能使用浮點(diǎn)數(shù)硼一,因?yàn)楦↑c(diǎn)數(shù)在不同編譯環(huán)境下是不一致的。
開(kāi)發(fā)鏈上存儲(chǔ)單元的特點(diǎn):
- Rust 原生數(shù)據(jù)類(lèi)型的子集梦抢,定義在核心庫(kù)和 alloc 庫(kù)中般贼。
- 原生類(lèi)型構(gòu)成的映射類(lèi)型。
- 滿足一定的編解碼條件奥吩。
- 分為3大類(lèi)“單值”哼蛆、“簡(jiǎn)單映射”、“雙鍵映射”
單值類(lèi)型
- 存儲(chǔ)某種單一類(lèi)型的值圈驼,入布爾人芽、數(shù)值、枚舉绩脆、結(jié)構(gòu)體等萤厅。
- 數(shù)值:
u8, i8, u32, i32, u64, i64, u128, i128
- 大整數(shù):
U128, U256, U512
- 布爾:
bool
- 集合:
Vec<T>, BTreeMap, BTreeSet
- 定點(diǎn)小數(shù):
Percent, Permill, Perbill, FixedU128
他們的主要目的是取代浮點(diǎn)數(shù)。 - 定長(zhǎng)哈希:
H128, H256, H512
- 其他復(fù)雜類(lèi)型:
Option<T>, tuple, enum, struct
- 內(nèi)置自定義類(lèi)型:
Moment, AccountId
(連上時(shí)間戳類(lèi)型靴迫、賬戶ID類(lèi)型)
數(shù)值類(lèi)型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 表示使用默認(rèn)查詢值惕味,如果不填寫(xiě)這個(gè)值則使用 OptionQuery 那么如果為空會(huì)返回一個(gè) Null
- 可以使用的方法
增:MyUnsignedNumber::<T>::pub(number);
查:MyUnsignedNumber::<T>::get();
改:MyUnsignedNumber::<T>::mutate(|v|v+1);
刪:MyUnsignedNumber::<T>::kill();
更多API,請(qǐng)參考文檔:https://crates.parity.io/frame_support/pallet_prelude/struct.StorageValue.html
數(shù)值類(lèi)型 u8, i8, u32, i32, u64, i64, u128, i128 的安全操作
返回Result 類(lèi)型: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);
大整數(shù) U256主守,U512類(lèi)型定義:
use sp_core::U256;
#[pallet::storage]
#[pallet::getter(fn my_big_integer)]
pub type MyBigInteger<T> = StorageValue<_, 256>;
- 操作和基礎(chǔ)類(lèi)型差不多禀倔,checked_add, overflowing_mul
- 更多API,參考文檔:https://crates.parity.io/sp_core/struct.U256.html
bool 類(lèi)型定義
#[pallet::storage]
#[pallet::getter(fn my_bool)]
pub type MyBool<T> = StorageValue<_, bool>;
- 對(duì)于 ValueQuery参淫,默認(rèn)值為 false
Vec<T> 類(lèi)型定義:
use sp_std::prelude::* ;
#[pallet::storage]
#[pallet::getter(fn my_string)]
// 這種定義方式通常用于字符串救湖,因?yàn)殒溕蠈?shí)際沒(méi)有字符串。
pub type MyString<T> = StorageValue<_, Vec<u8>>;
- 操作:
push, pop, iter ...
參考文檔:https://doc.rust-lang.org/alloc/vec/struct.Vec.html - 對(duì)于 ValueQuery, 默認(rèn)值為 0x00
Percent, Permill, Perbill 類(lèi)型定義
use sp_runtime::Permill;
#[pallet::storage]
#[pallet::getter(fn my_permill)]
pub type MyPermill<T> = StorageValue<_, Permil>;
- 構(gòu)造方式
Permill::from_percent(value);
Permill::from_parts(value);
Permill::from_rational(p, q);
- 計(jì)算
permill_one.saturating_mul(permill_two);
my_permill * 20000 as u32
Moment 時(shí)間類(lèi)型定義
#[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 的類(lèi)型別名
- 獲取鏈上時(shí)間的方法:pallet_timestamp::Pallet::<T>::get()
AccountId 賬戶類(lèi)型定義
#[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 類(lèi)型定義
- 這個(gè)需要注意的是鞋既,結(jié)構(gòu)類(lèi)型必須實(shí)現(xiàn) Clone ,Encode, Decode , Default 接口。
- 可以通過(guò) dervie 屬性宏實(shí)現(xiàn)如上的接口耍铜,例如:
#[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 類(lèi)型
- 與結(jié)構(gòu)struct 類(lèi)似邑闺,但是還需要自己實(shí)現(xiàn) Default 接口,因?yàn)槠錈o(wú)法通過(guò) Derive實(shí)現(xiàn)棕兼。
- 參考文檔:https://github.com/kaichaosun/play-substrate/blob/master/pallets/data-type/src/lib.rs#L39
簡(jiǎn)單映射類(lèi)型 StorageMap
- 用來(lái)保存鍵值對(duì)陡舅,單值類(lèi)型都可以用作key或者value。
#[pallet::storage]
#[pallet::getter(fn my_map)]
pub type MyMap<T> = StorageMap<
_,
Blake2_128Concat,
u8,
Vec<u8>,
>
- 其中第二個(gè)參數(shù)Blake2_128Concat 是key 的哈希算法伴挚。
- Blake2_128Concat 密碼學(xué)安全
- Twox64Concat 非密碼學(xué)安全靶衍,但是相對(duì)較快臂寝。
- Identity 如果一個(gè)值已經(jīng)是加密值,那么它本身就很安全摊灭,這是后Identity 可以避免無(wú)必要的計(jì)算從而讓效率更高咆贬。
- 基礎(chǔ)操作包括
插入一個(gè)元素:MyMap::<T>::insert(key, value);
通過(guò)key獲取value:MyMap::<T>::get(key);
刪除某個(gè)key對(duì)應(yīng)的元素:MyMap::remove(key);
覆蓋或者修改某個(gè)key對(duì)應(yīng)的元素 MyMap::insert(key, new_value); MyMap::mutate(key, |old_val| old_val+1);
- API文檔:https://crates.parity.io/frame_support/pallet_prelude/struct.StorageMap.html https://crates.parity.io/frame_support/storage/trait.IterableStorageMap.html
雙鍵映射類(lèi)型 StorageDoubleMap
- 使用兩個(gè)key來(lái)索引value,用于快速刪除key1對(duì)應(yīng)的任意記錄帚呼,也可遍歷key1對(duì)應(yīng)的所有記錄掏缎,定義:
#[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>, //存貯值。
>
- 基礎(chǔ)用法:
插入一個(gè)元素:MyDoubleMap::<T>::insert(key1,key2,value);
獲取某一元素:MyDoubleMap::<T>::get(key1, key2);
刪除某一元素:MyDoubleMap::<T>::remove(key1, key2);
刪除 key1 對(duì)應(yīng)的所有元素煤杀,MyDoubleMap::<T>::remove_prefix(key1)
- 迭代時(shí)需要注意不要讓迭代時(shí)間超過(guò)區(qū)塊生成時(shí)間眷蜈,否則會(huì)造成無(wú)法正常出塊。
- API文檔:https://crates.parity.io/frame_support/pallet_prelude/struct.StorageDoubleMap.html https://craes.parity.io/frame_support/storage/trait.IterableStorageDoubleMap.html
存儲(chǔ)的初始化
- 創(chuàng)世區(qū)塊的數(shù)據(jù)初始化
#[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);
}
}
- 可以參考sudo模塊的實(shí)現(xiàn)沈自,里面有一些初始化配置的方法酌儒,有助于更好的理解,https://github.com/paritytech/substrate/blob/master/frame/sudo/src/lib.rs枯途。
開(kāi)發(fā)中需要注意的事兒
1忌怎、最小化鏈上存儲(chǔ)。(存儲(chǔ)哈希值酪夷、設(shè)置列表容量等)
2榴啸、先校對(duì)在存儲(chǔ) Verify First, Write Last
3、事務(wù)管理 Transactional macro
- 可以通過(guò) pub關(guān)鍵字設(shè)置存儲(chǔ)單元的可見(jiàn)范圍晚岭。
- ValueQuery 設(shè)置默認(rèn)值鸥印,如 https://github.com/paritytech/substrate/blob/efd262f1a791be0a7986b25bd302338a590b46d3/frame/support/src/storage/types/value.rs#L228
結(jié)束
- 感謝閱讀