Offchain Worker (下)

Case 7 :  向链上发起签名交易

目的

  1. 修改 Node, runtime, pallet 文件

  2. 大部分都是样板代码

  3. 签名交易需要有账户,交易的执行会从这个账户收取tx fee

本节内容重点内容在于配置,现在我们就来设置一些必要的配置项

1.substrate-node-template/node/src/service.rs

--let client = Arc::new(client);+++// 生成账户放入keystore,这样pallet就可以获取该账户了,offchain使用if config.offchain_worker.enabled {       let keystore = keystore_container.sync_keystore();       sp_keystore::SyncCryptoStore::sr25519_generate_new(           &*keystore,           node_template_runtime::pallet_template::KEY_TYPE,           Some("//Alice"), //添加的是Alice账户,测试用的,本身有很多余额,也是Sudo           ).expect("Creating key with account Alice should succeed.");   }

ubstrate-node-template/node/Cargo.toml

sp-keystore = {version ="0.12.0",git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.30" }

2.substrate-node-template/runtime/src/lib.rs

注意:branch = "polkadot-v0.9.30" 版本 的Call 变为了RuntimeCall

use codec::Encode;use sp_runtime::SaturatedConversion; //在此处引入两个traitimpl pallet_sudo::Config for Runtime {type RuntimeEvent = RuntimeEvent;type RuntimeCall = RuntimeCall;}// 当offchain worker调用pallet中的函数时,在此处产生真正的交易impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for RuntimewhereRuntimeCall: From<LocalCall>,{fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(call: RuntimeCall,public: <Signature as sp_runtime::traits::Verify>::Signer,account: AccountId,nonce: Index,) -> Option<(RuntimeCall,<UncheckedExtrinsic as sp_runtime::traits::Extrinsic>::SignaturePayload,)> {let tip = 0;let period =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>().saturating_sub(1);let era = generic::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_asset_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None),pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),);let raw_payload = SignedPayload::new(call, extra).map_err(|_| {//log::warn!("Unable to create signed payload: {:?}", e);}).ok()?;let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?;let address = account;let (call, extra, _) = raw_payload.deconstruct();Some((call, (sp_runtime::MultiAddress::Id(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 RuntimewhereRuntimeCall: From<C>,{type Extrinsic = UncheckedExtrinsic;type OverarchingCall = RuntimeCall;}/// Configure the pallet-template in pallets/template./// Configure the pallet-template in pallets/template.impl pallet_template::Config for Runtime {type RuntimeEvent = RuntimeEvent;type AuthorityId = pallet_template::crypto::OcwAuthId;}

3.substrate-node-template/pallets/template/src/lib.rs

use frame_system::{   offchain::{       AppCrypto, CreateSignedTransaction, SendSignedTransaction,       Signer,   },};use sp_core::crypto::KeyTypeId;pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"ocwd");pub mod crypto {   use super::KEY_TYPE;   use sp_core::sr25519::Signature as Sr25519Signature;   use sp_runtime::{       app_crypto::{app_crypto, sr25519},       traits::Verify,       MultiSignature, MultiSigner,   };   app_crypto!(sr25519, KEY_TYPE);   pub struct OcwAuthId;   impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for OcwAuthId {       type RuntimeAppPublic = Public;       type GenericSignature = sp_core::sr25519::Signature;       type GenericPublic = sp_core::sr25519::Public;   }   impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature>       for OcwAuthId       {           type RuntimeAppPublic = Public;           type GenericSignature = sp_core::sr25519::Signature;           type GenericPublic = sp_core::sr25519::Public;       }}pub mod pallet {use super::*;use frame_support::pallet_prelude::*;use frame_system::pallet_prelude::*;use sp_std::{vec, vec::Vec};--#[pallet::config]pub trait Config: frame_system::Config + CreateSignedTransaction<Call<Self>> {/// Because this pallet emits events, it depends on the runtime's definition of an event.type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;type AuthorityId: AppCrypto<Self::Public, Self::Signature>;}#[pallet::call]impl<T: Config> Pallet<T> {--// 在链上定义函数,提交变量,如上面的函数一样,该怎么写就怎么写#[pallet::weight(0)]pub fn submit_data(origin: OriginFor<T>, payload: Vec<u8>) -> DispatchResultWithPostInfo {let _who = ensure_signed(origin)?;log::info!("in submit_data call: {:?}", payload);Ok(().into())}}#[pallet::hooks]impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {fn offchain_worker(block_number: T::BlockNumber) {log::info!("Hello World from offchain workers!: {:?}", block_number);// 通过offchain worker获得要提交的变量,可以是从外部获取let payload: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];// 执行辅助函数_ = Self::send_signed_tx(payload);log::info!("Leave from offchain workers!: {:?}", block_number);}fn on_initialize(_n: T::BlockNumber) -> Weight {log::info!("in on_initialize!");let weight: Weight = Default::default();weight}fn on_finalize(_n: T::BlockNumber) {log::info!("in on_finalize!");}fn on_idle(_n: T::BlockNumber, _remaining_weight: Weight) -> Weight {log::info!("in on_idle!");let weight: Weight = Default::default();weight}}impl<T: Config> Pallet<T> {fn send_signed_tx(payload: Vec<u8>) -> Result<(), &'static str> {let signer = Signer::<T, T::AuthorityId>::all_accounts();if !signer.can_sign() {return Err("No local accounts available. Consider adding one via `author_insertKey` RPC.",)}// 在辅助函数中调用 链上函数let results = signer.send_signed_transaction(|_account| Call::submit_data { payload: payload.clone() });for (acc, res) in &results {match res {Ok(()) => log::info!("[{:?}] Submitted data:{:?}", acc.id, payload),Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e),}}Ok(())}}}

3.substrate-node-template/pallets/template/Cargo.toml

[dependencies]--sp-core = { version ="6.0.0",default-features = false,git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.30" }

编译运行

重点:

  1. 配置offchain worker三个文件,node,runtime,template

  2. Offchain 逻辑:链上交易函数 - 分装在辅助函数中调用 - offchain 传递参数并调用辅助函数

  3. 当前块发起的交易在下一个块中执行

Case 8 : 向链上发送不签名交易

目的

  1. 无需修改 Node(不配置账户用于签名支付交易费用), 只需修改runtime, pallet 文件

  2. 大部分都是样板代码

  3. 学习宏 #[pallet::validate_unsigned],TransactionValidity,ValidTransaction,ensure_none等

  4. 每个块的不签名交易不建议发送太多

更改配置

substrate-node-template-case/runtime/src/lib.rs

++++use super::*;impl<C> frame_system::offchain::SendTransactionTypes<C> for RuntimewhereRuntimeCall: From<C>,{type Extrinsic = UncheckedExtrinsic;type OverarchingCall = RuntimeCall;}

substrate-node-template-case/pallets/template/Cargo.toml

[dependencies]--sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.30" }log = { version = "0.4.17", default-features = false }[features]default = ["std"]std = [--"sp-runtime/std",]

substrate-node-template-case/pallets/template/src/lib.rs

use frame_system::offchain::{   SubmitTransaction,};use sp_runtime::{   transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},};
#[pallet::config]pub trait Config:frame_system::Config + frame_system::offchain::SendTransactionTypes<Call<Self>>{/// Because this pallet emits events, it depends on the runtime's definition of an event.type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;}
#[pallet::call]impl<T: Config> Pallet<T> {--#[pallet::weight(0)]pub fn submit_data_unsigned(origin: OriginFor<T>, n: u64) -> DispatchResult {ensure_none(origin)?;log::info!("in submit_data_unsigned: {:?}", n);// Return a successful DispatchResultWithPostInfoOk(())}}#[pallet::hooks]impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {fn offchain_worker(block_number: T::BlockNumber) {log::info!("Hello World from offchain workers!: {:?}", block_number);let value: u64 = 42;let call = Call::submit_data_unsigned { n: value };_ = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()).map_err(|_| {log::error!("Failed in offchain_unsigned_tx");},);log::info!("Leave from offchain workers!: {:?}", block_number);}}#[pallet::validate_unsigned]impl<T: Config> ValidateUnsigned for Pallet<T> {type Call = Call<T>;fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {if let Call::submit_data_unsigned { n: _ } = call {//let provide = b"submit_xxx_unsigned".to_vec();ValidTransaction::with_tag_prefix("ExampleOffchainWorker").priority(10000).and_provides(1).longevity(3).propagate(true).build()} else {InvalidTransaction::Call.into()}}}impl<T: Config> Pallet<T> {// 暂未写业务逻辑}

编译并运行

可以看到,我们已经成功的提交了不签名交易

更多案例可以参考官方的完整实现

https://github.com/paritytech/substrate/blob/master/frame/examples/offchain-worker/src/lib.rs

Case 9 : 使用Offchain Indexing特性实现从链上向Offchain Storage中写入数据

本节内容我们使用一个较为完整的案例实现,具体见代码:

https://github.com/shiyivei/substrate-advanced-course/tree/master/lesson4/backend/pallets/kitties

branch = "polkadot-v0.9.30" 版本和 branch = "polkadot-v0.9.28" 配置有些许不一样,但是逻辑是一样的,这部分内容,挖坑后补。最近又要开始很忙了。OK,关于Offchain worker的介绍我们要再告一段落了。下个主题见

猜你喜欢

转载自blog.csdn.net/weixin_51487151/article/details/127523374