ETH 交易流程深度技术详解

发布于:2025-07-29 ⋅ 阅读:(21) ⋅ 点赞:(0)

1@2x-3.jpg

1.png

概述

在前面对 PolkaVM 和 Revive 的文章中,我们介绍了很多技术细节,开发工具。还对比 EVM,知道了 PolkaVM 的优势。很多同学还是对 Polkadot SDK 为什么可以运行 EVM 兼容的智能合约,以及交易处理的整个流程不太清楚。这篇文章将会带着大家梳理一下整个过程,在熟悉它之后,对于今后 Debug 代码,找到出错原因都会有很大好处。

整个处理的流程主要有下面几个模块组成,我们来逐一分析

  • RPC Server 接收交易,并转发到节点

  • Runtime 定义交易的格式扩展

  • Revive 做交易的转换,从 Eth 交易转成 Polkadot SDK 的 extrinsic

  • 最终的 bytecode 在 PolkaVM 中的执行

2.png

RPC Server

和普通的 Extrinsic 不同,也有别于之前在 Polkadot SDK 上出现的Frontier,Ink 的支持。在提交 Solidity 的交易的时候,我们会启动一个单独的RPC Server来接收交易。因此我们在配置本地的测试环境或者 Passet Hub 的时候,都需要这个 URL。

那么这个 Server 是怎么工作的呢?它的代码也在 Revive Pallet下,package 名字是 pallet-revive-eth-rpc。它主要有三个组件,一个 subxt 的客户端,一个 RPC 服务器,还有一个用来缓存数据的 sqlite 内存数据库。

subxt 主要是和 node 来交互,比如向区块链提交交易,查询数据等。 RPC 服务器来实现以太坊的 Web3 服务接口,大部分函数定义都在 EthRpc 这个 Trait 可以找到。其他还有 debug api 和 health api。

当 server 收到请求,都会转换成对区块链的请求,通过 subxt 的客户端发出,收到结果后返回给调用者。

这里分别举二个例子,一个是查询余额,一个提交交易。

截屏2025-07-21 09.49.49.png

这里的接口定义是 web3 的一致的,通过区块号或者 Hash 来查询余额。下面我们来看下它的实现。

截屏2025-07-21 09.50.42.png

这里 client 就是 subxt 的客户端,它把传进来的区块号,哈希或者 Tag,统一转换成区块哈希,进行查询,并返回结果。这里不用担心 Decimal 的问题,在 Node 一侧,当要查询的是一个Eth格式的地址时,就已经做好了转换。

下面是对发送交易的实现,它会组装一个 Pallet 是 Revive,Extrinsic 是 eth_transact 的交易,然后发给 Node。这个 Extrinsic 非常的特殊,我们接下来会在分析 Revive 代码的时候看下它是怎么处理的。

截屏2025-07-21 09.51.29.png

截屏2025-07-21 09.51.33.png

Sqlite 主要来缓存一些交易的数据,还有事件日志等。

图片

Runtime

在 Runtime 中,需要包含 Revive Pallet 才能处理Eth的交易,还有对Runtime API的实现。一个最重要的代码实现是对 UncheckedExtrinsic 的重新定义,对于不需要支持 Eth 的交易,普通的 sp_runtime::generic::UncheckedExtrinsic 就可以了。为了支持对 Eth 的交易,需要使用 Revive 里面  UncheckedExtrinsic 作为包含中 Block 中的类型。

这样区块链在处理交易池里面的 Eth 交易就可以识别他们,在验证交易的时候把它转换成普通的 Extrinsic。

4.png

Revive Pallet

Revive 当然是处理交易的核心,有些设计还比较的巧妙,我们先来看它如何转换Eth的交易。对于一个提交的 Eth 交易,下面这个函数用来对它进行转换,成为普通的 Extrinsic。

截屏2025-07-21 09.53.12.png

截屏2025-07-21 09.53.18.png

截屏2025-07-21 09.53.23.png

截屏2025-07-21 09.53.28.png

首先尝试解码 payload 成一个含有 Eth 签名的交易,并得到发送交易的以太坊地址。随后做一个基本的检查,比 如nonce,gas,chain ID 等等。

下一步就是要转换成 Call 了, 这里首先有一个特殊的地址  RUNTIME_PALLETS_ADDR,它被用来直接调用 Runtime 里面的任何交易。它把输入的数据直接解码成 Call 并调用,除此之外的都会转换成 Revive 的 Call 方法,调用某个智能合约里面的一个方法。如果没有调用的合约地址,那么只能是一个部署合约的方法,会把它尝试转换成一个 eth_instantiate_with_code 的方法。

从这里我们可以看出,从 Eth 来的交易,没有只 upload_code 的方法,当然这个也不在以太坊接口的定义里面。

在下面就是对于 gas_price, gas_fee 的处理,还会把多给的 gas_fee 作 为tip。

最终返回一个 CheckedExtrinsic,它也在这里有了使用 Polkadot 帐号的签名,它来自发送交易者映射到 Polkadot 地址格式的帐号。

这里有一个非常有意思,和难理解的就是 eth_transact 方法,它除了给出一个错误之外没有任何其他逻辑。那么它的作用是什么呢?首先,它提供了一个接口给外部调用,比如我们在 RPC Server 里面使用 subxt 来调用它。其次,我们使用它就可以把提交来的交易解码成这个 Call,得到它的 payload 部分,在用 payload 解码成封装的 Eth 交易。

但是在从 UncheckedExtrinsic 到 CheckedExtrinsic 的转换中,它就被处理了,转换成了其他的方法。call 或者 eth_instantiate_with_code,因此在正常情况下不会有 dispatch 到这个方法里面的交易。这就是为什么它只有报错处理。

截屏2025-07-21 09.55.36.png

截屏2025-07-21 09.55.40.png

当然在 Revive 里面还有很多处理逻辑,包括我们前面文章提到的precompile,这里不再重复介绍。

5.png

交易在 VM 的执行

在 Revive 中处理交易中最核心的是一个 Stack 的数据结构,从这个名字来看,这里依然要模拟的还是 EVM 基于栈的处理模型。它的代码如下:

截屏2025-07-21 09.56.52.png

截屏2025-07-21 09.56.56.png

除去基本信息比如区块号,时间戳,还有就是对于计算和存贮的计量。通过 frames 来记录每次对合约更深一层的调用,和函数的逐层调用一样。

transient_storage 是一个在 EVM 中非常特殊的存贮,它在整个的交易中都是有效的,因此它的存贮也在 frame 之外,单独存放。

在进行完一系列繁琐的环境准备之后,代码最终在 PolkaVM 的开始执行。下面是一个非常重要的数据结构 PreparedCall,我们通过它来调用 PolkaVM 去执行合约编译后的 RISC-V 代码,或者说 PolkaVM(它和标准的 RISC-V 有着细小的差别). Module 主要包含的是编译好的程序代码, RawInstance 是一个 PolkaVM 的实例,用来运行虚拟机。Runtime 则是区块链节点提供给它的执行环境。

截屏2025-07-21 09.57.58.png

6.png

总结

在这个文章中,我们介绍了交易从发送到 RPC Server,到最终在 PolkaVM 里面运行的整个过程。希望能帮助大家在调试代码的时候有所帮助,能够根据错误信息,找到对应的模块,解决问题。


网站公告

今日签到

点亮在社区的每一天
去签到