pallet进阶(6)-offchain-worker

offchain worker,链下工作者。

1. 概述

  1. offchain worker 可以提交签名交易、未签名交易、具有签名负载的未签名交易(即内容签名的未签名交易)等功能。
  2. 一个block import结束之后开始执行offchain worker
  3. ocw即为offchain worker的缩写

2. 提交签名交易

表示用自己的私钥签名的文件或内容,标明归属权

2.1 在pallet中添加代码

pallet的lib.rs中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#![cfg_attr(not(feature = "std"), no_std)]
//=========================
//需要关注的第一部分,引入相关密码学工具
// 这部分主要是用来在offchain worker提交签名交易时的签名的子模块。在实际的开发中,这部分基本上是固定的写法。在substrate中支持ed25519和sr25519,我们此处使用的是sr29915作为例子。其中KEY_TYPE是offchain worker签名时检索key使用的类型,由开发者指定,我们这里指定为“demo”。
//=========================
use sp_core::crypto::KeyTypeId;
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo");
pub mod crypto {
use super::KEY_TYPE;
use sp_runtime::app_crypto::{app_crypto, sr25519};
app_crypto!(sr25519, KEY_TYPE);
}
pub type AuthorityId = crypto::Public;
//==========================

pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

use frame_system::offchain::{
AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer,
};

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

//=========================
//需要关注的第二部分,需要实现签名配置
//config需要继承 CreateSignedTransaction;
//需要定义类型type AuthorityId: AppCrypto<Self::Public, Self::Signature>;
#[pallet::config]
pub trait Config: frame_system::Config + CreateSignedTransaction<Call<Self>> {
type AuthorityId: AppCrypto<Self::Public, Self::Signature>;
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
}
//=========================

#[pallet::storage]
pub type SomeInfo<T: Config> = StorageMap<_, Blake2_128Concat, u64, u64, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
SetSomeInfo(u64, u64),
}

#[pallet::error]
pub enum Error<T> {
OffchainSignedTxError,
NoAcctForSigning,
}

//=========================
//需要关注的第三部分 钩子函数中,加入链下函数,该offchain_worker函数会在一个块import成功后调用
//调用offchain worker是在钩子函数中实现。这里重点要提一下的是代码注释标注3.1的部分,在offchain worker中调用交易的方式是这样,
//=========================
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn offchain_worker(block_number: T::BlockNumber) {
log::info!(target: "ocw", "before offchain_worker set storage: {:?}", block_number);
let result = Self::offchain_signed_tx(block_number);
log::info!(target: "ocw", "after offchain_worker set storage: {:?}", block_number);
if let Err(e) = result {
log::error!(target:"ocw", "offchain_worker error: {:?}", e);
}
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(0)]
pub fn submit_something_signed(origin: OriginFor<T>, number: u64, ) -> DispatchResultWithPostInfo {
log::info!(target:"ocw", "11111 +++++++++++++++++++ ");
ensure_signed(origin)?;
let mut cnt: u64 = 0;
if number > 0 {
cnt = number;
}
log::info!(target:"ocw", "+++++++++++++++++++ offchain_worker set storage: {:?}, cnt: {:?}", number, cnt);
SomeInfo::<T>::insert(&number, cnt);
Self::deposit_event(Event::SetSomeInfo(number, cnt));
Ok(().into())
}
}

//这是属于内部方法
impl<T: Config> Pallet<T> {
fn offchain_signed_tx(block_number: T::BlockNumber) -> Result<(), Error<T>> {
let signer = Signer::<T, T::AuthorityId>::any_account();
log::info!(target:"ocw", "+++++++++++++++++++, can sign: {:?}", signer.can_sign());
let number: u64 = block_number.try_into().unwrap_or(0);
//需要关注的3.1
let result = signer.send_signed_transaction(|_acct| Call::submit_something_signed { number });
if let Some((_acc, res)) = result {
if res.is_err() {
return Err(<Error<T>>::OffchainSignedTxError)
}
Ok(())
} else {
Err(<Error<T>>::NoLocalAcctForSigning)
}
}
}
}

2.2 在runtime中添加相关代码

runtime/src/lib.rs中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime
where Call: From<LocalCall>, {
fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(
call: Call,
public: <Signature as sp_runtime::traits::Verify>::Signer,
account: AccountId,
nonce: Index, ) -> Option<(Call, <UncheckedExtrinsic as sp_runtime::traits::Extrinsic>::SignaturePayload)> {
let tip = 0;
// take the biggest period possible.
let period = 1 << 7;
// BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64;

let current_block = System::block_number()
.saturated_into::<u64>()
// The `System::block_number` is initialized with `n+1`,
// so the actual block number is `n`.
.saturating_sub(1);
let era = Era::mortal(period, current_block);
let extra = (
frame_system::CheckNonZeroSender::<Runtime>::new(),
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(era),
frame_system::CheckNonce::<Runtime>::from(nonce),
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
);
let raw_payload = SignedPayload::new(call, extra)
.map_err(|e| {
log::warn!("Unable to create signed payload: {:?}", e);
}).ok()?;
let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?;
let address = <Self as frame_system::Config>::Lookup::unlookup(account);
let (call, extra, _) = raw_payload.deconstruct();
Some((call, (address, signature.into(), extra)))
}
}

impl frame_system::offchain::SigningTypes for Runtime {
type Public = <Signature as sp_runtime::traits::Verify>::Signer;
type Signature = Signature;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
where Call: From<C>, {
type OverarchingCall = Call;
type Extrinsic = UncheckedExtrinsic;
}

在大多数情况下直接这样使用就好了。下面还需要为runtime添加pallet_ocw_sigtx::Config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub struct MyAuthorityId;

impl frame_system::offchain::AppCrypto<<Signature as Verify>::Signer, Signature> for MyAuthorityId {
type RuntimeAppPublic = pallet_ocw_sigtx::crypto::Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}

impl pallet_ocw_sigtx::Config for Runtime {
type AuthorityId = MyAuthorityId;
type Event = Event;
}

construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic{
System: frame_system,
//...
OcwSigtx: pallet_ocw_sigtx,
}

3. 提交未签名交易

3.1 在pallet中添加offchain worker

  1. 修改Config配置
1
2
3
4
#[pallet::config]
pub trait Config: frame_system::Config + SendTransactionTypes<Call<Self>> {
//...
}
  1. 实现具体的未签名调度函数
    也就是公共函数
1
2
3
4
5
6
7
8
9
10
11
12
#[pallet::weight(0)]
pub fn submit_something_unsigned(origin: OriginFor<T>, number: u64, ) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
let mut cnt: u64 = 0;
if number > 0 {
cnt = number;
}
log::info!(target:"ocw", "unsigned +++++++++++++++++++ offchain_worker set storage: {:?}, cnt: {:?}", number, cnt);
SomeInfo::<T>::insert(&number, cnt);
Self::deposit_event(Event::UnsignedPutSetSomeInfo(number, cnt));
Ok(().into())
}
  1. 在offchain worker中调用未签名交易函数
    钩子函数中调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn offchain_worker(block_number: T::BlockNumber) {
let number: u64 = block_number.try_into().unwrap_or(0);
//下面为具体的调用未签名交易的方式
let call = Call::submit_something_unsigned { number };
if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
.map_err(|_| <Error<T>>::OffchainUnsignedTxError) {
log::error!(target:"ocw", "offchain_worker submit unsigned tx error: {:?}", e);
} else {
log::info!(target:"ocw", "offchain_worker submit unsigned tx success");
}
}
}
  1. 实现未签名交易验证的trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
//Call冒号后面就是具体的提交未签名交易的函数,
//需要对此交易进行验证
if let Call::submit_something_unsigned { number: _ } = call {
ValidTransaction::with_tag_prefix("OcwUnsigtx")
.priority(TransactionPriority::max_value())
.longevity(5)
.propagate(false)
.build()
} else {
InvalidTransaction::Call.into()
}
}
}

3.2 在runtime中添加相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
impl pallet_ocw_unsigtx::Config for Runtime {
type Event = Event;
}

construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: frame_system,
//...
OcwUnSigtx: pallet_ocw_unsigtx,
}
)

4. 具有签名负载的未签名交易

提交未签名交易的方式类似,需要时再细查

5. 应用:在ocw中发送http请求

这部分的功能非常简单,就是发送请求https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD 来获取btc价格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//内部方法
impl<T: Config> Pallet<T> {
fn parse_price(price_str: &str) -> Option<u32> {
let val = lite_json::parse_json(price_str);
let price = match val.ok()? {
JsonValue::Object(obj) => {
let (_, v) =
obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
match v {
JsonValue::Number(number) => number,
_ => return None,
}
},
_ => return None,
};
let exp = price.fraction_length.saturating_sub(2);
Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
}

fn fetch_price() -> Result<u32, http::Error> {
let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000));
let request = http::Request::get(
"https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD",
);
let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?;
let response =
pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??;
if response.code != 200 {
log::warn!("Unexpected status code: {}", response.code);
return Err(http::Error::Unknown)
}
let body = response.body().collect::<Vec<u8>>();
let body_str = sp_std::str::from_utf8(&body).map_err(|_| {
log::warn!("No UTF8 body");
http::Error::Unknown
})?;
let price = match Self::parse_price(body_str) {
Some(price) => Ok(price),
None => {
log::warn!("Unable to extract price from the response: {:?}", body_str);
Err(http::Error::Unknown)
},
}?;
log::warn!("Got price: {} cents", price);
Ok(price)
}
}

钩子函数调用:

1
2
3
4
5
6
7
8
9
10
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn offchain_worker(_block_number: T::BlockNumber) {
if let Ok(data) = Self::fetch_price() {
log::info!(target:"offchain-index-demo", "1. get price, price ======================== {:?}", data);
} else {
log::info!(target:"offchain-index-demo", "2. get price failed ==================== ");
}
}
}

总结

本文编辑完毕

  • Copyrights © 2017-2023 Jason
  • Visitors: | Views:

谢谢打赏~

微信