以太坊源码之『RLP』

RLP源码解析

一:概念:RLP(Recursive Length Prefix--递归长度前缀):是一个编码算法

二:功能:主要用于编码任意嵌套结构的二进制数据。是以太坊中序列和反序列化的主要方法,所有的区块、交易等数据结构都会经过RLP编码之后再存储到区块数据库中

三:数据处理特性:RLP处理两类数据

  1. 字符串(一串二进制数据)
  2. 列表(不单是一个列表,可以是一个嵌套递归的结构,里面还可以包含字符串,列表)

四:RLP编码规则

  1. 对于单个字节,如果其值范围是**(0x00,0x7f]**,它的RLP编码是其本身
  2. 如果不是单个字节,一个字符串的长度是0~55字节,它的RLP编码包含一个单字节的前缀,后面跟着字符串本身,这个前缀的值是0x80加上字符串的长度,由于被编码的字符串最大长度是55=0x37,因此单字节的前缀最大值0x80+0x37=0xb7,即编码的第一个字节取值范围是**(0x80,0xb7]**
  3. 如果字符串长度大于55个字节,它的RLP编码包含一个单字节的前缀,**然后后面跟着字符串的长度**(转化成16进制),再后面跟着字符串本身。这个前缀的值是0xb7加上字符串的长度的**二进制形式**的字节长度
  4. 如果一个列表的总长度(列表总长度是它包含的项的数量加它包含的各项的长度之和)是0-55字节,它的RLP编码包含一个单字节的前缀,后面跟着列表中各项元素的RLP编码,这个前缀的值是0xc0加上列表的总长度,编码的第一个字节的取值范围是**[0xc0,0xf7]**
  5. 如果一个列表的总长度大于55个字节,它的RLP编码包含一个单字节的前缀,后面跟着列表的总长度,再后面跟着列表中各项元素的RLP编码,这个前缀的值是0xf7加上**列表总长度二进制形式的字节长度**,编码的第一个字节取值范围是**(0xf8,0xff]**。

五:编码实例

  1. 规则1:"d"="d"
  2. 规则2:"dog"=[0x83,'d','o','g']
  3. 规则3:如果一个字符串长度1024,它的二进制就是1000000000,该二进制长度为两个字节(一个字节8位),则该字符串前缀应该是0xb9。字符串长度1024=0x400。编码的第一个字节范围就[0xb8,0xbf].      [0xb9,0x04,0x00,...]
  4. 规则4:空列表:[]=[0xc0]  ;                 ["cat","dog"]= [0xc8,0x83,'c','a','t',0x83,'d','o','g']      0xc8的8是6+2
  5. 规则5:以列表总长度为1024为例,它的二进制就是1000000000,该二进制长度为两个字节(一个字节8位),则该字符串前缀应该是0xf9,列表总长度0x400,再跟上各项元素的总长度编码             [....]=[0xf9,0x04,0x00,...]

六:解码规则

  • 根据RLP编码规则和过程,RLP解码的输入一律视为二进制字符数组,其过程如下:

         1. 根据输入首字节数据,解码数据类型、实际数据长度和位置;

         2. 根据类型和实际数据,解码不同类型的数据;

         3. 继续解码剩余的数据;

  • 其中,解码数据类型、实际数据和位置的规则如下:

         1. 如果首字节(prefix)的值在[0x00, 0x7f]范围之间,那么该数据是字符串,且字符串就是首字节本身;

         2. 如果首字节的值在[0x80, 0xb7]范围之间,那么该数据是字符串,且字符串的长度等于首字节减去0x80,且字符串位于首字节之后;(比如首字节占0x87,那么长度就是0x87-0x80=7)

         3. 如果首字节的值在[0xb8, 0xbf]范围之间,那么该数据是字符串,该字符串长度大于55,且字符串的长度的**字节长度**等于首字节减去0xb7,数据的长度位于首字节之后,且字符串位于数据的长度之后;

         4. 如果首字节的值在[0xc0, 0xf7]范围之间,那么该数据是列表,在这种情况下,需要对列表各项的数据进行递归解码。列表的总长度(列表各项编码后的长度之和)等于首字节减去0xc0,且列表各项位于首字节之后;

         5. 如果首字节的值在[0xf8, 0xff]范围之间,那么该数据为列表,总长度大于55,列表的总长度的字节长度等于首字节减去0xf7,列表的总长度位于首字节之后,且列表各项位于列表的总长度之后;

七:总结

      1. RLP编码主要和字符串或者列表的长度有关,在解码的过程中,采用相对应编码规则逆推的方式进行

      2. 与其他的序列化方式相比,RLP编码优点在于灵活使用长度前缀来表示数据的实际长度,并且使用递归的方式可以编码相当的数据

      3. 在接收到经过RLP编码的数据之后,根据第1个字节就可以推断出数据类型,长度,数据本身等信息。而其它的序列化方式,不能要搞第一个字节获得这么多信息

八:目录结构

         decode.go 解码器,把RLP数据解码成go的数据结构

         decode_tail_test.go/decode_test.go 解码器测试代码

         encode.go 编码器,把GO的数据结构转换成RLP的编码

         encode_test.go/encode_example_test.go 编码器的测试

         raw.go 原始的RLP数据

         raw_test.go 测试文件

         typecache.go 类型缓存,记录了类型->内容(编码器/解码器)

         ```

九:typecache.go:根据给定的类型找到对应的编码器和解码器

  • 在C++或者JAVA等语言中,支持重载,可以通过不同的类型重载同一个函数名称来实现方法针对不同类型的实现,也可以通过泛型来实现函数的分派。
  • ```c++

         string encode(int)

         string encode(log)

         string encode(struct test*)

         ```

  • go语言本身不支持重载,也没泛型,所以需要自己来实现函数的分派,typecache.go就是通过自身的类型快速找到对应的编码器与解码器的函数。
  • 总结

         1. 该文件定义了类型->编码器/解码器函数的核心数据结构

         2. 定义了编码器和解码器的函数

         3. 通过对应类型查找对应的编码器和解码器

         4. 通过给定的类型生成对应的编码器和解码器

十:encoder.go:编码器函数,把数据结构转换为RLP编码 

          1. 定义编码器接口

          2. RLP编码函数

          3. RLP数据组装

十一:decoder.go:解码器函数,把RLP编码转换为对应的golang数据结构

          1. 定义解码器接口

          2. RLP解析函数

 

你可能感兴趣的:(区块链)