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

news2025/1/15 20:38:27

目录标题

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/435687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

整理的汉字及拼音、编码数据文件,依据拼音声母进行归类共计2万多条

本篇文章主要讲解自己整理的汉字拼音数据资源的下载及使用方法。 资源截图 包含&#xff1a;sql、xls、txt、json等格式文件 汉字涵盖&#xff1a;多音字、生僻字 下载地址&#xff1a;https://download.csdn.net/download/hj960511/87705416 使用方法 步骤一、下载资源包&…

七、使用arcgis对道路结果进行后处理及iou优化步骤详解

最近在研究对道路的后处理 废话不多说 直接放我的教程了 分别对real真实和predict预测的图片进行镶嵌操作 教程在这里 工具在这里 结果如下 矢量化提取道路中心线 经过很多尝试 arcscan是提取效果最好的一个方法&#xff0c;操作见这 或者这里这篇文章注解更详细一点&am…

问题汇总1

问题汇总 Linux相关1. vim 修改挂载文件时 报错 read-only filesystem2.root 用户密码无法更改3.linux 用户被锁定4.linux 查看登录日志其他小问题 Windows 相关1.添加 删除 默认路由2.exel合并单元格 添加 分隔符 Linux相关 1. vim 修改挂载文件时 报错 read-only filesystem …

Linux namespace

​ 前言 从《initrd&init进程》可知&#xff0c;我们通过ssh连接linux服务器&#xff0c;其实主是linux启动一shell进程与我们做交互。而Linux又是多租户的&#xff0c;这使用得用户与用户间产生了&#xff0c;资源的争抢。 如何隔离资源&#xff0c;且让用户都无法察觉&…

SpringBoot项目实现热部署

文章目录 SpringBoot实现热部署手动开启热部署自动开启热部署热部署相关配置 SpringBoot实现热部署 什么是热部署&#xff1f; 所谓热部署&#xff0c;就是在应用正在运行的时候升级软件&#xff0c;却不需要重新启动应用。对于Java应用程序来说&#xff0c;热部署就是在运行时…

初学数据库

1、什么是数据库 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库。 每个数据库都有一个或多个不同的API用于创建&#xff0c;访问&#xff0c;管理&#xff0c;搜索和复制所保存的数据。 我们也可以将数据存储在文件中&#xff0c;但是在…

企业级实践:大厂项目研发流程

引言 战国邹孟轲《孟子离娄上》&#xff1a;“离娄之明&#xff0c;公输子之巧&#xff0c;不以规矩&#xff0c;不能成方圆。” 每一个行业都有自己行之有效的规矩&#xff0c;同样软件行业也有自己一套的开发流程&#xff0c;今天就来跟大家聊一聊咱们公司的开发流程&#…

智能修改文案-智能写作平台

智能原创自动写作工具在线 随着人工智能技术的发展&#xff0c;智能原创自动写作工具在线已经成为了网络营销的一个重要工具。这种工具可以根据您输入的关键词和主题&#xff0c;自动生成高质量、原创性强的文章。下面是智能原创自动写作工具在线的优势。 节省时间和人力成本 …

IDEA重复下载SNAPSHOT包问题

问题现象 reimport 之后 状态栏显示resolving dependencies… 遇到某个比较大的快照包(33M)&#xff0c;同一天的第2个版本时 1.0-xxx-SNAPSHOT.时间戳-2 idea importer 会先分片下载 x.jar.part文件中&#xff0c;然后复制为x.jar吧 如图中所示&#xff0c;其实已经下载完了&…

C++四种类型转换运算符

C语言之所以增加强制类型转换的语法&#xff0c;就是为了强调风险&#xff0c;让程序员意识到自己在做什么。 但是&#xff0c;这种强调风险的方式还是比较粗放&#xff0c;粒度比较大&#xff0c;它并没有表明存在什么风险&#xff0c;风险程度如何。再者&#xff0c;C风格的…

深入浅出OkHttp,【带你手写】构建高效、高性能的网络请求框架

简述 OKHttp是一个用Java编写的网络框架&#xff0c;可用于 Android&#xff0c;以及一些基于Java的web应用开发中。它使用了HTTP/2标准的支持和连接池技术&#xff0c;可以让应用快速向Web服务器发送网络请求&#xff0c;并得到响应。OKHttp提供了一个简单的API&#xff0c;允…

【SQL】列的选择与查询

本文内容参考书籍《SQL基础教程》第二章&#xff0c;课后习题在最后&#xff0c;请多指教。之前章节的内容请点击下方链接。 前言 PostgreSQL的下载与安装 第一章 数据库的创建&#xff0c;表的创建、更新、删除 一、SELECT语句 1、查询表中的列 &#xff08;1&#xff09…

【每日一题】——移除元素

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

重装系统重启后无法进入系统解决方法

重启无法进入系统怎么办?让大家平日里使用小白装机系统的话&#xff0c;很有可能会由于没有办法顺利的进行引导菜单而导致无法进入系统下面&#xff0c;小编今天教大家小白装机重启无法进入系统解决方法。 方法/步骤&#xff1a; 方法一&#xff1a;运用热键再次进入。 1、小…

【LeetCode】剑指 Offer(29)

目录 题目&#xff1a;剑指 Offer 56 - II. 数组中数字出现的次数 II - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;剑指 Offer 57. 和为s的两个数…

JVM垃圾回收机制(GC)

目录 GC的作用&#xff1a; 申请内存的时机和释放内存的时机 内存泄露和内存溢出 内存泄露 内存溢出 GC&#xff08;垃圾回收的劣势&#xff09; GC&#xff08;垃圾回收&#xff09; 的工作过程 垃圾回收的过程&#xff1a; 第一阶段&#xff1a;找垃圾/判定垃…

Baumer工业相机堡盟工业相机如何联合BGAPISDK和OpenCV实现图像的直方图算法增强(C++)

Baumer工业相机堡盟工业相机如何联合BGAPISDK和OpenCV实现图像的直方图算法增强&#xff08;C&#xff09; Baumer工业相机Baumer工业相机使用图像算法增加图像的技术背景Baumer工业相机通过BGAPI SDK联合OpenCV使用图像增强算法1.引用合适的类文件2.BGAPI SDK在图像回调中引用…

基于NXP iMX8处理器扩展外部 SGTL5000 音频接口

By Toradex胡珊逢 Apalis iMX8 计算机模块的数字音频接口 SAI&#xff08;Synchronous Audio Interface&#xff09;可以配置为 AC97、I2S格式&#xff0c;用于连接外部音频编解码器。文章接下来将介绍在 Linux BSP v6 上如何扩展第二路 SGTL5000。 iMX8 处理器具有多路 SAI 通…

I2C基础入门

I2C参数 主从模式&#xff1a; 主机从机 常见速率&#xff1a; 普通模式&#xff08;100kHz&#xff09;快速模式&#xff08;400kHz&#xff09;快速模式&#xff08;1MHz&#xff09;高速模式&#xff08;3.4MHz&#xff09;超高速模式&#xff08;5MHz&#xff09; 地址…

__cplusplus和extern “C“

文章目录 __cplusplus是什么extern "C"使用场景的示例通过MinGW编译及查看下目标文件中的符号用gcc编译器添加 -c选项 使my_handle.c文件编译后生成my_handle.o文件&#xff0c;这里的 -o是 output的意思nm命令 是GCC编译集合下最常用的查看目标文件中的符号的命令 -…