Substrate学习(二) Runtime 宏介绍

21 阅读3分钟

Rust宏

rust宏是一种元程序,类似于java的反射。rust提供了两种宏编程。

  • 声名宏
  • 过程宏

Substrate中 宏的使用

为了简化运行时的开发,substrate提供了一套DSL(Domain Specific Language)。

  • 方便开发者理解
  • 提高开发效率,简化开发
  • 提高抽象性,不需要开发之知道底层的逻辑

Runtime的定义

image.png

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_apisimpl_runtime_apis。定义runtime API,可以通过RPC被客户端进行调用
  • runtime_interface 。定义runtime中可以调用host的函数。