Substrate off-chain worker

作者: songtianyi create@2022-07-24

Features

相较于 Oracle 这类通过 events log 方式, substrate 提供了一种集成度更高的链上链下的数据交互方式。

Off-Chain Storage

Off-chain storage 只存在于当前节点,不会同步到其它节点,由于 off-chain worker 会在出块的时候被触发执行,且执行是异步的,所以会存在多个 worker 执行实例,如下图:

在开发 worker 的时候要注意并发问题。

多个 worker 实例之间是可以共享 storage 的。storage 的内容可以从外部通过 rpc 来获取。

Off-Chain Indexing

除了 off-chain storage, substrate 提供了 off-chain indexing, 它是专门用于 on-chain logic 的,而 storage 主要用于 worker. off-chain indexing 会被同步到其它节点

Developer guide

Entrypoint

decl_module! { pub struct Module<T: Trait> for enum Call where origin: T:: Origin { // --snip-- fn offchain_worker(block: T::BlockNumber) { debug::info!("Hello World."); } } }

offchain_worker 是固定的入口函数

Signed transaction

decl_module! { pub struct Module<T: Trait> for enum Call where origin: T:: Origin { // --snip-- pub fn onchain_callback(origin, _block: T::BlockNumber, input: Vec<u8>) -> dispatch::Result { let who = ensure_signed(origin)?; debug::info!("{:?}", core::str::from_utf8(&input).unwrap()); Ok(()) } fn offchain_worker(block: T::BlockNumber) { // Here we specify the function to be called back on-chain in next block import. let call = Call::onchain_callback(block, b"hello world!".to_vec()); T::SubmitSignedTransaction::submit_signed(call); } } }

Unsigned transaction

decl_module! { pub struct Module<T: Trait> for enum Call where origin: T:: Origin { // --snip-- pub fn onchain_callback(_origin, _block: T::BlockNumber, input: Vec<u8>) -> dispatch::Result { debug::info!("{:?}", core::str::from_utf8(&input).unwrap()); Ok(()) } fn offchain_worker(block: T::BlockNumber) { // Here we specify the function to be called back on-chain in next block import. let call = Call::onchain_callback(block, b"hello world!".to_vec()); T::SubmitUnsignedTransaction::submit_unsigned(call); } } }

去掉校验即可

Fetching External Data

use sp_runtime::{ offchain::http, transaction_validity::{ TransactionValidity, TransactionLongevity, ValidTransaction, InvalidTransaction } }; // --snip--decl_module! { pub struct Module<T: Trait> for enum Call where origin: T:: Origin { // --snip-- fn offchain_worker(block: T:: BlockNumber) { match Self::fetch_data() { Ok(res) => debug::info!("Result: {}", core::str::from_utf8(&res).unwrap()), Err(e) => debug::error!("Error fetch_data: {}", e), }; } } }impl<T: Trait> Module<T> { fn fetch_data() -> Result<Vec<u8>, &'static str> { // Specifying the request let pending = http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD") .send() .map_err(|_| "Error in sending http GET request")?; // Waiting for the response let response = pending.wait() .map_err(|_| "Error in waiting http response back")?; // Check if the HTTP response is okay if response.code != 200 { debug::warn!("Unexpected status code: {}", response.code); return Err("Non-200 status code returned from http request"); } // Collect the result in the form of bytes Ok(response.body().collect::<Vec<u8>>()) } }

参考资料