Rust宏
rust宏是一种元程序,类似于java的反射。rust提供了两种宏编程。
- 声名宏
- 过程宏
Substrate中 宏的使用
为了简化运行时的开发,substrate提供了一套DSL(Domain Specific Language)。
- 方便开发者理解
- 提高开发效率,简化开发
- 提高抽象性,不需要开发之知道底层的逻辑
Runtime的定义
Call 宏
区块链链上状态的更改都是由transactions
触发。 substrate不仅仅支持自定义存储,也支持自定义交易,比如投票,身份注册,转账等。
substrate中,这样的交易一般叫做execution
,表示外部交易,表示外部触发的。 每个外部交易都是根据一个调用触发的,就是substrate中的call
这个宏。
Call Macro demo
#[pallet::call]
impl<T: Config> Pallet<T> {
/// An example dispatchable that takes a single u32 value as a parameter, writes the value
/// to storage and emits an event.
///
/// It checks that the _origin_ for this call is _Signed_ and returns a dispatch
/// error if it isn't. Learn more about origins here: <https://docs.substrate.io/build/origins/>
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::do_something())]
pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult {
// Check that the extrinsic was signed and get the signer.
let who = ensure_signed(origin)?;
// Update storage.
Something::<T>::put(something);
// Emit an event.
Self::deposit_event(Event::SomethingStored { something, who });
// Return a successful `DispatchResult`
Ok(())
}
/// An example dispatchable that may throw a custom error.
///
/// It checks that the caller is a signed origin and reads the current value from the
/// `Something` storage item. If a current value exists, it is incremented by 1 and then
/// written back to storage.
///
/// ## Errors
///
/// The function will return an error under the following conditions:
///
/// - If no value has been set ([`Error::NoneValue`])
/// - If incrementing the value in storage causes an arithmetic overflow
/// ([`Error::StorageOverflow`])
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::cause_error())]
pub fn cause_error(origin: OriginFor<T>) -> DispatchResult {
let _who = ensure_signed(origin)?;
// Read a value from storage.
match Something::<T>::get() {
// Return an error if the value has not been set.
None => Err(Error::<T>::NoneValue.into()),
Some(old) => {
// Increment the value read from storage. This will cause an error in the event
// of overflow.
let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
// Update the value in storage with the incremented result.
Something::<T>::put(new);
Ok(())
}
}
}
}
}
Event Macro
区块链是一个异步系统,客户端通过监听链上的事件获取事务的结果
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
SomethingStored {something: u32,who: T::AccountId,
},
}
Error Macro
当事务执行期间发生错误时,通知链下客户端通过错误类型。 在可调用函数里触发的错误类型
- 不能添加字段数据
- 通过
metadata
暴露给客户端 - 错误发生时,触发了一个
ExtrinsicFailed
事件,事件里包含了错误对应的索引信息。比如由那个名字触发的
#[pallet::error]
pub enum Error<T> {
/// The value retrieved was `None` as no value was previously set.
NoneValue,
/// There was an attempt to increment the value in storage over `u32::MAX`.
StorageOverflow,
}
Hooks Macro
substrate中runtime 里有一些保留函数,可以在特定的时间点执行一些自定义的逻辑。
on_initialize
,每个区块的开头执行on_finalize
,每个区块的结束执行offchain_worker
,区块的开头执行,但是不同的是会新启一个线程,并不会占用链上的执行资源。一般执行一些计算要求比较高,或者与链外系统进行交互的一些操作。on_runtime_upgrade
,当有runtime
升级时才会执行,一般是用作遗留数据的迁移功能。
construct_runtime Macro
用来开发runtime功能模块的宏是construct_runtime
,它可以用来加载刚才开发的出来的功能模块。
首先需要对功能模块的配置接口进行实现,比如上面的template模块。
我们对他们的config
配置接口进行实验
impl pallet_template::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = node::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: frame_system::{Pallet, Call, Event<T>, Config<T>} = 0,
}
)
最新的版本添加了 frame_support::runtime这个宏。
#[frame_support::runtime]
mod runtime {
#[runtime::runtime]
#[runtime::derive(
RuntimeCall,
RuntimeEvent,
RuntimeError,
RuntimeOrigin,
RuntimeFreezeReason,
RuntimeHoldReason,
RuntimeSlashReason,
RuntimeLockId,
RuntimeTask
)]
pub struct Runtime;
}
其他宏
另外两种比较常见的是
decl_runtime_apis
和impl_runtime_apis
。定义runtime API,可以通过RPC被客户端进行调用runtime_interface
。定义runtime中可以调用host的函数。