以太坊RLP怎么解码?原始交易与类型识别

 / 
3

解码以太坊原始交易时,第一个要判断的不是"怎么解 RLP",而是这串字节到底是不是纯 RLP。传统交易(Legacy)是裸 RLP 列表,而 EIP-2718 引入的类型化交易在 RLP 负载前多了一个类型字节。如果你拿到 0x02... 开头的字节却直接丢进 RLP 解码器,第一个字节 0x02 会被当成"单字节字符串"处理,后面的字段全部错位,你得到的是一堆看似合法、实际无意义的垃圾数据。

欧易OKX交易所
全球领先的加密货币平台,适合新手与进阶交易者
新手福利:注册即享20% 交易手续费减免!

先看首字节:RLP 列表前缀,还是类型标识

EIP-2718 的规范写得很清楚:类型化交易的格式是 TransactionType || TransactionPayload,其中 TransactionType 是一个 0 到 0x7f 之间的无符号 8 位整数。传统交易被定义为 rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]),整串字节就是一个 RLP 列表,没有前置类型字节。

判断规则只有一条:看原始字节的第一个字节。

如果首字节落在 RLP 列表前缀区间(0xc0 到 0xff),这串字节是传统交易,直接对整个字节串做 RLP 解码,得到一个包含 9 个元素的列表。如果首字节是 0x01、0x02、0x03 或其他小于 0x80 的值,它是类型化交易:首字节是类型标识,剩余字节才是 RLP 编码的负载,你需要先剥离首字节,再对剩余部分做 RLP 解码。

OnFinality 的指南把这个分支说成"正确解码器和静默产生垃圾字段的解码器之间的区别"。这不是夸张。一个 0x02 开头的 EIP-1559 交易,如果误当传统 RLP 处理,解码器会把 0x02 读成一个单字节字符串,然后把后面的 0xf8 或 0xf9 当成列表前缀去解析,字段顺序和含义完全对不上。

类型字节对应的常见交易

当前以太坊主网上你会遇到的类型字节主要是这几个:

0x01 对应 EIP-2930 访问列表交易,负载的 RLP 列表结构为 [chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, yParity, r, s]。0x02 对应 EIP-1559 动态费用交易,结构为 [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, yParity, r, s]。0x03 对应 EIP-4844 blob 交易,结构在 1559 基础上增加了 blob 相关字段。0x04 对应 EIP-7702 的授权列表交易,负载中会包含 authorizationList。

传统交易没有类型字节,所以它的"类型"在代码里通常用 0 或 null 表示。BloXroute 的文档把传统交易标为 "Type 0 legacy transaction",类型化交易则显式携带 type: '0x2' 这样的字段。

剥离类型字节之后:RLP 解码本身很简单

RLP 的编码规则不复杂,核心是前缀字节决定后续字节的含义和长度。对于单字节值 0x00 到 0x7f,字节本身就是编码;短字符串(0–55 字节)以 0x80 + 长度 开头;长字符串以 0xb7 + 长度字节数 开头;短列表以 0xc0 + 负载长度 开头;长列表以 0xf7 + 长度字节数 开头。

以太坊官方文档特别强调了一条:正整数必须以大端序、无前导零的形式表示,反序列化时遇到带前导零的正整数应视为无效。这条规则在你手动解码或写校验逻辑时有用,但如果你用成熟的库(viem 的 parseTransaction、ethers 的 parse、Alloy 的 TxEnvelope),库内部会处理这些。

一个需要留意的边界:RLP 编码的是结构,不是类型。解码器返回的是嵌套的字节数组,每个叶子节点是一段十六进制数据。你需要按照对应交易类型的字段顺序,自己把字节解释成地址、整数或字节数组。比如 to 字段解码出来是一段字节,你要判断它是不是 20 字节的地址;value 解码出来是字节,你要按大端序转成整数。库帮你做了 RLP 层面的解码,但字段语义的映射仍然依赖你对交易类型的了解。

如果你不想自己写解码器

viem 的 parseTransaction 接受一个十六进制字符串,返回解析后的交易对象,文档写明支持签名的和未签名的 EIP-1559、EIP-2930 和 Legacy 交易。eth ers 的 @ethersproject/transactions 子模块提供 parse 函数做同样的事。Alloy 用 TxEnvelope 类型来承载解码结果,区分带类型字节和不带类型字节的情况。

如果你用这些库,类型识别这一步库已经帮你做了。但理解首字节的判断逻辑仍然有价值:当库返回的字段和你预期不一致时,你能快速判断是"字节串本身不是你以为的那种交易",还是"库的版本不支持这个类型"。比如一个 0x04 开头的 EIP-7702 交易,在只支持到 1559 的旧版本库里可能解析失败或返回错误字段。

验证你解对了没有

最直接的校验是 keccak256(原始字节) == 交易哈希。这个等式对传统交易和类型化交易都成立,类型字节包含在哈希原像中。如果你从 RPC 的 eth_getRawTransactionByHash 拿到了原始字节,用 keccak256 算一遍,和请求的交易哈希比对。不一致说明端点返回了错误的字节(可能是代理缓存、错误链或过期数据)。

第二层校验是对照 eth_getTransactionByHash 的返回对象。解码原始字节得到的 type、nonce、to 等字段,应该和 JSON 对象一致。如果原始字节解码出 type: 2 而 JSON 对象说 type: 0,两者至少有一个是错的,而通常错的是你拿到的原始字节或你使用的端点。

一个容易踩的坑:不是所有"原始交易"都是主网格式

如果你在 L2 或 OP Stack 链上处理交易,类型字节的范围可能更宽。viem 的 OP Stack 版本支持 0x7e 开头的 deposit 交易。Mantle 的文档也提到了同样的 deposit 类型。这些类型在主网规范里没有,但在对应链上是合法的。你的解码器如果硬编码"只认 0x01/0x02/0x03",遇到 0x7e 就会失败。判断标准不变:首字节小于 0x80 就当作类型字节剥离,但你需要根据目标链的规范来决定支持哪些类型值。

你该从哪里开始

如果你手头有一笔具体交易的原始字节,先用 eth_getRawTransactionByHash 确认你拿到的确实是原始字节而不是解码对象。看第一个字节:0xc0 以上走传统 RLP 解码,0x80 以下走类型化路径。用 viem 或 ethers 的解析函数做一次交叉验证,再用 keccak256 对一遍哈希。这三步做完,你至少能确认"我拿到的字节是什么格式、解出来的字段是否自洽"。至于字段的具体含义和业务逻辑,那是在解码正确之后才需要关心的事。

欧易OKX交易所
全球领先的加密货币平台,适合新手与进阶交易者
新手福利:注册即享20% 交易手续费减免!

参考资料