其他
Substrate Off-Chian Workers 是什么?如何用?
概念部分的 Substrate 核心:Off-Chain Workers
开发部分的 Runtime 模块:Off-Chian Workers
1
概念:Off-Chain Workers 链下工作机
Overview 概览
APIs 应用程序接口
能够向链提交交易(已签名或未签名)以发布计算结果。
一个功能齐全的HTTP客户端,允许链下工作机从外部服务中访问和获取数据。
访问本地密钥库以签署和验证声明(statements)或交易。
另一个本地键值(key-value)数据库,在所有链下工作机之间共享。
一个安全的本地熵源(entropy),用于生成随机数。
访问节点的精确本地时间,以及休眠和恢复工作的功能。
2
开发部分的 Runtime 模块:Off-Chian Workers
在 Runtime 中使用链下工作
use support::{ debug, dispatch };
use system::offchain;
use sp_runtime::transaction_validity::{
TransactionValidity, TransactionLongevity, ValidTransaction, InvalidTransaction
};
/// 总的事件类型
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
type Call: From<Call<Self>>;
type SubmitSignedTransaction: offchain::SubmitSignedTransaction<Self, <Self as Trait>::Call>;
type SubmitUnsignedTransaction: offchain::SubmitUnsignedTransaction<Self, <Self as Trait>::Call>;
}
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// --snip--
fn offchain_worker(block: T::BlockNumber) {
debug::info!("Hello World.");
}
}
}
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"abcd");
// --snip--
pub mod crypto {
pub use super::KEY_TYPE;
use sp_runtime::app_crypto::{app_crypto, sr25519};
app_crypto!(sr25519, KEY_TYPE);
}
type SubmitTransaction = system::offchain::TransactionSubmitter<
offchain_pallet::crypto::Public, Runtime, UncheckedExtrinsic>;
impl runtime::Trait for Runtime {
type Event = Event;
type Call = Call;
// 在 runtime 中使用签名的交易
type SubmitSignedTransaction = SubmitTransaction;
// 在 runtime 中使用未签名的交易
type SubmitUnsignedTransaction = SubmitTransaction;
}
// --snip--
impl system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime {
type Public = <Signature as Verify>::Signer;
type Signature = Signature;
fn create_transaction<TSigner: system::offchain::Signer<Self::Public, Self::Signature>> (
call: Call,
public: Self::Public,
account: AccountId,
index: Index,
) -> Option<(Call, <UncheckedExtrinsic as sp_runtime::traits::Extrinsic>::SignaturePayload)> {
let period = 1 << 8;
let current_block = System::block_number().saturated_into::<u64>();
let tip = 0;
let extra: SignedExtra = (
system::CheckVersion::<Runtime>::new(),
system::CheckGenesis::<Runtime>::new(),
system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
system::CheckNonce::<Runtime>::from(index),
system::CheckWeight::<Runtime>::new(),
transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
);
let raw_payload = SignedPayload::new(call, extra).ok()?;
let signature = TSigner::sign(public, &raw_payload)?;
let address = Indices::unlookup(account);
let (call, extra, _) = raw_payload.deconstruct();
Some((call, (address, signature, extra)))
}
}
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
// --snip--
// 使用未签名交易
OffchainPallet: offchain_pallet::{ Module, Call, Storage, Event<T>, transaction_validity::ValidateUnsigned }
// 使用签名交易
// OffchainPallet: offchain_pallet::{ Module, Call, Storage, Event<T> }
}
);
在 service.rs 中添加密钥(Keys)
选项 1(开发阶段):添加第一个用户密钥作为应用的子密钥
-> Result<impl AbstractService, ServiceError>
{
// --snip--
// 给Alice clone密钥
let dev_seed = config.dev_key_seed.clone();
// --snip--
let service = builder.with_network_protocol(|_| Ok(NodeProtocol::new()))?
.with_finality_proof_provider(|client, backend|
Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _)
)?
.build()?;
// 添加以下部分以将密钥添加到keystore
if let Some(seed) = dev_seed {
service
.keystore()
.write()
.insert_ephemeral_from_seed_by_type::<runtime::offchain_pallet::crypto::Pair>(
&seed,
runtime::offchain_pallet::KEY_TYPE,
)
.expect("Dev Seed should always succeed.");
}
}
选项2:通过 CLI 添加应用的子密钥
$ subkey -s generate
# 通过RPC提交一个新密钥
$ curl -X POST -vk 'http://localhost:9933' -H "Content-Type:application/json;charset=utf-8" \
-d '{
"jsonrpc":2.0,
"id":1,
"method":"author_insertKey",
"params": [
"<YourKeyTypeId>",
"<YourSeedPhrase>",
"<YourPublicKey>"
]
}'
签名交易
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) {
// 这里指定下一个区块导入阶段的链上回调函数。
let call = Call::onchain_callback(block, b"hello world!".to_vec());
T::SubmitSignedTransaction::submit_signed(call);
}
}
}
未签名交易
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) {
// 这里指定下一个区块导入阶段的链上回调函数。
let call = Call::onchain_callback(block, b"hello world!".to_vec());
T::SubmitUnsignedTransaction::submit_unsigned(call);
}
}
}
// --snip--
}
impl<T: Trait> Module<T> {
// --snip--
}
#[allow(deprecated)]
impl<T: Trait> support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
fn validate_unsigned(call: &Self::Call) -> TransactionValidity {
match call {
Call::onchain_callback(block, input) => Ok(ValidTransaction {
priority: 0,
requires: vec![],
provides: vec![(block, input).encode()],
longevity: TransactionLongevity::max_value(),
propagate: true,
}),
_ => InvalidTransaction::Call.into()
}
}
}
链上回调函数中的参数
获取外部数据
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> {
// 指定请求
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")?;
// 等待响应
let response = pending.wait()
.map_err(|_| "Error in waiting http response back")?;
// 检查HTTP响应是否正确
if response.code != 200 {
debug::warn!("Unexpected status code: {}", response.code);
return Err("Non-200 status code returned from http request");
}
// 以字节形式收集结果
Ok(response.body().collect::<Vec<u8>>())
}
}
示例
Sub0 工作坊链下工作机的资料
链下工作机价格获取
参考文档
Substrate im-online 模块, 一个 Substrate 内部的 pallet,使用链下工作机通知其他节点,网络中的验证人在线。
扫码关注公众号,回复“1”加入开发者社群