以太坊区块和交易的存储结构分析

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定义了区块头key的前缀为hbodyPrefix定义了区块体key的前缀为b

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

// WriteHeader serializes a block header into thedatabase.

funcWriteHeader(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 numbermapping into database: %v", err)

    }

    key = append(append(headerPrefix, encNum...), hash...)

    if err := db.Put(key, data); err != nil {

       glog.Fatalf("failed to store header intodatabase: %v", err)

    }

   glog.V(logger.Debug).Infof("stored header#%v [%x…]", header.Number, hash[:4])

    returnnil

}

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

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

// WriteBody serializes the body of a block intothe database.

funcWriteBody(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 ablock into the database.

funcWriteBodyRLP(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 intodatabase: %v", err)

    }

   glog.V(logger.Debug).Infof("stored blockbody [%x…]", hash.Bytes()[:4])

    returnnil

}

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

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

交易存储

除了区块外,数据库中还存储了所有的交易,每条交易的存储格式如下:

txHash -> rlpEncode(tx)

txHash + txMetaSuffix -> rlpEncode(txMeta)

每条交易对应存储两条数据,一条是交易本身,一条是交易的元信息(meta)。交易以交易的hashkey、交易的RLP编码为value存储;元信息以txHash+txMetaSuffixkey、元信息的RLP编码为value存储。元信息中包含交易所在区块的区块hash、区块号、交易在区块中的索引。具体可以看WriteTransactions函数:

// WriteTransactions stores the transactionsassociated with a specific block

// into the given database. Beside writing thetransaction, the function also

// stores a metadata entry along with thetransaction, detailing the position

// of this within the blockchain.

funcWriteTransactions(db ethdb.Database,block *types.Block) error {

    batch:= db.NewBatch()

 

    // Iterate over each transaction and encode it with its metadata

    for i, tx := range block.Transactions() {

        // Encode and queue up the transaction for storage

       data, err := rlp.EncodeToBytes(tx)

        if err != nil {

           return err

        }

        if err := batch.Put(tx.Hash().Bytes(), data); err!= nil {

           return err

        }

        // Encode and queue up the transaction metadata for storage

       meta := struct {

           BlockHash  common.Hash

           BlockIndex uint64

           Index      uint64

        }{

           BlockHash:  block.Hash(),

           BlockIndex: block.NumberU64(),

           Index:      uint64(i),

        }

       data, err = rlp.EncodeToBytes(meta)

        if err != nil {

           return err

        }

        if err := batch.Put(append(tx.Hash().Bytes(), txMetaSuffix...), data); err != nil {

           return err

        }

    }

    // Write the scheduled data into the database

    if err := batch.Write(); err != nil {

        glog.Fatalf("failed to store transactions into database: %v", err)

    }

    returnnil

}

此外还有GetTransaction函数,根据交易hash从数据库中读取交易,它返回对应的交易、交易所在区块的区块hash、交易所在区块的区块号、交易在区块中的索引。


你可能感兴趣的:(以太坊)