> 文章列表 > substrate实例-基于OCW发送一个签名交易

substrate实例-基于OCW发送一个签名交易

substrate实例-基于OCW发送一个签名交易

目录标题

  • 1. 获取substrate-node-template代码
  • 2. 添加一个用于测试的ocw-test目录至pallets
  • 3. 编写ocw-test/src/lib.rs代码
    • 3.1 需要用到的包名的引用
    • 3.2 模块crypto的实现
    • 3.3 mode pallet 的实现-config、storage、event
    • 3.4 mode pallet 的实现-call
    • 3.5 mode pallet 的实现-hook-offchain_worker
  • 4. runtime/src/lib.rs的实现
  • 5. 使用subkey生成公私钥
  • 6. 使用polkadot.js.org/apps来测试
    • 6.1 使用RPC calls配置密钥信息
    • 6.2 向测试账户转账
    • 6.2 在poldadot的UI上确认成功交易

1. 获取substrate-node-template代码

https://github.com/substrate-developer-hub/substrate-node-template/
获取代码后切换到 polkadot-v0.9.30 (这是一个tag)

2. 添加一个用于测试的ocw-test目录至pallets

对于如何添加一个新的pallet中以参照:添加新的pallet

3. 编写ocw-test/src/lib.rs代码

3.1 需要用到的包名的引用

#![cfg_attr(not(feature = "std"), no_std)]use frame_system::{offchain::{AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer},pallet_prelude::*,
};
use sp_core::crypto::KeyTypeId;
pub use pallet::*;

3.2 模块crypto的实现

此处KeyTypeId由四个字母组成、在后边做insertKey时使用

pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"xuan");pub mod crypto {use super::KEY_TYPE;use sp_runtime::{app_crypto::{app_crypto, sr25519},// traits::Verify,MultiSignature,MultiSigner,};app_crypto!(sr25519, KEY_TYPE);pub struct TestAuthId;impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for TestAuthId {type RuntimeAppPublic = Public;type GenericSignature = sp_core::sr25519::Signature;type GenericPublic = sp_core::sr25519::Public;}
}

TestAuthId必须实现AppCrypto这个trait,runtime中要用它初始化关联类型,主要用处是指定签名类型。

3.3 mode pallet 的实现-config、storage、event

#[frame_support::pallet]
pub mod pallet {use super::*;use frame_support::pallet_prelude::*;#[pallet::pallet]#[pallet::generate_store(pub(super) trait Store)]pub struct Pallet<T>(_);#[pallet::config]pub trait Config: frame_system::Config + CreateSignedTransaction<Call<Self>> {type AuthorityId: AppCrypto<Self::Public, Self::Signature>;type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;}#[pallet::storage]#[pallet::getter(fn something)]	pub type Something<T> = StorageValue<_, u64>;#[pallet::event]#[pallet::generate_deposit(pub(super) fn deposit_event)]pub enum Event<T: Config> {SendSignedSomething(u64, T::AccountId),}

3.4 mode pallet 的实现-call

该方法用于调用签名交易成功时执行,将区块号写入storage

	#[pallet::call]impl<T: Config> Pallet<T> {#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]pub fn submit_send_signed_something(origin: OriginFor<T>,blocknumber: u64,) -> DispatchResult {log::info!("### submit_send_signed_something start...]");let who = ensure_signed(origin)?;log::info!("### submit_send_signed_something who:[{:?}], number:[{:?}]",who,blocknumber);<Something<T>>::put(blocknumber);Self::deposit_event(Event::SendSignedSomething(blocknumber, who));Ok(())}}

3.5 mode pallet 的实现-hook-offchain_worker

#[pallet::hooks]impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {fn offchain_worker(block_number: T::BlockNumber) {let signer = Signer::<T, T::AuthorityId>::any_account();log::info!("### offchain_worker signer can sign:[{:?}]", signer.can_sign());let number: u64 = block_number.try_into().unwrap_or(0);log::info!("### offchain_worker signer number:[{:?}]", number);let result = signer.send_signed_transaction(|_account| {Call::submit_send_signed_something { blocknumber: number }});if let Some((_acc, res)) = result {if res.is_err() {if let Err(e) = res {log::info!("### Error! send_signed_transaction:[{:?}]", e);} else {log::info!("### Error! Unknow100");}} else {log::info!("### Error! Offchain Signed Error!");}}else {log::info!("### Error! NoAccountForSigning!");}}}

4. runtime/src/lib.rs的实现

impl pallet_ocw_test::Config for Runtime {type AuthorityId = pallet_ocw_test::crypto::TestAuthId;type RuntimeEvent = RuntimeEvent;
}use codec::Encode;
use sp_runtime::{generic::Era, SaturatedConversion, traits};impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime
whereRuntimeCall: From<LocalCall>,
{fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(call: RuntimeCall,public: <Signature as Verify>::Signer,account: AccountId,nonce: Index,) -> Option<(RuntimeCall, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> {let tip = 0;// take the biggest period possible.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>()// 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 = account;let (call, extra, _) = raw_payload.deconstruct();Some((call, (sp_runtime::MultiAddress::Id(address), signature, extra)))}
}impl frame_system::offchain::SigningTypes for Runtime {type Public = <Signature as traits::Verify>::Signer;type Signature = Signature;
}impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
whereRuntimeCall: From<C>,
{type Extrinsic = UncheckedExtrinsic;type OverarchingCall = RuntimeCall;
}

代码写完后,编译运行

cargo build
./target/debug/node-template --dev

5. 使用subkey生成公私钥

获取substrate工程代码、编译package subkey,

git clone https://github.com/paritytech/substrate.git
cargo build -p subkey --release

生成密钥
在这里插入图片描述

6. 使用polkadot.js.org/apps来测试

打开 https://polkadot.js.org/apps/ 连接本地网络、

6.1 使用RPC calls配置密钥信息

在这里插入图片描述
keyType对应代码中的KeyTypeId对应的四位字母;
suri 对应的是生成密钥的第一行Secret phrase;
publicKey对应添入Public key (hex)的值;
提交RPC调用后,可以看到命令行输出的变化:
在这里插入图片描述
can sign由false变成了true,但是出现一行错误

### offchain_worker signer can sign:[true]
Transaction pool error: Invalid transaction validity: InvalidTransaction::Payment

这表示账户余额不足所报的错,我们可以使用其它账户转一些balance过来

6.2 向测试账户转账

在这里插入图片描述

请注册此处的AccountId需要手动输入上面subkey生成的密钥中的Public key (SS58)对应的值,提交交易后
可以看到命令行中的变化
在这里插入图片描述
因为代码中提交成功输出的内容是:

  log::info!("### Error! Offchain Signed Error!");

因此这时已经是成功状态了,可以通过界面查看结果

6.2 在poldadot的UI上确认成功交易

可以看到在下方ocwTestModule.something: Option中的数值一直在不断变化,因为每次import一个新的块时,对应的OCW代码都会执行一次,将当前的区块号写入,因此数值会不断增长
在这里插入图片描述