在以太坊的世界里,数据的高效、紧凑序列化是保障网络性能和一致性的基石,RLP(Recursive Length Prefix,递归长度前缀)编码正是以太坊中用于序列化任意嵌套数据结构的核心算法,无论是交易、区块状态,还是账户信息,其底层存储和传输都离不开 RLP 的身影,本文将深入探讨 RLP 的编码原理、详细用法及其在以太坊中的实际应用。
什么是 RLP?为何需要它
RLP 的设计目标非常明确:简洁、高效且能够序列化任意嵌套的二进制数据结构,这里的“任意嵌套”指的是列表可以包含列表,形成复杂的树形结构,以太坊选择 RLP 而非其他序列化方案(如 JSON、Protocol Buffers),主要因为它:
- 简洁高效:RLP 的编码方式非常紧凑,只包含必要的信息,没有额外的元数据开销,适合区块链对存储和带宽的严格要求。
- 易于实现:算法逻辑相对简单,便于在以太坊客户端(如 geth、parity)等多种编程语言中实现。
- 通用性强:能够处理字符串和列表两种基本类型,并通过递归嵌套表示复杂数据。

RLP 的核心原理
RLP 的核心思想是:对于数据项(字符串或列表),其编码由两部分组成:数据本身和一个长度前缀(Length Prefix),用于指示数据的长度,从而解码时能够正确分割数据。
RLP 只处理两种数据类型:
- 字符串(String):字节数组,在以太坊中,数字通常也以其字节数组形式表示(如大端序)。
- 列表(List):一个有序的数据项集合,其中数据项可以是字符串或列表本身,列表可以嵌套。
1 字符串的 RLP 编码规则
对于一个字符串(字节数组),其编码规则取决于其长度 len:
-
情况 1:0 <= len <= 55 字节
- 编码结果:单个字节(值为
0x80 + len) + 字符串本身。 - 示例:空字符串 ,长度为 0,编码为
0x80。 - 示例:
"dog"(ASCII 编码为[0x64, 0x6f, 0x67]),长度为 3,编码为0x83+'d'+'o'+'g'=0x83646f67。
- 编码结果:单个字节(值为
-
情况 2:56 <= len < 2^56 字节(即长度本身需要 1 到 8 字节表示)
- 编码结果:单个字节(值为
0xb7 + len 的字节数) + 长度本身的 RLP 编码(大端序) + 字符串本身。 - 示例:一个长度为 56 的字符串
s。len = 56,len 的字节数 = 1(因为 56 < 256)。- 第一个字节:
0xb7 + 1 = 0xb8。 - 长度本身的编码:
0x38(56 的十六进制)。 - 编码结果:
0xb8+0x38+s(共 1 + 1 + 56 = 58 字节)。
- 编码结果:单个字节(值为
-
情况 3:len >= 2^56 字节(理论上以太坊中不会出现如此大的字符串)
- 编码结果:单个字节(值为
0xb7 + len 的字节数) + 长度本身的 RLP 编码(大端序,使用尽可能多的字节) + 字符串本身。 - (此情况在实际以太坊应用中极少遇到,了解即可)
- 编码结果:单个字节(值为
2 列表的 RLP 编码规则
对于一个列表,其编码规则如下:
- 计算列表中所有数据项(字符串或列表)的 RLP 编码后的总长度
total_len。 - 根据
total_len的值,选择前缀字节:- 情况 1:0 <= total_len <= 55 字节
- 编码结果:单个字节(值为
0xc0 + total_len) + 各数据项 RLP 编码的串联。
- 编码结果:单个字节(值为
- 情况 2:56 <= total_len < 2^56 字节
- 编码结果:单个字节(值为
0xf7 + total_len 的字节数) + total_len 本身的 RLP 编码(大端序) + 各数据项 RLP 编码的串联。
- 编码结果:单个字节(值为
- 情况 3:total_len >= 2^56 字节(同样,以太坊中罕见)
- 编码结果:单个字节(值为
0xf7 + total_len 的字节数) + total_len 本身的 RLP 编码(大端序) + 各数据项 RLP 编码的串联。
- 编码结果:单个字节(值为
- 情况 1:0 <= total_len <= 55 字节
列表编码示例:
-
列表
["cat", "dog"]:"cat"的 RLP 编码:0x83636174"dog"的 RLP 编码:0x83646f67total_len = len("cat" RLP) + len("dog" RLP) = 4 + 4 = 8字节。total_len = 8 <= 55,所以前缀字节为0xc0 + 8 = 0xc8。- 列表 RLP 编码:
0xc8+0x83636174+0x83646f67=0xc88363617483646f67。
-
嵌套列表
["cat", ["dog"]]:"cat"的 RLP 编码:0x83636174["dog"]的 RLP 编码:"dog"的 RLP 编码:0x83646f67- 内层列表
total_len = 4,前缀字节0xc0 + 4 = 0xc4。 - 内层列表 RLP 编码:
0xc483646f67。
- 外层列表
total_len = len("cat" RLP) + len(["dog"] RLP) = 4 + 5 = 9字节。 - 外层列表前缀字节:
0xc0 + 9 = 0xc9。 - 外层列表 RLP 编码:
0xc9+0x83636174+0xc483646f67=0xc983636174c483646f67。
以太坊中的 RLP 用法
RLP 在以太坊中无处不在,主要用于以下几个方面:
-
区块(Block)的序列化:
- 一个区块包含多个字段:父区块哈希、叔父区块哈希列表、Coinbase 地址、根状态、交易列表、收据列表、日志布隆过滤器、难度、时间戳、数字签名、额外数据等。
- 这些字段通过 RLP 编码成一个字节数组,这就是我们通常所说的“区块头”中的“区块体”(实际上区块头本身也有 RLP 结构)的 RLP 编码,或者整个区块的 RLP 编码用于节点间同步。
-
交易(Transaction)的序列化:
- 以太坊交易包含 nonce、gas 价格、gas 限制、接收方地址、金额、数据、签名等信息。
- 这些字段被打包成一个结构化的数据,然后通过 RLP 编码,形成网络上传输的交易数据,一个简单的转账交易(无 data 字段)的 RLP 编码会包含上述关键字段的序列化串联。
-
状态存储(State Storage):
以太坊的状态树(Merkle Patricia Trie)的节点键和值都是通过 RLP 编码的,账户对象(包含 nonce、余额、存储根代码哈希)本身就是一个列表或结构化数据,其序列化依赖于 RLP,存储在合约中的键值对,其键和值在存入状态树前也会进行 RLP 编码。
-
收据(Receipt)的序列化:
交易执行后生成的收据(包含状态根、gas 使用量、日志等)也需要通过 RLP 编码后存入区块的收据列表中。
-
其他数据结构:
如 Uncle 列表、区块头中的 ExtraData 字段等,