以太坊Geth RLP编码源码解析

RLP编码

基本原理

RLP (Recursive Length Prefix) 编码唯一的目的是解决结构体编码问题。RLP编码设计的两个结构体分别为:String和List。它们的定义如下:

String指的是一串字节,对应Go语言中的 string/[]byte/uint/[]uint/[N]uint 等
List指定是由许多String和List组成的一个列表,如 struct/[]interface{} 等

比如,"\x11 \x22 \x33" 和 “abc” 为String类型,而 [“ab”, [“abc”,“a”], “\x11”] 则为List类型。

对于String类型,RLP编码的规则如下:

  1. 如果String的长度为 1 1 1,且它的值在 [ 0 x 00 , 0 x 7 f ] [0x00,0x7f] [0x00,0x7f] 之间,则其RLP编码就是字符本身,即前缀为空。前缀的值在 [ 0 x 00 , 0 x 7 f ] [0x00,0x7f] [0x00,0x7f] 之间。
  2. 否则,若String的长度小于 56 56 56,则其RLP编码是前缀拼接上字符本身,其前缀为 0 x 80 0x80 0x80 加上String长度。前缀的值在 [ 0 x 80 , 0 x b 7 ] [0x80,0xb7] [0x80,0xb7] 之间。
  3. 否则,若String的长度小于 2 64 2^{64} 264,则其RLP编码是前缀拼接上字符长度再拼接上字符本身,其前缀为 0 x b 7 0xb7 0xb7 加上String长度的二进制表示的字节长度。比如,一个String的长度为 1024 1024 1024,则其长度的二进制表示为 b 00000100 00000000 b00000100\quad00000000 b0000010000000000,其对应的十六进制表示为 0 x 0400 0x0400 0x0400,字节长度为 2 2 2。前缀的值在 [ 0 x b 8 , 0 x b f ] [0xb8,0xbf] [0xb8,0xbf] 之间。
  4. 否则,即String的长度大于等于 2 64 2^{64} 264,则其RLP编码为 ∅ \varnothing ∅。

从编码规则中可以知道,前缀的值和String的长度是有关的。另外需要注意,String长度为0的情况以及String长度为1但它的值不在 [ 0 x 00 , 0 x 7 f ] [0x00,0x7f] [0x00,0x7f] 中的情况是包含在第2条规则中的。

对于List类型,RLP编码的规则如下:

  1. 如果List中各项的RLP编码拼接起来长度小于 56 56 56,则其RLP编码是前缀拼接上各项的RLP编码,其前缀为 0 x c 0 0xc0 0xc0 加上各项的RLP编码拼接后的总长度。前缀的值在 [ 0 x c 0 , 0 x f 7 ] [0xc0,0xf7] [0xc0,0xf7] 之间。
  2. 否则,如果List中各项的RLP编码拼接起来长度小于 2 64 2^{64} 264,则其RLP编码是前缀拼接上各项编码拼接后的总长度再拼接上各项的RLP编码,其前缀为 0 x f 7 0xf7 0xf7 加上总长度的二进制表示的字节长度。前缀的值在 [ 0 x f 8 , 0 x f f ] [0xf8,0xff] [0xf8,0xff] 之间。

由于列表是任意嵌套的,因此列表的编码是递归的。

下图展示了RLP编码的过程。其中,黑色字段表示需要编码的数据,红色字段表示编码的结果。叶子节点表示一个String而非叶子节点表示List,并且非叶子节点代表的List中的各项由其孩子组成。由于编码的前缀字段反映了数据的类型以及长度信息,因此我们可以通过前缀字段对编码的数据进行解码。
以太坊Geth RLP编码源码解析

此外,对于空字符串以及空列表,它们的编码分别为 “\x80” 和 “\xc0”。

具体实现

geth对于RLP编码的实现位于 go-ethereum/rlp 文件夹下,其中主要的代码文件包括 typecache.go, encode.go, decode.go。由于go语言中存在各种内置数据类型以及用户自定义类型,因此针对不同的数据类型,编码器和解码器的具体实现有所不同。

RLP编码模块的总体框架如下:
以太坊Geth RLP编码源码解析

该模块的导出函数分别为 EncodeToBytes/Encode/EncodeToReader 和 Decode/DecodeBytes。根据需求不同,外界可以调用这个包中的不同的函数。根据编码或者解码的数据类型,导出函数利用typecache.go 中的 cachedTypeInfo 函数获取相应的编码器或者解码器。typecache.go 中会暂存相应类型的编码器或者解码器在 typeCache 变量中。在利用cachedTypeInfo 获取编码器或者解码器时,如果相应类型的编码器或者解码器还未被暂存,则会调用 encode.go 中的 makeWriter 和 decode.go 中的 makeDecoder 生成对应的编码器和解码器,并将其暂存下来。在下次需要用到这个编码器或解码器时,便可以直接从 typeCache 中获取。

typecache.go

typecache.go 中通过 typeCache 变量保存已经生成的编码器和解码器(针对不同的go语言数据类型,需要用到不同的编码器和解码器),并利用 typeCacheMutex 实现对 typeCache 的互斥访问。它们的定义如下:

var (
	typeCacheMutex sync.RWMutex
	typeCache      = make(map[typekey]*typeinfo)
)

type typekey struct {
	reflect.Type
	tags
}

type typeinfo struct {
	decoder    decoder
	decoderErr error // error from makeDecoder
	writer     writer
	writerErr  error // error from makeWriter
}

// tags represents struct tags.
type tags struct {
	// rlp:"nil" controls whether empty input results in a nil pointer.
	nilOK bool

	// This controls whether nil pointers are encoded/decoded as empty strings
	// or empty lists.
	nilKind Kind

	// rlp:"tail" controls whether this field swallows additional list
	// elements. It can only be set for the last field, which must be
	// of slice type.
	tail bool

	// rlp:"-" ignores fields.
	ignored bool
}

type decoder func(*Stream, reflect.Value) error

type writer func(reflect.Value, *encbuf) error

typekey 中保存了相应的类型信息,typeinfo 中保存了对应类型的编码器和解码器(函数),如果在创建编码器和解码器时出错,它会记录相应的错误信息。另外,对于结构体中的字段,它们的类型信息还包括相应的 tags。

如对于以下结构体:

type S struct{
	X uint
    Y uint `rlp:"nil"`
}

则在对结构体 S 编码时会忽略其中的的字段 Y。

typecache.go 中提供了 cachedTypeInfo 函数来获取对应类型的编码器和解码器,如果不存在,则通过 generate 函数生成。

func cachedTypeInfo(typ reflect.Type, tags tags) *typeinfo {
    typeCacheMutex.Lock()
	defer typeCacheMutex.Unlock()
	key := typekey{typ, tags}
	info := typeCache[key]
	if info != nil {
		return info
	}
	info = new(typeinfo)
	typeCache[key] = info
	info.generate(typ, tags)
	return info
}

另外,typecache.go 中还提供了 structFields 函数来解析结构体中的每一个字段(导出字段,首字母大写),并根据相应字段的类型及 tags 信息创建对应的编码器和解码器保存在 typeCache 中。

encode.go

encode.go 中提供了 Encode/EncodeToBytes/EncodeToReader 三个导出函数来实现RLP编码的不同输出形式。这些函数都会调用 encode 函数来进行编码,其中 encode 函数又会从 typeCache 中获取相应的编码器进行编码操作。

func (w *encbuf) encode(val interface{}) error {
	rval := reflect.ValueOf(val)
	writer, err := cachedWriter(rval.Type())
	if err != nil {
		return err
	}
	return writer(rval, w)
}

其中借助到了 encbuf 这一结构体,它的主要作用是暂存编码的中间结果,并通过它可以获取最终的编码结果。它的定义如下:

type encbuf struct {
	str      []byte        // string data, contains everything except list headers
	lheads   []listhead    // all list headers
	lhsize   int           // sum of sizes of all encoded list headers
	sizebuf  [9]byte       // auxiliary buffer for uint encoding
	bufvalue reflect.Value // used in writeByteArrayCopy
}

type listhead struct {
	offset int // index of this header in string data
	size   int // total size of encoded data (including list headers)
}

注意到 str 中存储的不是最终RLP编码的结果,因为它不包含List类型的头部,而List类型的头部则被存储在 lheads 中,最终的编码结果可以由这两项导出(通过 toBytes 和 toWriter 函数实现)。listhead 中存储了相应List类型头部在编码中的偏移 offset 及整个List类型的大小 size(注释中包含头部的含义是如果是嵌套List,则 size的大小包含了其中子List加上其头部的大小,但是不包含自身头部的大小)。

encode.go 中实现了针对go语言中各种不同类型的编码器的实现,cachedTypeInfo 函数中 generate 函数就会调用其中的 makeWriter 来生成相应的编码器。

// makeWriter creates a writer function for the given type.
func makeWriter(typ reflect.Type, ts tags) (writer, error) {
	kind := typ.Kind()
	switch {
	case typ == rawValueType:
		return writeRawValue, nil
	case typ.AssignableTo(reflect.PtrTo(bigInt)):
		return writeBigIntPtr, nil
	case typ.AssignableTo(bigInt):
		return writeBigIntNoPtr, nil
	case kind == reflect.Ptr:
		return makePtrWriter(typ, ts)
	case reflect.PtrTo(typ).Implements(encoderInterface):
		return makeEncoderWriter(typ), nil
	case isUint(kind):
		return writeUint, nil
	case kind == reflect.Bool:
		return writeBool, nil
	case kind == reflect.String:
		return writeString, nil
	case kind == reflect.Slice && isByte(typ.Elem()):
		return writeBytes, nil
	case kind == reflect.Array && isByte(typ.Elem()):
		return makeByteArrayWriter(typ), nil
	case kind == reflect.Slice || kind == reflect.Array:
		return makeSliceWriter(typ, ts)
	case kind == reflect.Struct:
		return makeStructWriter(typ)
	case kind == reflect.Interface:
		return writeInterface, nil
	default:
		return nil, fmt.Errorf("rlp: type %v is not RLP-serializable", typ)
	}
}

通过传入相应的类型及标志,makeWriter 函数会返回相应的编码器。

String类型

这里以 Uint 为例,注意所有go语言中的具体类型(包括用户自定义类型)都可以被抽象为String类型和List类型,其中Uint可以被抽象为String类型。

case isUint(kind):
	return writeUint, nil

func isUint(k reflect.Kind) bool {
	return k >= reflect.Uint && k <= reflect.Uintptr
}

如果类型属于 Uint,则对应的编码器为 writeUint 函数,其定义如下:

func writeUint(val reflect.Value, w *encbuf) error {
	w.encodeUint(val.Uint())
	return nil
}

func (w *encbuf) encodeUint(i uint64) {
	if i == 0 {
		w.str = append(w.str, 0x80)
	} else if i < 128 {
		// fits single byte
		w.str = append(w.str, byte(i))
	} else {
		s := putint(w.sizebuf[1:], i)
		w.sizebuf[0] = 0x80 + byte(s)
		w.str = append(w.str, w.sizebuf[:s+1]...)
	}
}

它的编码形式其实就是将其抽象成String类型按前面所述的规则进行编码。

List类型

这里以 Struct 为例,它可以被抽象为List类型:

case kind == reflect.Struct:
	return makeStructWriter(typ)

其中 makeStructWriter 函数的定义如下:

func makeStructWriter(typ reflect.Type) (writer, error) {
	fields, err := structFields(typ)
	if err != nil {
		return nil, err
	}
	for _, f := range fields {
		if f.info.writerErr != nil {
			return nil, structFieldError{typ, f.index, f.info.writerErr}
		}
	}
	writer := func(val reflect.Value, w *encbuf) error {
		lh := w.list()
		for _, f := range fields {
			if err := f.info.writer(val.Field(f.index), w); err != nil {
				return err
			}
		}
		w.listEnd(lh)
		return nil
	}
	return writer, nil
}

该函数返回一个该 Struct 对应的编码器,这个编码器会解析 Struct 中的各个字段,并对各个字段采用相应的编码方式进行编码。同时,注意到,由于 Struct 可以抽象为List类型,因此,在对其进行编码开始已经编码结束时会分别调用 list 以及 listEnd 函数以设置 encbuf 中 lhead s一项 的 offset 以及 size 项。这里给出 list 函数及 listEnd 函数的定义:

// list adds a new list header to the header stack. It returns the index
// of the header. The caller must call listEnd with this index after encoding
// the content of the list.
func (w *encbuf) list() int {
	w.lheads = append(w.lheads, listhead{offset: len(w.str), size: w.lhsize})
	return len(w.lheads) - 1
}

func (w *encbuf) listEnd(index int) {
	lh := &w.lheads[index]
	lh.size = w.size() - lh.offset - lh.size
	if lh.size < 56 {
		w.lhsize++ // length encoded into kind tag
	} else {
		w.lhsize += 1 + intsize(uint64(lh.size))
	}
}

list 函数创建一个 listhead 结构体加入到 encbuf 的 lheads 中, listEnd 函数则会在List类型编码结束后设置对应List类型的大小 size字段。

其他各种go语言类型变量都可以被抽象为String类型或者List类型,具体编码方式在细节上有所不同,但总体上都是按照基本原理处所说的方式进行编码

其他细节

编码完成后的信息被存放在 encbuf 结构体中,但里面存储的不是最终的RLP编码,而是一个中间结果。我们可以通过调用 toBytes 函数将其转换成字节串的形式输出。至于为什么不直接存储最终的RLP编码结果,我想是因为这样可以让用户选择不同的输出形式,如 toBytes 或者 toWriter。其中 toBytes 函数的内容如下:

func (w *encbuf) toBytes() []byte {
	out := make([]byte, w.size())
	strpos := 0
	pos := 0
	for _, head := range w.lheads {
		// write string data before header
		n := copy(out[pos:], w.str[strpos:head.offset])
		pos += n
		strpos += n
		// write the header
		enc := head.encode(out[pos:])
		pos += len(enc)
	}
	// copy string data after the last list header
	copy(out[pos:], w.str[strpos:])
	return out
}

这个函数将最终的RLP编码结果写入到 out 中,并输出。因此需要不断从 w *encbuf 中向 out 拷贝数据,其中 pos 表示当前拷贝到 out 中的位置, strpos 表示当前拷贝在 w.str 中的位置,这段代码的最终结果会将 w.str 的内容在适当位置加上List类型头部并写入 out 中,返回。

decode.go

decode.go 中提供了 Decode/DecodeBytes 两个个导出函数来实现从不同输入源获取RLP编码并进行解码。这些函数都会调用 Decode 函数来进行解码,其中 encode 函数又会从 typeCache 中获取相应的解码器进行编码操作。

// Decode decodes a value and stores the result in the value pointed
// to by val. Please see the documentation for the Decode function
// to learn about the decoding rules.
func (s *Stream) Decode(val interface{}) error {
	if val == nil {
		return errDecodeIntoNil
	}
	rval := reflect.ValueOf(val)
	rtyp := rval.Type()
	if rtyp.Kind() != reflect.Ptr {
		return errNoPointer
	}
	if rval.IsNil() {
		return errDecodeIntoNil
	}
	decoder, err := cachedDecoder(rtyp.Elem())
	if err != nil {
		return err
	}

	err = decoder(s, rval.Elem())
	if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 {
		// add decode target type to error so context has more meaning
		decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")"))
	}
	return err
}

其中要求 val 是一个指向go语言类型的指针,如果其不是指针或者指针为空,则返回相应的错误。否则,在 typeCache 中查询相应的解码器进行解码。

解码的过程中需要借助 Stream 这一结构体,它用来存储待解码的数据以及一些解码过程中的中间信息,其定义如下:

type Stream struct {
	r ByteReader

	// number of bytes remaining to be read from r.
	remaining uint64
	limited   bool

	// auxiliary buffer for integer decoding
	uintbuf []byte

	kind    Kind   // kind of value ahead
	size    uint64 // size of value ahead
	byteval byte   // value of single byte in type tag
	kinderr error  // error from last readKind
	stack   []listpos
}
// Kind 为 -1, Byte, String, List 其中之一

type listpos struct{ pos, size uint64 }
//pos为已经读取的数据在整个List类型中的位置,size为List类型原始数据的大小

其中,r 中存储了待解码的数据,需要不断从中读取数据并解码。每次进行解码,需要调用 Reset 函数初始化待解码的数据,在调用重置函数 Reset 时也会重置remaining, limited, uintbuf, kind, size, byteval, kinderr, stack 这些字段。如果是待解码的数据是字节串,则会设置 limited 为 true,同时设置 remaining 为其长度。uintbuf 用来暂存如字节串长度等信息,kind表示待解码的数据类型(初始化为-1),size表示原生的数据长度,如果 kind 类型为 Byte 则 byteval 为单字节数据的值,kinderr 为读取类型时遇到的错误,stack 主要用于处理嵌套列表的情况。

String类型

这里以 Uint 为例,观察其具体的解码器是怎么实现的

case isUint(kind):
	return decodeUint, nil
	
func decodeUint(s *Stream, val reflect.Value) error {
	typ := val.Type()
	num, err := s.uint(typ.Bits())
	if err != nil {
		return wrapStreamError(err, val.Type())
	}
	val.SetUint(num)
	return nil
}

func (s *Stream) uint(maxbits int) (uint64, error) {
	kind, size, err := s.Kind()
	if err != nil {
		return 0, err
	}
	switch kind {
	case Byte:
		if s.byteval == 0 {
			return 0, ErrCanonInt
		}
		s.kind = -1 // rearm Kind 重置类型
		return uint64(s.byteval), nil
	case String:
		if size > uint64(maxbits/8) {
			return 0, errUintOverflow
		}
		v, err := s.readUint(byte(size))
		switch {
		case err == ErrCanonSize:
			// Adjust error because we're not reading a size right now.
			return 0, ErrCanonInt
		case err != nil:
			return 0, err
		case size > 0 && v < 128:
			return 0, ErrCanonSize
		default:
			return v, nil
		}
	default:
		return 0, ErrExpectedString
	}
}

decodeUint 函数获取要解码数据类型的比特数,传入 Stream 结构体的成员函数 uint。 uint 函数首先调用 Kind 函数获取接下来要解码数据的类型 kind (Byte/String/List)以及原始数据的大小 size(所占字节数),如果调用 Kind 函数中出错,则会返回相应的错误信息到 err 中。如果没有出错,那么根据 Kind 函数获取的待解码数据类型,如果待解码数据为单字节 Byte,那么其原生数据和编码后的数据是相同的,Byte类型的数据值在调用 Kind 函数时会被存放在 Stream 的 byteval 中,直接返回它的值就可以了;如果待解码数据是 String,那么通过获取的原始数据大小 size 去读取接下来的数据即可,同时还会有相应的错误检测过程,如果无误,则返回结果。

List类型

这里以 Struct 为例,观察其具体的解码器是怎么实现的

case kind == reflect.Struct:
	return makeStructDecoder(typ)

func makeStructDecoder(typ reflect.Type) (decoder, error) {
	fields, err := structFields(typ)
	if err != nil {
		return nil, err
	}
	for _, f := range fields {
		if f.info.decoderErr != nil {
			return nil, structFieldError{typ, f.index, f.info.decoderErr}
		}
	}
	dec := func(s *Stream, val reflect.Value) (err error) {
		if _, err := s.List(); err != nil {
			return wrapStreamError(err, typ)
		}
		for _, f := range fields {
			err := f.info.decoder(s, val.Field(f.index))
			if err == EOL {
				return &decodeError{msg: "too few elements", typ: typ}
			} else if err != nil {
				return addErrorContext(err, "."+typ.Field(f.index).Name)
			}
		}
		return wrapStreamError(s.ListEnd(), typ)
	}
	return dec, nil
}

这里的实现形式其实与 encode.go 中的类似,只是将编码器改成了解码器。主要需要观察其中的 s.List 和 s.ListEnd 函数是怎么修改 s.stack 从而创建List类型信息的。它们的实现代码如下:

// List starts decoding an RLP list. If the input does not contain a
// list, the returned error will be ErrExpectedList. When the list's
// end has been reached, any Stream operation will return EOL.
func (s *Stream) List() (size uint64, err error) {
	kind, size, err := s.Kind()
	if err != nil {
		return 0, err
	}
	if kind != List {
		return 0, ErrExpectedList
	}
	s.stack = append(s.stack, listpos{0, size})
	s.kind = -1
	s.size = 0
	return size, nil
}

// ListEnd returns to the enclosing list.
// The input reader must be positioned at the end of a list.
func (s *Stream) ListEnd() error {
	if len(s.stack) == 0 {
		return errNotInList
	}
	tos := s.stack[len(s.stack)-1]
	if tos.pos != tos.size {
		return errNotAtEOL
	}
	s.stack = s.stack[:len(s.stack)-1] // pop
	if len(s.stack) > 0 {
		s.stack[len(s.stack)-1].pos += tos.size
	}
	s.kind = -1
	s.size = 0
	return nil
}

List 函数通过调用 s.Kind 来获取待解码数据的类型及大小,如果待解码的数据类型为List类型,则设置 s.stack,之后需要重置 s.kind 和 s.size。ListEnd 函数表示已经完成对一个 List 类型的解析,因此需要将其从 s.stack 中取出,在取出之前需要比较它的 pos 字段 和 size 字段来确保已经读完整个List,最后如果存在嵌套列表,则还需要对其父List的 pos 加上相应的长度,表示这段已经解析过了。

其他细节

由于所有类型的编码器的实现都借助到了 Stream 结构体中的成员函数,因此需要查看一下其中一些重要的成员函数是怎么实现的。

对于 Kind 函数:

// Kind returns the kind and size of the next value in the
// input stream.
//
// The returned size is the number of bytes that make up the value.
// For kind == Byte, the size is zero because the value is
// contained in the type tag.
//
// The first call to Kind will read size information from the input
// reader and leave it positioned at the start of the actual bytes of
// the value. Subsequent calls to Kind (until the value is decoded)
// will not advance the input reader and return cached information.
func (s *Stream) Kind() (kind Kind, size uint64, err error) {
	var tos *listpos
	if len(s.stack) > 0 {
		tos = &s.stack[len(s.stack)-1]
	}
	if s.kind < 0 {
		s.kinderr = nil
		// Don't read further if we're at the end of the
		// innermost list.
		if tos != nil && tos.pos == tos.size {
			return 0, 0, EOL
		}
		s.kind, s.size, s.kinderr = s.readKind()
		if s.kinderr == nil {
			if tos == nil {
				// At toplevel, check that the value is smaller
				// than the remaining input length.
				if s.limited && s.size > s.remaining {
					s.kinderr = ErrValueTooLarge
				}
			} else {
				// Inside a list, check that the value doesn't overflow the list.
				if s.size > tos.size-tos.pos {
					s.kinderr = ErrElemTooLarge
				}
			}
		}
	}
	// Note: this might return a sticky error generated
	// by an earlier call to readKind.
	return s.kind, s.size, s.kinderr
}

Kind 函数返回输入流中下一个需要解码的数据的类型及大小,同时在获取数据大小由于需要从输入流中读取数据,因此会更新输入流中读指针的位置。首先获取s.stack 中的最后一个元素,如果当前解码的类型为一个列表,那么获取的内容肯定不为空(利用这个可以检测需要解码的数据是否已经超出了整个列表的长度)。

当已经成功处理完一个待解码的数据(此时s.kind==-1),那么会通过调用 s.readKind 函数获取下一个要处理的数据类型,大小等信息,另外还需要检查这个大小信息是否已经超出了长度限制;否则,直接返回当前待处理的数据类型,大小等信息。s.readKind 函数的定义如下:

func (s *Stream) readKind() (kind Kind, size uint64, err error) {
	b, err := s.readByte()
	if err != nil {
		if len(s.stack) == 0 {
			// At toplevel, Adjust the error to actual EOF. io.EOF is
			// used by callers to determine when to stop decoding.
			switch err {
			case io.ErrUnexpectedEOF:
				err = io.EOF
			case ErrValueTooLarge:
				err = io.EOF
			}
		}
		return 0, 0, err
	}
	s.byteval = 0
	switch {
	case b < 0x80:
		// For a single byte whose value is in the [0x00, 0x7F] range, that byte
		// is its own RLP encoding.
		s.byteval = b
		return Byte, 0, nil
	case b < 0xB8:
		// Otherwise, if a string is 0-55 bytes long,
		// the RLP encoding consists of a single byte with value 0x80 plus the
		// length of the string followed by the string. The range of the first
		// byte is thus [0x80, 0xB7].
		return String, uint64(b - 0x80), nil
	case b < 0xC0:
		// If a string is more than 55 bytes long, the
		// RLP encoding consists of a single byte with value 0xB7 plus the length
		// of the length of the string in binary form, followed by the length of
		// the string, followed by the string. For example, a length-1024 string
		// would be encoded as 0xB90400 followed by the string. The range of
		// the first byte is thus [0xB8, 0xBF].
		size, err = s.readUint(b - 0xB7)
		if err == nil && size < 56 {
			err = ErrCanonSize
		}
		return String, size, err
	case b < 0xF8:
		// If the total payload of a list
		// (i.e. the combined length of all its items) is 0-55 bytes long, the
		// RLP encoding consists of a single byte with value 0xC0 plus the length
		// of the list followed by the concatenation of the RLP encodings of the
		// items. The range of the first byte is thus [0xC0, 0xF7].
		return List, uint64(b - 0xC0), nil
	default:
		// If the total payload of a list is more than 55 bytes long,
		// the RLP encoding consists of a single byte with value 0xF7
		// plus the length of the length of the payload in binary
		// form, followed by the length of the payload, followed by
		// the concatenation of the RLP encodings of the items. The
		// range of the first byte is thus [0xF8, 0xFF].
		size, err = s.readUint(b - 0xF7)
		if err == nil && size < 56 {
			err = ErrCanonSize
		}
		return List, size, err
}

这个函数通过解析编码的头部信息,来返回被编码数据的原始类型(Byte/String/List)和大小信息。其中 s.readUint() 函数的作用是读取相应大小的字节数据。

reflect库说明

为了方便实现对不同go数据类型的编码解码,需要借助reflect库,其具体说明见这里

上一篇:以太合约 JSON-RPC API


下一篇:关联规则ASSOCIATION RULE