go-ethereum源码剖析:区块存储

区块和交易等数据最终都是存储在leveldb数据库中的,本文介绍区块和交易在leveldb中的存储格式。在core/database_util.go中封装了所有与区块存储和读取相关的代码,通过这些代码可以弄清楚区块、交易等数据结构在数据库中是如何存储的。

区块存储

leveldb是一个key-value数据库,所有数据都是以键-值对的形式存储。key一般与hash相关,value一般是要存储的数据结构的RLP编码。区块存储时将区块头和区块体分开存储。

区块头的存储格式为:

headerPrefix + num (uint64 big endian) + hash -> rlpEncode(header)

其中key由区块号(uint64大端格式)、区块hash、以及一个前缀构成,value是区块头的RLP编码。

区块体的存储格式为:

bodyPrefix + num (uint64 big endian) + hash -> rlpEncode(block body)

其中key由区块号(uint64大端格式)、区块hash、以及一个前缀构成,value是区块体的RLP编码。

key中的前缀可以用来区分数据的类型,在core/database_util.go中定义了各种前缀:

headerPrefix        = []byte("h")   // headerPrefix + num (uint64 big endian) + hash -> header
tdSuffix            = []byte("t")   // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td
numSuffix           = []byte("n")   // headerPrefix + num (uint64 big endian) + numSuffix -> hash
blockHashPrefix     = []byte("H")   // blockHashPrefix + hash -> num (uint64 big endian)
bodyPrefix          = []byte("b")   // bodyPrefix + num (uint64 big endian) + hash -> block body

其中headerPrefix定义了区块头的前缀为h,bodyPrefix定义了区块体的前缀为b。

下面是存储区块头的函数:

// WriteHeader serializes a block header into the database.
func WriteHeader(db ethdb.Database, header *types.Header) error {
    data, err := rlp.EncodeToBytes(header)
    if err != nil {
        return err
    }
    hash := header.Hash().Bytes()
    num := header.Number.Uint64()
    encNum := encodeBlockNumber(num)
    key := append(blockHashPrefix, hash...)
    if err := db.Put(key, encNum); err != nil {
        glog.Fatalf("failed to store hash to number mapping into database: %v", err)
    }
    key = append(append(headerPrefix, encNum...), hash...)
    if err := db.Put(key, data); err != nil {
        glog.Fatalf("failed to store header into database: %v", err)
    }
    glog.V(logger.Debug).Infof("stored header #%v [%x…]", header.Number, hash[:4])
    return nil
}

它是先对区块头进行RLP编码,encodeBlockNumber将区块号转换成大端格式,然后组装key。这里先向数据库中存储一条 区块hash->区块号 的映射,最后将区块头的RLP编码写到数据库中。

下面是存储区块体的函数:

// WriteBody serializes the body of a block into the database.
func WriteBody(db ethdb.Database, hash common.Hash, number uint64, body *types.Body) error {
    data, err := rlp.EncodeToBytes(body)
    if err != nil {
        return err
    }
    return WriteBodyRLP(db, hash, number, data)
}

// WriteBodyRLP writes a serialized body of a block into the database.
func WriteBodyRLP(db ethdb.Database, hash common.Hash, number uint64, rlp rlp.RawValue) error {
    key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
    if err := db.Put(key, rlp); err != nil {
        glog.Fatalf("failed to store block body into database: %v", err)
    }
    glog.V(logger.Debug).Infof("stored block body [%x…]", hash.Bytes()[:4])
    return nil
}

WriteBody先对区块体进行RLP编码,然后调用WriteBodyRLP将区块体的RLP编码写到数据库中。WriteBodyRLP根据上面的规则组装key,然后向数据库中写入一条记录。

还有一个WriteBlock函数分别调用WriteBody和WriteHeader将区块写到数据库中。此外还有GetHeader GetBody GetBlock函数用于从数据库中读取区块。

你可能感兴趣的:(以太坊源码分析)