rlp

package
v0.0.0-...-fda1b34 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 24, 2022 License: Apache-2.0 Imports: 14 Imported by: 0

README

RLP简介

递归长度前缀(Recursive Length Prefix,RLP)编码是以太坊项目特别设计的一种编码方式,它的编码结果相比于JSON编码占用更少的存储空间,因为在JSON编码方式中,需要额外的字段名信息来组织编码内容。而在RLP编码里,则使用一种被称为前缀的字段来组织编码内容,这可以有效减少编码过程中产生的其他额外信息。

RLP编码结果由**编码前缀(Encoding Prefix,EP)编码内容(Encoding content,EC)**两部分组成:

编码结果 := EP || EC

1. 类型标记位

RLP编码中,EP由两部分组成,其中第一部分是占据1个字节存储空间的类型标记位(Type Marker Bit,TMB),它可被分为五种,对应RLP编码中处理的五种数据类型,如下所示:

  • 0~127 | 0x0~0x7F:单个的ASCII
  • 128~183 | 0x80~0xB7:长度在56以内的string,即EC的长度小于56
  • 184~191 | 0xB8~0xBF:长度大于55string,即EC的长度大于55
  • 192~247 | 0xC0~0xF7:编码结果的长度小于56list,即EC的长度小于56
  • 248~255 | 0xF8~0xFF:编码结果的长度大于55list,即EC的长度大于55

go语言中,上面提到的string我们可以将其简单理解为简单数据类型的变量,例如uintstringbyte等,而list我们可以将其理解为复合数据类型,例如struct等。值得注意的是,在go-ethereum里,[]byte[num]byte被归类为string,而[]x[num]x(其中x为非byte类型)被归类为list,另外,空接口,即interface{}也被归类为list

当我们给定一个数据对象x,然后利用RLP编码技术对x进行编码,得到编码结果r,一般情况下,r的第一个字节存储的就是TMB,因此,我们可以根据TMB推断出x的数据类型。

2. 可选长度编码

前面我们介绍了EP由两部分组成,其中第一部分是类型标记位TMB,第二部分就是可选长度编码(Optional Length Coding,OLC)。当TMB的取值在184~191248~255这两个区间内时,OLC才会出现在前缀里。根据观察TMB的取值范围和对应编码的数据类型时,我们发现,当TMB取值在128~183192~247之间时,我们只需要TMB就可以求出EP后面紧跟着的EC的长度,例如我们利用RLP去编码一段长度为32stringTMB将等于160,它的取值落在128~183之间,那么利用160减去128就可以得到EC长度等于32这个结论。其实,之所以EC的长度也等于32,是因为,对于string类型的数据,RLP对其编码得到的EC其实就是数据它本身。

到这里,我们可能会感受到,rlp是一种和数据类型以及数据长度息息相关的编码技术。

如果我们编码一个长度为1025string类型数据,那么仅仅根据TMB就无法计算出EP后面跟着的EC有多长了,在这种情况下,我们需要OLC来辅助存储编码数据的长度。首先,RLP会先对1025进行长度编码,长度编码遵从大端编码规则(高位字节存储在低地址位)1025的二进制表现形式为[00000100,00000001],其实这个1025的二进制表现形式就是它的长度编码结果,为了表示方便,我们用[4,1]来表示长度编码结果,即,如果我们编码一个长度为1025string类型数据,那么EP中的OLC就等于[4,1]。现在我们先尝试组装一下编码结果,得到结果如下所示:

EP || EC $\rightarrow$ TMB || OLC || EC

当我们把这串数据发送给接收者后,接收者如何进行解码呢?我们知道,TMP只占据一个字节存储空间,因此接收者可以直接获得TMB,但是OLC具体占据多少字节存储空间则是未知的,接收者无法区分OLCEC。基于这一点考虑,RLPOLC的长度信息存储到了TMB中。

还以上面的例子为例,如果我们编码一个长度为1025string类型数据,那么TMB的取值应当落在184~191之间,前面我们知道,1025的长度编码结果为[00000100,00000001](简写为[4,1]),因此至少需要两个字节空间来存储1025的长度编码,换句话说,这个例子里的OLC长度为2,所以我们需要将2这个长度信息存储到TMB中,具体的存储方式如下:

TMB := 184 + (2 - 1) = 185

这样的话,接收者在拿到编码结果以后,根据TMB可以知道OLC的长度,获取到OLC的长度以后,就可将OLCEC分隔开。根据TMB获取OLC的长度计算规则如下:

  • if TMB $\in$ 184~191, then, length $\leftarrow$ TMB - 183
  • if TMB $\in$ 248~255, then, length $\leftarrow$ TMB - 247

3. 递归编码

要理解递归编码这个概念,我们需要先理解list到底是什么,前面我们已经介绍过对string类型的数据进行编码,得到的EC就是数据本身,而对list类型的数据进行RLP编码,得到的EC还是数据本身吗?这里先说答案:不是

我们给一个例子,例如我们对以下数据进行RLP编码:

x := []string{"abc", "def"}

x是一个字符串切片,根据前面对stringlist数据类型的介绍,我们知道:x属于list数据类型,但是x里面存储的"abc""def"却是string数据类型,对string数据类型的编码结果等于EP连接上ECEC就是数据本身,因此我们只需要计算EP是多少就行。

"def"为例,它的长度等于3EC的长度也等于3),因此EP将只含有TMB,不含有OLC,所以EP=TMB=128+3=131,将EPEC进行组合,得到:[131 100 101 102],其中100101102分别是'a''b''c'ASCII码值。

同理,对"abc"进行RLP编码,得到结果:[131 97 98 99]。

x内部元素RLP编码结果已知,现在需要返回到list这一层,对x进行编码,对x进行RLP编码,得到的编码结果中的EC等于什么呢?实际上,EC就等于"abc""def"的编码结果“之和”:[131 97 98 99 131 100 101 102],那么EC的长度我们就可以求得等于8,根据TMB和不同数据类型的对应关系,我们可以推断TMB的取值应当落在192~247之间,且根据OLC的出现条件,我们可以判断此处ECTMB可以划等号,所以EC=TMB=192+8=200,所以,x最终的编码结果为:[200, 131 97 98 99 131 100 101 102]。

上面的例子可能还不能很好地体会到递归的精髓,下面给一个新的例子:

x := []interface{}{[]interface{}{}, [][]interface{}{{}}}

它的RLP编码结果为:[195 192 193 192]。

对上面编码结果如果存在疑问,可以查看源码,其中空接口的编码方式如下:

if val.IsNil() {
	buf.str = append(buf.str, 0xC0)
	return nil
}

4. 结构体中的编码规则

rlpstruct\rlpstruct.go文件里定义了使用RLP编码如何对用户自定义的数据结构进行编解码的方式,通过为结构体字段的tag设置不同的rlp标签,可以实现若干种编解码方式。go-ethereum定义了一个Tags结构体来维护结构体字段的rlp标签,如下所示:

type Tags struct {
    NilKind NilKind
    NilManual bool
    Optional bool
    Tail bool
    Ignored bool
}
  • NilKind字段定义了结构体字段的空值编码规则:NilKindStringNilKindList

  • NilManual如果设置为true,则表明结构体字段的空值编码规则被手动设置为:rlp:"nil" rlp:"nilString" rlp:"nilList"

  • Optional用来表示该字段的rlp标签是否被设置成rlp:"optional",如果某个结构体定义了4个可导出的字段,并且在第二个字段的rlp标签里设置了rlp:"optional",那么第三和第四个字段的tag里也必须要设置rlp:optional,除非第四个字段是一个切片,并且它的rlp标签已经被设置为rlp:"tail",那么这第四个字段的tag就不能设置为rlp:"optional"。给结构体的字段的rlp标签设置成optional具有以下作用呢:当我们对结构体进行编码时,如果从某个字段开始往后所有字段的tag都被设置成optional,且这些字段中存在违未被始化的情况,,它会遵循以下规则进行编码:排在最后一个rlp标签optional且值为非零值的字段前面的字段(包括该字段),不管它们的rlp标签有没有设置为optional,也不管它们的值是否等于零值,这些字段都将参与编码,而排在后面的值为零值的字段,这些字段由于它们的rlp标签一定被设置成optional,且它们的值在运行时阶段是零值,所以它们将不参与编码,下面给一个例子做为说明:

    type People struct {
        Name string
        Age uint8 `rlp:"optional"`
        Son *People `rlp:"optional"`
        Daughter *People `rlp:"optional"`
    }
    var p1 People = People{Name: "Tom", Age: 35, Daughter: &People{Name: "Lina", Age: 8}}
    // 由于People的第二、三、四3个字段的tag都被设置成optional,所以当对p1进行编码时,因为最后一个非零值字段是Daughter,所以排在它前面的字段(包括Son字段,尽管它的值等于零值)包括它自己(Daughter字段)都会被编码,所以编码结果如下:
    // [205 131 84 111 109 35 192 198 132 76 105 110 97 8]
    var p2 People = People{Name: "Tom", Son: &People{Name: "David", Age: 10}}
    // 我们对p2进行编码,发现p2最后一个非零字段是Son,尽管它前面的Age字段是零值,但是它排在Son前面,所以依然会被编码,而Daughter字段为零值,且排在Son
    // 之后,所以不会被编码,那么编码结果就如下所示:
    // [205 131 84 111 109 128 199 133 68 97 118 105 100 10]
    
  • Tail字段用来表示该字段的rlp标签是否被设置成rlp:"tail"RLP编码规则规定:在任何自定义结构体中,只有最后一个可导出字段,且该字段还必须是切片类型,才能给该字段的rlp标签设置成rlp:"tail",这也映证了Tail的中文含义。那么它的作用是什么呢?根据RLP的编码规则,我们知道那些元素类型为非byte类型的切片或者数组会被当成列表进行编码,并且在go代码中,切片或者数组里的所有元素类型必须统一,那么如果我们给结构体的最后一个字段的rlp标签设置成tail,在编码时,会将该字段“拆开看”,所谓拆开看就是不会将该字段看成一个整体:list,而是会逐一对该字段所表示的切片里的元素进行编码,例如下面给出了两个示例:

    // 示例1
    type class struct {
    	ClassID  uint8
    	Students []string `rlp:"tail"`
    }
    var c class = class{ClassID: 3, Students: []string{"abc", "def"}}
    // 由于此时我们给class结构体的Students字段的tag设置成tail,所以在编码时,会将其拆开看,不会将其看成整体一个,也就是说在编码时是会把class结构体看
    // 成如下的结构体:
    // type class struct {
    //     ClassID  uint8
    //     Student1 string
    //     Student2 string
    //     Student3 string
    //     ...
    // }
    // 所以对上面的c进行编码的结果是:[201 3 131 97 98 99 131 100 101 102]
    // 而如果我们把Students字段的tag里的tail去掉,编码结果则变为:[202 3 200 131 97 98 99 131 100 101 102],将其作为一个列表(整体)进行编码
    
    // 示例3
    type class struct {
    	ClassID  uint8
    	Students []string `rlp:"tail"`
    }
    var c class = class{ClassID: 3}
    // 对c进行编码,因为c里面并没有初始化Students字段,所以它的值等于零值,又因为Students的tag被设置为tail,所以不会将其看成一个列表在对其进行编码,
    // 仅仅是将其看成若干个连续的string类型的字段,所以对c的编码结果为:[193 3]
    // 而如果我们把Students字段的tag里的tail去掉,编码结果则变为:[194 3 192],因为此时会将Students字段看成是一个整体(列表),空列表的编码结果为
    // 0XC0
    
  • Ignored字段用来表示该字段的rlp标签是否被设置成rlp:"-",如果被设置成rlp:"-",那么该字段在编码时会被直接忽略,不参与编码,例如下面给出了一个代码示例:

    type student struct {
    	Name  string
        Age   uint8 `rlp:"-"`
    	Birth string
    }
    var s student = student{Name: "abc", Age: 18, Birth: "def"}
    // 比如上面给出了一个结构体student,该结构体内定义了一个学生的姓名、年龄和出生日期,一般来说,我们对数据进行编码是为了进行网络传输或者文件存储,为了
    // 减小网络开销,我们提倡只编码有用的数据,例如在这个例子里,当我们知道一个学生的出生日期,那么就可以推出该学生的年龄,所以我们忽略对student结构体的
    // Age字段进行编码,那么对s进行编码的结果是:[200 131 97 98 99 131 100 101 102]
    // 如果我们将Age字段的tag里的“-”给去掉,编码结果则变为:[201 131 97 98 99 18 131 100 101 102]
    

总结下来,利用rlp编码规则对自定义结构体进行编码,我们可以在结构体字段的tag里设置以下种编码标记:

  • rlp:"nil"
  • rlp:"nilString"
  • rlp:"nilList"
  • rlp:"optional"
  • rlp:"tail"
  • rlp:"-"

5. 案例

5.1 编码bool类型数据

原值 编码结果
true [1]
false [128]

5.2 编码无符号整数

原值 编码结果
0 [128]
127 [127]
128 [129 128]
256 [130 1 0]
1024 [130 4 0]
0xffffff [131 255 255 255]

5.3 编码大整数

原值 编码结果
0 [80]
1 [1]
127 [127]
128 [129 128]
256 [130 1 0]
0x123456789abcdef123456789abcdef [143 18 52 86 120 154 188 222 241 35 69 103 137 171 205 239]

5.4 编码字节数组

原值 编码结果
[0]byte{} [128]
[1]byte{0} [0]
[1]byte{1} [1]
[1]byte{127} [127]
[1]byte{128} [129 128]
[3]byte{1,2,3} [131 1 2 3]
[60]byte{1,2,3} [184 60 1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

5.5 编码字节切片

原值 编码结果
[]byte{} [128]
[]byte{0} [0]
[]byte{1} [1]
[]byte{127} [127]
[]byte{128} [129 128]
[]byte{1,2,3} [131 1 2 3]

5.6 编码字符串

原值 编码结果
"" [80]
"aaa" [131 97 97 97]
"My major is cyberspace security" [159 77 121 32 109 97 106 111 114 32 105 115 32 99 121 98 101 114 115 112 97 99 101 32 115 101 99 117 114 105 116 121]
"RLP encoding is a new encoding method specifically implemented in the Ethereum" [184 78 82 76 80 32 101 110 99 111 100 105 110 103 32 105 115 32 97 32 110 101 119 32 101 110 99 111 100 105 110 103 32 109 101 116 104 111 100 32 115 112 101 99 105 102 105 99 97 108 108 121 32 105 109 112 108 101 109 101 110 116 101 100 32 105 110 32 116 104 101 32 69 116 104 101 114 101 117 109]

5.7 编码非字节切片

原值 编码结果
[]uint{} [192]
[]uint{1} [193 1]
[]uint{1 9 17} [195 1 9 17]
[]interface{}{[]interface{}{}} [193 192]
[]interface{}{[]interface{}{}, uint(3)} [194 192 3]
[]interface{}{[]interface{}{}, []interface{}{[]interface{}{}}} [195 192 193 192]
[]interface{}{[]interface{}{}, [][]interface{}{{}}} [195 192 193 192]
[]string{"aaa", "bbb", "ccc"} [204 131 97 97 97 131 98 98 98 131 99 99 99]
[]interface{}{uint(1), uint(0xffffff), []interface{}{[]uint{4, 5, 6}}, "abc"} [206 1 131 255 255 255 196 195 4 5 6 131 97 98 99]
[][]string{{"aaa", "bbb", "ccc"}, {"aaa", "bbb", "ccc"}, {"aaa", "bbb", "ccc"}, {"aaa", "bbb", "ccc"}, {"aaa", "bbb", "ccc"}} [248 65 204 131 97 97 97 131 98 98 98 131 99 99 99 204 131 97 97 97 131 98 98 98 131 99 99 99 204 131 97 97 97 131 98 98 98 131 99 99 99 204 131 97 97 97 131 98 98 98 131 99 99 99 204 131 97 97 97 131 98 98 98 131 99 99 99]

5.8 编码结构体

type simplestruct struct {
	A uint
	B string
}

type recstruct struct {
	I     uint
	Child *recstruct `rlp:"nil"`
}

type intField struct {
	X int
}

type ignoredFiled struct {
	A uint
	B uint `rlp:"-"`
	C uint
}

type tailStruct struct {
	A    uint
	Tail []RawValue `rlp:"tail"`
}

type optionalFields struct {
	A uint
	B uint `rlp:"optional"`
	C uint `rlp:"optional"`
}

type optionalAndTailField struct {
	A    uint
	B    uint   `rlp:"optional"`
	Tail []uint `rlp:"tail"`
}

type optionalBigIntField struct {
	A uint
	B *big.Int `rlp:"optional"`
}

type optionalPtrFiled struct {
	A uint
	B *[3]byte `rlp:"optional"`
}

type optionalPtrFieldNil struct {
	A uint
	B *[3]byte `rlp:"optional,nil"`
}
原值 编码结果
simplestruct{} [194 128 128]
simplestruct{A: 3, B: "abc"} [197 3 131 97 98 99]
simplestruct{A: 326, B: "abc"} [199 130 1 70 131 97 98 99]
&recstruct{I: 5, Child: nil} [194 5 192]
&recstruct{I: 5, Child: &recstruct{I: 5, Child: &recstruct{I: 5, Child: nil}}} [198 5 196 5 194 5 192]
intField{X: 3} 错误:"rlp: type int is not RLP-serializable (struct field rlp.intField.X)"
ignoredFiled{A: 1, B: 2, C: 3} [194 1 3]
tailStruct{A: 1, Tail: nil} [193 1]
tailStruct{A: 1, Tail: []RawValue{{1, 2, 3}}} [196 1 1 2 3]
optionalFields{A: 1, B: 2, C: 3} [195 1 2 3]
optionalFields{A: 1, B: 0, C: 3} [195 1 128 3]
optionalFields{A: 1, B: 2} [194 1 2]
optionalFields{A: 1, C: 3} [195 1 128 3]
optionalFields{A: 1, B: 2, C: 0} [194 1 2]
&optionalAndTailField{A: 1, B: 2} [194 1 2]
&optionalAndTailField{A: 1} [193 1]
&optionalAndTailField{A: 1, B: 2, Tail: []uint{3, 4}} [196 1 2 3 4]
&optionalAndTailField{A: 1, Tail: []uint{3, 4}} [196 1 128 3 4]
&optionalAndTailField{A: 1} [193 1]
&optionalPtrFiled{A: 1} [193 1]
optionalPtrFiled{A: 1, B: &[3]byte{1, 2, 3}} [197 1 131 1 2 3]
&optionalPtrFieldNil{A: 1} [193 1]

6. 注意

遗憾的是,目前乙太坊官方实现的RLP编码还无法随心所欲地对任何自定义数据类型进行编解码,比如在下面的这个例子里,有一个数据结构如下所示:

type Dog struct {
	Child *Dog
	Name string
}

然后我们实例化一个*Dog

d := &Dog{Child: &Dog{Name: "bb", Child: nil}, Name: "aa"}

接着我们对其进行编码:

bz, _ := EncodeToBytes(d) // bz: [200 196 192 130 98 98 130 97 97]

再然后我们对编码结果进行解码:

dst := &Dog{}
err := DecodeBytes(bz, dst)

程序运行到这里,会报错:

*&rlp.decodeError{msg:"too few elements", typ:(reflect.rtype)(0x6ddba0), ctx:[]string{".Child", ".Child", "(rlp.Dog)"}}

那么上面的问题如何解决呢?其实我们只需要在定义Dog结构体时做一点调整就可以了,如下所示:

type Dog struct {
	Name  string
	Child *Dog `rlp:"optional"`
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrCanonSize        = errors.New("rlp: non-canonical size information")
	ErrExpectedString   = errors.New("rlp: expected String or Byte")
	ErrExpectedList     = errors.New("rlp: expected List")
	ErrCanonInt         = errors.New("rlp: non-canonical integer format")
	ErrElemTooLarge     = errors.New("rlp: element is larger than containing list")
	ErrValueTooLarge    = errors.New("rlp: value size exceeds available input length")
	ErrMoreThanOneValue = errors.New("rlp: input contains more than one value")
)
View Source
var EOL = errors.New("rlp: end of list")

EOL ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

EOL "end of list"

View Source
var EmptyList = []byte{0xC0}
View Source
var EmptyString = []byte{0x80}
View Source
var ErrNegativeBigInt = errors.New("rlp: cannot encode negative big.Int")

ErrNegativeBigInt ♏ |作者:吴翔宇| 🍁 |日期:2022/11/8|

ErrNegativeBigInt 被编码的大整数是一个负数时,会报该错误。

Functions

func AppendUint64

func AppendUint64(b []byte, i uint64) []byte

AppendUint64 ♏ |作者:吴翔宇| 🍁 |日期:2022/11/8|

AppendUint64 接受两个参数,第一个参数是一个字节切片bz,第二个参数是一个64位无符号整数i,该方法的目的就是将整数i的rlp编码 追加到切片bz之后。例如给定切片bz=[129 137],给定整数i=45678,执行该方法得到的切片result=[129 137 130 178 110]。

func CountValues

func CountValues(bz []byte) (int, error)

CountValues ♏ |作者:吴翔宇| 🍁 |日期:2022/11/8|

CountValues 接受一个rlp编码结果bz,该方法的功能就是计算有多少个值被编码进bz里面,例如:

给定bz=[129 130 12 132 97 97 97 97],经过计算我们发现有三个值被编码进去了,分别是数字130、数字12
以及字符串"aaaa"。

func Decode

func Decode(r io.Reader, val interface{}) error

Decode ♏ |作者:吴翔宇| 🍁 |日期:2022/11/11|

Decode 方法接受两个参数,第一个参数是一个 io.Reader,RLP编码数据被存储在里面,第二个参数是一个指针, 将被编码的数据解码到该指针里面。

func DecodeBytes

func DecodeBytes(bz []byte, val interface{}) error

DecodeBytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/11|

DecodeBytes 方法接受两个参数,第一个参数是一个字节切片,里面存储了原始的RLP编码数据,第二个参数是一个指针, 将被编码的数据解码到该指针里面。

func Encode

func Encode(w io.Writer, x interface{}) error

Encode ♏ |作者:吴翔宇| 🍁 |日期:2022/11/9|

Encode 方法接受两个参数:第一个参数是一个 io.Writer,编码结果会被写入到writer里,第二个参数是任意类型的数据, 这个给定的数据就是要被编码的数据。该方法的返回值表明在编码过程中是否出现错误。

func EncodeToBytes

func EncodeToBytes(x interface{}) ([]byte, error)

EncodeToBytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/9|

EncodeToBytes 方法接受一个入参:任意类型的数据x,x是要被编码的数据,返回值有两个,第一个返回值表示 编码结果,第二个返回值表示编码过程中可能出现的错误。

func EncodeToReader

func EncodeToReader(x interface{}) (size int, r io.Reader, err error)

EncodeToReader ♏ |作者:吴翔宇| 🍁 |日期:2022/11/9|

EncodeToReader 方法接受一个入参:任意类型的数据x,x是要被编码的数据,返回值有三个,第一个返回值表示 编码结果的长度(字节个数),第二个参数返回的是一个 *encReader 实例,该实例实现了 Read 方法,Read 方 法接受一个字节切片作为入参,然后将编码结果读取到给定的字节切片中,该方法用于网络传输数据,第三个参数表示 编码过程中可能遇到的错误。

func IntSize

func IntSize(x uint64) int

IntSize ♏ |作者:吴翔宇| 🍁 |日期:2022/11/7|

IntSize 方法接受一个64位无符号整型参数x,该方法的作用是计算对x进行rlp编码后得到的结果的字节长度。对于值 小于128的整数,可以将其看成是一个单独的ASCII码,根据rlp编码规则,所以仅需1个字节就可以存储编码结果;如果 值大于128,例如1025,1025需要两个字节来存储表示:00000100,00000001,那么编码1025这个数字就需要3个字 节:[128+2, 4, 1]->[10000010, 00000100, 00000001]。

func ListSize

func ListSize(contentSize uint64) uint64

ListSize ♏ |作者:吴翔宇| 🍁 |日期:2022/11/7|

ListSize 方法接受一个64位无符号整型参数contentSize,contentSize表示的是对一个列表进行编码后,除去 头剩下的编码内容的长度。这个方法在 Block 和 Transaction 两个结构体实现 DecodeRLP 方法时被调用。

func SplitList

func SplitList(bz []byte) (content, rest []byte, err error)

SplitList ♏ |作者:吴翔宇| 🍁 |日期:2022/11/8|

SplitList 与 SplitString 方法作用类似,该方法明确知道bz里面含有rlp编码列表部分的内容,如果解析出来 的类型显式不是 List,则返回 ErrExpectedList 错误。

func SplitString

func SplitString(bz []byte) (content, rest []byte, err error)

SplitString ♏ |作者:吴翔宇| 🍁 |日期:2022/11/7|

SplitString 与 Split 方法类似,不同的地方在于,SplitString 方法知道自己要解析的对象bz是一个对象字符串进 行rlp编码的结果,所以,如果解析得到编码类型不是 String,则会报错。然后该方法的返回值与 Split 的后三个返回值 具有相同的含义。

func SplitUint64

func SplitUint64(bz []byte) (x uint64, rest []byte, err error)

SplitUint64 ♏ |作者:吴翔宇| 🍁 |日期:2022/11/7|

SplitUint64 方法接受一个字节切片bz作为入参,该方法的作用就是解析bz里面被rlp编码的整数,需要主义的是:

  1. 整数0的编码结果是0x80,它表示的是编码头后面有0个字节的内容是为整数0编码的,那么我们在解码的时候,会 得到0个字节,也就是len(content)=0,整数1的编码结果就是1,直到整数127的编码结果也是127,也就是说,编 码任何非0数字,都至少需要1个字节,那么当编码结果的长度等于1时,编码结果是不可能等于0的。
  2. 64位无符号整数最多由8个字节组成,超过8个字节的都是不合法的。

该方法第2个返回值很有意思,它表示的是整数编码内容后面紧跟着的数据,很像协议号的意思,例如我们给定一段数据 bz=[129 130 97 98 99],解析结果为:0x82, [97 98 99], nil

Types

type ByteReader

type ByteReader interface {
	Read(p []byte) (n int, err error) // 从源中读取至多len(p)个字节到p中
	ReadByte() (byte, error)          // 每次读取一个字节
}

ByteReader ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

ByteReader 接口被例如 bufio.Reader 和 bytes.Reader 实现。这里定义接口的方式与官方源码略有不同,官方源码地址:

https://github.com/ethereum/go-ethereum/blob/972007a517c49ee9e2a359950d81c74467492ed2/rlp/decode.go#L544

type Decoder

type Decoder interface {
	DecodeRLP(*Stream) error
}

Decoder ♏ |作者:吴翔宇| 🍁 |日期:2022/10/31|

那些实现 Decoder 接口的类型,可以自定义解码规则。

type EncodeBuffer

type EncodeBuffer struct {
	// contains filtered or unexported fields
}

EncodeBuffer ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

EncodeBuffer

func NewEncodeBuffer

func NewEncodeBuffer(dst io.Writer) EncodeBuffer

NewEncodeBuffer ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

NewEncodeBuffer 方法根据给定的 io.Writer 生成一个新的 EncodeBuffer。需要注意的是,如果给定的 io.Writer 满足不同的条件,生成的 EncodeBuffer 会有区别:

  • 如果给定的 io.Writer 的底层实现是 EncodeBuffer或者是*EncodeBuffer,再或者是*encBuffer,则生成的 EncodeBuffer 实例如下: EncodeBuffer{buf: encBufferFromWriter(dst), dst: nil, ownBuffer: false} 其中dst就是 NewEncodeBuffer 方法的入参
  • 如果给定的 io.Writer 就是普通的writer,则生成的 EncodeBuffer 实例如下: EncodeBuffer{buf: encBufferPool.Get().(*encBuffer), dst: dst, ownBuffer: true}

func (*EncodeBuffer) AppendToBytes

func (encBuf *EncodeBuffer) AppendToBytes(dst []byte) []byte

AppendToBytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

AppendToBytes 方法接受一个字节切片dst作为入参,该方法就是将 EncodeBuffer.buf 内存储的rlp编码结果追加到dst后面。

func (*EncodeBuffer) Flush

func (encBuf *EncodeBuffer) Flush() error

Flush ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

Flush 方法将 EncodeBuffer.buf 里面缓存的rlp编码数据全部写入到 EncodeBuffer.dst 中,写完之后,EncodeBuffer.buf 如果是 EncodeBuffer 从 encBufferPool 里面拿的,那就要再把它放回去,并且 EncodeBuffer 会被重置为空,也就是说, Flush 方法只能被调用一次,下次还想调用,就必须在调用前先执行 Reset 方法。

func (EncodeBuffer) ListEnd

func (encBuf EncodeBuffer) ListEnd(index int)

ListEnd ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

ListEnd 方法接受一个整型参数index,该方法在编码列表数据结束之后被调用,其目的就是更新 EncodeBuffer.buf.lHeads 上给定 index索引位值处的 listHead.size 和 EncodeBuffer.buf.lHeadsSize 两个字段。实际上,该方法的逻辑通过调用如下函数来实现:

EncodeBuffer.buf.listEnd(index)

func (EncodeBuffer) ListStart

func (encBuf EncodeBuffer) ListStart() int

ListStart ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

ListStart 方法用来往 EncodeBuffer.buf.lHeads 里添加一个 listHead 实例,该方法在编码列表数据前被调用,用来为编码列表数据 作准备,实际上,该方法的逻辑通过调用如下函数来实现:

EncodeBuffer.buf.listStart()

func (*EncodeBuffer) Reset

func (encBuf *EncodeBuffer) Reset(dst io.Writer)

Reset ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

Reset 方法接受一个实现了 io.Writer 接口的对象实例dst,如果当前 EncodeBuffer.buf 是通过 encBufferFromWriter 方法获得的,那么调用 Reset 方法会panic,如果给定的dst参数不为空,且dst的底层实现是 EncodeBuffer 或 *EncodeBuffer 或 *encBuffer,则重置 EncodeBuffer.buf 为dst,并且 EncodeBuffer.ownBuffer 置为false,到此,直接退出 Reset方 法;否则就从 encBufferPool 里面重新获取一个 *encBuffer 实例,并赋值给 EncodeBuffer.buf,然后将 EncodeBuffer.ownBuffer 置为true,最后调用 encBuffer.reset 方法重置 EncodeBuffer.buf。

func (*EncodeBuffer) ToBytes

func (encBuf *EncodeBuffer) ToBytes() []byte

ToBytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

ToBytes 方法调用 EncodeBuffer.buf.makeBytes() 方法,将编码结果完整的返回出来。

func (EncodeBuffer) Write

func (encBuf EncodeBuffer) Write(bz []byte) (int, error)

Write ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

Write 方法接受一个字节切片参数bz,该方法实际上调用 encBuffer.Write 方法实现将bz追加到 encBuffer.str 后面。

func (EncodeBuffer) WriteBigInt

func (encBuf EncodeBuffer) WriteBigInt(i *big.Int)

WriteBigInt ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

WriteBigInt 方法接受一个大整数i,i的类型是*big.Int,该方法的作用就是将大整数编码到 EncodeBuffer.buf.str 里。事实上,大整数不 一定就比最大的64位无符号整数大,在这种情况下,我们可以调用 writeUint64 方法将该所谓的大整数编码到str里;但是,如果给定的大整数大于 最大的64位无符号整数,在这种情况下,我们需要将其看成是一个字节切片进行编码,此时该大整数需要超过8个字节来存储。

func (EncodeBuffer) WriteBool

func (encBuf EncodeBuffer) WriteBool(b bool)

WriteBool ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

WriteBool 方法接受一个bool类型的变量,然后根据其值将其编码到 EncodeBuffer.buf.str 里,如果给的值等于true,那么 就往str里写入0x01,否则写入0x80,0x80表示的是一个空值。

func (EncodeBuffer) WriteBytes

func (encBuf EncodeBuffer) WriteBytes(bz []byte)

WriteBytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

WriteBytes 方法接受一个字节切片bz,该方法的目的就是将字节切片bz编码到 EncodeBuffer.buf.str 里。当bz满足不同情况时,编码 方式也不同,具体会遇到以下两种方式:

  • 给定的字节切片bz长度等于1,并且里面唯一的字节小于或等于0x7F,即127,那么该字节切片(或者说该字节更准确)会被直接追加到str后。
  • 给定的字节切片长度大于1,那么会将bz的长度先编码到str里,这里又会遇到两种情况:1.长度小于56;2.长度大于55。面对不同的情况, 如何对字节长度进行编码必须遵守以下准则: · 当长度size小于56,则将0x80+size的值追加到str后 · 当长度size大于55,例如1024,存储1024最少需要2个字节,并且这两个字节分别是00000100和00000000,那么就将0xB7+2和这 两个字节追加到str后,字节的长度被编码到str里后,剩下的工作则是直接将切片bz追加到str后。

func (EncodeBuffer) WriteString

func (encBuf EncodeBuffer) WriteString(s string)

WriteString ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

WriteString 方法接受一个字符串参数s,该方法的作用就是将s编码到 EncodeBuffer.buf.str 里,实际上,该方法的逻辑是调用了如下 函数,来实现将s编码到str里:

EncodeBuffer.buf.writeString(s)

func (EncodeBuffer) WriteUint64

func (encBuf EncodeBuffer) WriteUint64(i uint64)

WriteUint64 ♏ |作者:吴翔宇| 🍁 |日期:2022/11/5|

WriteUint64 方法接受一个64位的无符号整数作为输入参数,然后将其编码到 EncodeBuffer.buf.str 里,如果输入的整数大小小于 128,则可以将其看成是一个单独的ASCII码,那么该整数自身就可以作为编码结果写入到str里;如果给定的整数大于或等于128,则会计算 需要多少个字节才能存储这个数,例如需要n个,那么编码结果就是(0x80+n||整数的字节表现形式);如果给定的整数等于0,则将0x80写 入到str里,代表空值,下面给出三个示例来对该方法的逻辑进行解释:

  • 0:append(str, 0x80)
  • 123:append(str, byte(123))
  • 1024:append(str, []byte{0x80+2, 000000100, 00000000})

type Encoder

type Encoder interface {
	EncodeRLP(io.Writer) error
}

Encoder ♏ |作者:吴翔宇| 🍁 |日期:2022/10/31|

那些实现 Encoder 接口的类型,可以自定义编码规则。

type Kind

type Kind int8
const (
	Byte Kind = iota
	String
	List
)

func Split

func Split(bz []byte) (k Kind, content, rest []byte, err error)

Split ♏ |作者:吴翔宇| 🍁 |日期:2022/11/7|

Split 方法接受一个字节切片bz作为入参,bz是一个rlp编码结果,Split 方法解析bz,返回被编码的对象是何种类型, Byte 、 String 或 List,然后第二个参数返回的是编码结果除编码前缀外剩下的编码内容,第三个参数返回的是其他 编码数据,第四个参数返回的是 Split 方法在执行过程中可能遇到的错误,这里只会遇到两种错误,一个是 ErrCanonSize, 另一个是 ErrValueTooLarge。

给一个例子:bz = [204 131 97 97 97 8 198 133 72 101 102 101 105]
对给定的bz进行解析,返回的结果将是:List, [131 97 97 97 8 198 133 72 101 102 101 105], [], nil

func (Kind) String

func (k Kind) String() string

type RawValue

type RawValue []byte

RawValue ♏ |作者:吴翔宇| 🍁 |日期:2022/11/6|

RawValue 官方对其解释是:

RawValue代表一个已编码的RLP值,可用于延迟RLP解码或预先计算一个编码。请注意,解码器并不验证RawValues的内容是否是有效的RLP。

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream ♏ |作者:吴翔宇| 🍁 |日期:2022/10/30|

Stream

func NewListStream

func NewListStream(r io.Reader, inputLimit uint64) *Stream

NewListStream ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

NewListStream 与 NewStream 方法相比,该方法有两处不同,一是 *Stream.kind 被设置为 List,二是 *Stream.size 被设置为该方法的第二个入参:inputLimit。值得一提的是,该方法只在测试文件中被调用。

func NewStream

func NewStream(r io.Reader, inputLimit uint64) *Stream

NewStream ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

NewStream 方法接受两个入参:io.Reader 和一个64位无符号整数 inputLimit,这两个参数用来实例化 *Stream, *Stream 的读取源 *Stream.r 会被设置为 io.Reader,然后如果 inputLimit 大于0,则 *Stream.limited 会被置为 true,而 *Stream.remaining 会被置为 inputLimit,否则 *Stream.remaining 会被设置为 io.Reader 的长度

func (*Stream) Bytes

func (s *Stream) Bytes() ([]byte, error)

Bytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/11|

Bytes 方法返回底层stream中存储的接下来的字符串解码结果,不能是列表数据。

func (*Stream) Decode

func (s *Stream) Decode(val interface{}) error

Decode ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

Decode 这个方法非常类似于 json.Unmarshal 方法,接受某个类型的指针,然后将底层stream存储的rlp编码内容解码到 给定类型指针指向的空间里。实际上,给定某个类型的指针,我们首先要从 typeCache 缓冲区里寻找针对该类型的解码器,找 到的话就直接用,找不到的话就生成一个。

func (*Stream) Kind

func (s *Stream) Kind() (kind Kind, size uint64, err error)

Kind ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

Kind 方法返回下一个编码数据的类型和其EC部分的大小,类型就三类:Byte、String、List。 如果每次在 ListStart 方法被调用之后再调用此方法,会从底层stream中读取一个字节的TMB(类型标记位),因此, Stream.remaining 和 Stream.stack 里的最后一个元素会被减一。

func (*Stream) ListEnd

func (s *Stream) ListEnd() error

ListEnd ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

ListEnd

func (*Stream) ListStart

func (s *Stream) ListStart() (size uint64, err error)

ListStart ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

ListStart 官方源码的写法是:"List",我将其改成了:"ListStart",该方法返回的第一个参数表示list 编码数据EC部分的长度。

接下来要解码的数据是一个list的RLP编码结果,在解码前,需要做一些准备工作。

func (*Stream) Raw

func (s *Stream) Raw() ([]byte, error)

Raw ♏ |作者:吴翔宇| 🍁 |日期:2022/11/11|

Raw 方法返回stream里存储的 RawValue 数据。

func (*Stream) ReadBytes

func (s *Stream) ReadBytes(bz []byte) error

ReadBytes ♏ |作者:吴翔宇| 🍁 |日期:2022/11/11|

ReadBytes 方法接受一个字节切片bz,从底层stream解码出相应长度的字符串,非列表数据。

func (*Stream) Reset

func (s *Stream) Reset(r io.Reader, inputLimit uint64)

Reset ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

Reset 方法接受两个入参:io.Reader 和一个64位无符号整数 inputLimit,这两个参数用来重置 *Stream, *Stream 的读取源 *Stream.r 会被 io.Reader 替换,然后如果 inputLimit 大于0,则 *Stream.limited 会被置为 true,而 *Stream.remaining 会被置为 inputLimit,否则 *Stream.remaining 会被设置为 io.Reader 的长度

func (*Stream) Uint64

func (s *Stream) Uint64() (uint64, error)

Uint64 ♏ |作者:吴翔宇| 🍁 |日期:2022/11/10|

Uint64 方法从底层stream解码出一个64位无符号整数。

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL