美文网首页
以太坊encoding.go 源码解析-keybytes,Hex

以太坊encoding.go 源码解析-keybytes,Hex

作者: Amberlucky | 来源:发表于2018-08-21 14:09 被阅读0次

    以太坊源码(一)Merkle-Patricia Trie(MPT)的实现 这篇文章讲的比较清楚,参考了他的文章,对decoding.go进行了解读,内容查看备注信息。

    以下除代码外的文字描述内容转载自上述文章
    MPT中涉及到了三种编码,分别为keybytes编码、Hex编码和Compact编码

    keybytes 编码这种编码格式就是原生的key字节数组,大部分的Trie的API都是使用这边编码格式

    Hex 编码: 当 [k,v] 数据插入 MPT 时,它们的 k(key)都必须经过编码。这时对 key 的编码,要保证原本是 []byte 类型的 key 能够以 16 进制形式按位进入 fullNode.Children[],因为 Children[] 数组最多只能容纳 16 个子节点。相应的,Ethereum 代码中在这里定义了一种编码方式叫 Hex,将 1byte 的字符大小限制在 4bit(16 进制)以内。

    先来看 Hex 编码的实现,Hex 编码的基本逻辑如下图:

    image.png

    很简单,就是将 keybytes 中的 1byte 信息,将高 4bit 和低 4bit 分别放到两个 byte 里,最后在尾部加 1byte 标记当前属于 Hex 格式。这样新产生的 key 虽然形式还是 []byte,但是每个 byte 大小已经被限制在 4bit 以内,代码中把这种新数据的每一位称为 nibble。这样经过编码之后,带有[]nibble 格式的 key 的数据就可以顺利的进入 fullNode.Children[] 数组了。

    节点被加载到内存里面的时候key使用的是这种编码,因为它的方便访问。

    Hex 编码虽然解决了 key 是 keybytes 形式的数据插入 MPT 的问题,但代价也很大,就是数据冗余。典型的如 shortNode,目前 Hex 格式下的 Key,长度会变成是原来 keybytes 格式下的两倍。这一点对于节点的哈希计算,比如计算 hashNode,影响很大。所以 Ethereum 又定义了另一种编码格式叫 Compact,用来对 Hex 格式进行优化。

    Compact 编码:又叫 hex prefix 编码,它的主要意图是将 Hex 格式的字符串恢复到 keybytes 的格式,同时要加入当前 Compact 格式的标记位,还要考虑在奇偶不同长度 Hex 格式字符串下,避免引入多余的 byte。

    image.png

    如上图所示,

    • Compact 编码首先将 Hex 尾部标记 byte 去掉,然后将原本每 2 nibble 的数据合并到 1byte;
    • 增添 1byte 在输出数据头部以放置 Compact 格式标记位00100000;
    • 如果输入 Hex 格式字符串有效长度为奇数,还可以将 Hex 字符串的第一个 nibble 放置在标记位 byte 里的低 4bit,并增加奇数位标志 0011xxxx。

    节点放入数据库时候的key用到的就是Compact编码,可以节约磁盘空间。

    trie/encoding.go源码

    encoding.go主要处理trie树中的三种编码格式的相互转换的工作

    作者:duanyu
    链接:https://www.jianshu.com/p/1e7455d00065
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    这个代码很好理解,结合测试用例测试一下,就能很清楚的理解了。

    package trie
    
    import (
        "fmt"
    )
    // 将HEX转化为Compact格式
    // 1.若存在标志位,则去掉标志位,并且设置terminator为1(叶子节点),否则为0(扩展节点)
    // 2.若HEX为奇数位,则叶子节点buf[0]:0011xxxx,扩展节点:buf[0]:0001xxxx,其中xxxx为hex[0]即第一个nibble字节
    // 2.若HEX为偶数位,则叶子节点buf[0]:00100000,扩展节点:buf[0]:00000000。
    // 3.将每两个nibble的数据合并到1byte,返回buf
    func hexToCompact(hex []byte) []byte {
        terminator := byte(0)
        // 如果hex存在标志位,那么terminator置为1,并且去掉标志位
        // terminator为1的为叶子节点
        if hasTerm(hex) {
            terminator = 1
            hex = hex[:len(hex)-1]
        }
        fmt.Println("hex length:", len(hex))
        buf := make([]byte, len(hex)/2+1)
        // 增添 1byte 在输出数据头部以放置 Compact 格式标记位00100000; 若terminator为0,即扩展节点,buf[0]= 00000000
        buf[0] = terminator << 5 // the flag byte
        // 如果HEX的长度为奇数,则执行一下步骤
        if len(hex)&1 == 1 {
            buf[0] |= 1 << 4 // odd flag 叶子节点奇数标志 buf[0] = 00110000 扩展节点奇数标志 buf[0] = 00010000
            buf[0] |= hex[0] // first nibble is contained in the first byte 把hex[0]即第一个nibble字节存入buf[0],buf[0] = 0011xxxx
            hex = hex[1:]
        }
        fmt.Println("buf[0]:", buf[0])
        //  将每两个nibble的数据合并到1byte
        decodeNibbles(hex, buf[1:])
        fmt.Println("buf[0]:", buf[0])
        fmt.Println("buf[1]:", buf[1])
        fmt.Println("buf[2]:", buf[2])
        return buf
    }
    
    func compactToHex(compact []byte) []byte {
        base := keybytesToHex(compact)
        // delete terminator flag
        if base[0] < 2 {
            base = base[:len(base)-1]
        }
        // apply odd flag
        chop := 2 - base[0]&1
        return base[chop:]
    }
    
    // 将keybytes转成HEX编码格式
    // 1.将keybytes中的1byte信息,将高4bit和低4bit分别放到两个byte里
    // 2.在尾部加1byte值为16,标记当前属于Hex格式,
    func keybytesToHex(str []byte) []byte {
        l := len(str)*2 + 1
        var nibbles = make([]byte, l)
        for i, b := range str {
            nibbles[i*2] = b / 16   //右移四位
            nibbles[i*2+1] = b % 16 //取b的右侧四位
        }
        nibbles[l-1] = 16
        return nibbles
    }
    
    // hexToKeybytes turns hex nibbles into key bytes.
    // This can only be used for keys of even length.
    // 将hex格式转换成keybytes格式,智能转换偶数位的key
    func hexToKeybytes(hex []byte) []byte {
        // 若存在标记位则去除标记为
        if hasTerm(hex) {
            hex = hex[:len(hex)-1]
        }
        // 奇数位不能变换
        if len(hex)&1 != 0 {
            panic("can't convert hex key of odd length")
        }
        key := make([]byte, len(hex)/2)
        decodeNibbles(hex, key)
        return key
    }
    
    // 将每两个nibble的数据合并到1byte
    func decodeNibbles(nibbles []byte, bytes []byte) {
        for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 {
            bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1]
        }
    }
    
    // prefixLen returns the length of the common prefix of a and b.
    // 返回a,b的共同前缀序号
    func prefixLen(a, b []byte) int {
        var i, length = 0, len(a)
        if len(b) < length {
            length = len(b)
        }
        for ; i < length; i++ {
            if a[i] != b[i] {
                break
            }
        }
        return i
    }
    
    // hasTerm returns whether a hex key has the terminator flag.
    // hasTerm 返回一个十六进制的key是否拥有终结符标志
    func hasTerm(s []byte) bool {
        return len(s) > 0 && s[len(s)-1] == 16
    }
    
    

    相关文章

      网友评论

          本文标题:以太坊encoding.go 源码解析-keybytes,Hex

          本文链接:https://www.haomeiwen.com/subject/cpzdiftx.html