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

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
where
    RuntimeCall: 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
where
	RuntimeCall: 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代码都会执行一次,将当前的区块号写入,因此数值会不断增长
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuanwenchao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值