在以太坊的世界里,数据序列化是区块链节点间通信、状态存储和交易处理的核心环节,而 RLP(Recursive Length Prefix,递归长度前缀)正是以太坊中用于序列化数据结构的主要编码方式,无论是账户状态、交易数据还是区块信息,其底层都离不开 RLP 的身影,本教程将带你从零开始,深入理解 RLP 的原理,并通过实例掌握其编码与解码方法。
什么是 RLP?为什么需要它
RLP 是一种针对以太坊中任意嵌套字节数组和字节数组的序列化方法,它的设计目标是简洁、高效,并且能够处理嵌套结构。
为什么需要 RLP?
- 简洁性:RLP 的设计非常简洁,没有复杂的类型系统,只处理字节数组。
- 高效性:编码后的数据紧凑,节省存储空间和网络传输带宽。
- 通用性:能够表示以太坊中几乎所有需要序列化的数据结构,如字符串、列表、嵌套列表等。
- 确定性:同一个数据结构经过 RLP 编码后,结果总是唯一的,这对于区块链的一致性至关重要。
RLP 的核心设计原则
RLP 的核心思想是:对于单个字节数组,如果它的长度在某个阈值内,则直接编码;否则,在其前面加上一个前缀表示其长度,对于嵌套的数据结构(列表),则将其所有元素依次 RLP 编码后拼接起来,然后在整个拼接结果前加上一个前缀表示总长度。
RLL 编码规则详解
RLP 的编码对象有两种基本类型:字符串(字节数组)和列表。
字符串(String)的 RLP 编码
字符串是指一串字节数据("以太坊" 的 UTF-8 编码,或者一个十六进制数的字节表示)。
编码规则如下:
-
如果字符串长度为 0 (空字符串): 编码结果为单字节
0x80(二进制10000000)。- 示例:
RLP("") = 0x80
- 示例:
-
如果字符串长度为 1,且字节值小于
0x80(即最高位为 0): 编码结果就是该字节本身,这称为“短字符串”优化。- 示例:
RLP("d")("d" 的 ASCII 码是 0x64) =0x64
- 示例:
-
如果字符串长度为 1,且字节值大于等于
0x80(即最高位为 1): 编码结果为前缀0x37(十进制 55) 加上该字节。- 示例:
RLP("\x80")(一个字节,值为 0x80) =0x3780
- 示例:
-
如果字符串长度大于 1:
- a. 计算字符串长度 L。
- b. L < 56 (即
0x38): 编码结果为单字节前缀0x80 + L加上字符串本身。- 示例:
RLP("dog")(长度为 3) =0x83 + "dog"=0x646f67(注意:"dog" 的 ASCII 码分别是 0x64, 0x6f, 0x67)
- 示例:
- c. L >= 56:
- i. 计算长度 L 的字节表示 B(大端序)。
- ii. 编码结果为前缀
0xb7 + len(B)加上 B,再加上字符串本身。 - 示例:
RLP("abcdefghijklmnopqrstuvwxyz")(长度为 26,小于 56,所以是0x80 + 26=0x9a+ 字符串) =0x9a6162636465666768696a6b6c6d6e6f707172737475767778797a - 示例(长字符串):假设一个字符串长度为 56 (
0x38),则 B 为0x38,len(B)=1,编码结果为0xb7 + 1=0xb8+0x38+ 字符串 =0xb838+ 字符串。 - 示例(更长字符串):假设字符串长度为 1024 (
0x400),B 为0x0400(2字节),len(B)=2,编码结果为
0xb7 + 2=0xb9+0x0400+ 字符串 =0xb90400+ 字符串。
列表(List)的 RLP 编码
列表是零个或多个 RLP 编码后的字符串或列表的集合。
编码规则如下:
-
如果列表为空(没有元素): 编码结果为单字节
0xc0(二进制11000000)。- 示例:
RLP([]) = 0xc0
- 示例:
-
如果列表不为空:
- a. 将列表中的每个元素分别进行 RLP 编码,然后将这些编码结果依次拼接起来,得到一个总字节数组 S。
- b. 计算总字节数组 S 的长度 L。
- c. L < 56 (即
0x38): 编码结果为单字节前缀0xc0 + L加上 S。- 示例:
RLP(["dog", "cat"])- "dog" RLP 编码:
0x646f67 - "cat" RLP 编码:
0x636174 - S =
0x646f67636174(长度 6) - L = 6 < 56
- 编码结果:
0xc0 + 6=0xc6+ S =0xc6646f67636174
- "dog" RLP 编码:
- 示例:
- d. L >= 56:
- i. 计算长度 L 的字节表示 B(大端序)。
- ii. 编码结果为前缀
0xf7 + len(B)加上 B,再加上 S。 - 示例:
RLP(["hello", "world", "!"])(假设每个元素的 RLP 编码拼接后 S 的长度为 60)- L = 60 >= 56
- B =
0x3c(60 的单字节表示) - len(B) = 1
- 编码结果:
0xf7 + 1=0xf8+0x3c+ S =0xf83c+ S
RLP 解码规则
解码是编码的逆过程,相对复杂一些,需要递归处理。
- 读取第一个字节(前缀字节),判断数据类型和长度信息。
- 如果前缀字节 <
0x80:这是一个短字符串,编码结果就是该字节本身。
- 如果前缀字节 ==
0x80:这是一个空字符串。
0x80< 前缀字节 <=0xb7:- 这是一个字符串,字符串长度 = 前缀字节 -
0x80。 - 读取接下来的该长度的字节,即为字符串内容。
- 这是一个字符串,字符串长度 = 前缀字节 -
0xb7< 前缀字节 <=0xbf:- 这是一个字符串,字符串长度字节数 = 前缀字节 -
0xb7。 - 接下来的该字节数的值表示字符串长度 L。
- 读取接下来的 L 个字节,即为字符串内容。
- 这是一个字符串,字符串长度字节数 = 前缀字节 -
- 如果前缀字节 ==
0xc0:这是一个空列表。
0xc0< 前缀字节 <=0xf7:- 这是一个列表,列表总字节数(所有元素 RLP 编码后的总长度)= 前缀字节 -
0xc0。 - 读取接下来的该总字节数的数据,然后递归解码这个数据块为列表元素。
- 这是一个列表,列表总字节数(所有元素 RLP 编码后的总长度)= 前缀字节 -
- 如果前缀字节 >
0xf7:这是一个列表,列表总字节数的长度字节数 = 前缀字节 - `0xf7