1. 程式人生 > >以太坊區塊和交易存儲

以太坊區塊和交易存儲

完成 limit 體系 轉換成 tran rate sco not rds

區塊存儲

區塊(Block)是以太坊的核心數據結構之一,Block包含Header和Body兩部分。區塊的存儲是由leveldb完成的,leveldb的數據是以鍵值對存儲的。

// BlockChain 表示了一個規範的鏈,這個鏈通過一個包含了創世區塊的數據庫指定. BlockChain管理了鏈的插入,還原,重建等操作.
//插入一個區塊需要通過一系列指定的規則指定的兩階段的驗證器.
// 使用Processor來對區塊的交易進行處理. 狀態的驗證是第二階段的驗證器. 錯誤將導致插入終止.
//需要註意的是GetBlock可能返回任意不在當前規範區塊鏈中的區塊,
//但是GetBlockByNumber總是返回當前規範區塊鏈中的區塊.
type BlockChain struct {
    chainConfig *params.ChainConfig // Chain & network configuration
    cacheConfig *CacheConfig        // Cache configuration for pruning

    db     ethdb.Database // Low level persistent database to store final content in
    triegc *prque.Prque   // Priority queue mapping block numbers to tries to gc
    gcproc time.Duration  // Accumulates canonical block processing for trie dumping

    hc            *HeaderChain //只包含了區塊頭的區塊鏈
    rmLogsFeed    event.Feed   // 底層數據庫
    chainFeed     event.Feed   // 下面是很多消息通知的組件
    chainSideFeed event.Feed
    chainHeadFeed event.Feed
    logsFeed      event.Feed
    scope         event.SubscriptionScope
    genesisBlock  *types.Block  // 創世區塊

    mu      sync.RWMutex // global mutex for locking chain operations
    chainmu sync.RWMutex // blockchain insertion lock
    procmu  sync.RWMutex // block processor lock

    checkpoint       int          // checkpoint counts towards the new checkpoint
    currentBlock     *types.Block // Current head of the block chain 當前的區塊頭
    currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!) 當前的快速同步的區塊頭

    stateCache   state.Database // State database to reuse between imports (contains state cache)
    bodyCache    *lru.Cache     // Cache for the most recent block bodies
    bodyRLPCache *lru.Cache     // Cache for the most recent block bodies in RLP encoded format
    blockCache   *lru.Cache     // Cache for the most recent entire blocks
    futureBlocks *lru.Cache     // future blocks are blocks added for later processing 暫時還不能插入的區塊存放位置

    quit    chan struct{} // blockchain quit channel
    running int32         // running must be called atomically
    // procInterrupt must be atomically called
    procInterrupt int32          // interrupt signaler for block processing
    wg            sync.WaitGroup // chain processing wait group for shutting down

    engine    consensus.Engine  // 一致性引擎
    processor Processor // block processor interface    // 區塊處理器接口
    validator Validator // block and state validator interface // 區塊和狀態驗證器接口
    vmConfig  vm.Config //虛擬機的配置

    badBlocks *lru.Cache // Bad block cache 錯誤區塊的緩存
}
// Block represents an entire block in the Ethereum blockchain.
type Block struct {
   header       *Header
   uncles       []*Header
   transactions Transactions

   // caches
   hash atomic.Value
   size atomic.Value

   // Td is used by package core to store the total difficulty
   // of the chain up to and including the block.
   td *big.Int

   // These fields are used by package eth to track
   // inter-peer block relay.
   ReceivedAt   time.Time
   ReceivedFrom interface{}
}
// Header represents a block header in the Ethereum blockchain.
type Header struct {
   ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
   UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
   Coinbase    common.Address `json:"miner"            gencodec:"required"`
   Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
   TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
   ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
   Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
   Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
   Number      *big.Int       `json:"number"           gencodec:"required"`
   GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
   GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
   Time        *big.Int       `json:"timestamp"        gencodec:"required"`
   Extra       []byte         `json:"extraData"        gencodec:"required"`
   MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
   Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}

bitcoin

技術分享圖片

ethereum

技術分享圖片

以太坊的數據庫體系-Merkle-Patricia Trie(MPT), 它是由一系列節點組成的二叉樹,在樹底包含了源數據的大量葉子節點, 父節點是兩個子節點的Hash值,一直到根節點。

Blockchain和HeaderChain, Blockchain管理所有的Block, 讓其組成一個單向鏈表。Headerchain管理所有的Header,也形成一個單向鏈表, Headerchain是Blockchain裏面的一部分

Transaction是Body的重要數據結構,一個交易就是被外部擁有賬戶生成的加密簽名的一段指令,序列化,然後提交給區塊鏈。

在這裏保存區塊信息時,key一般是與hash相關的,value所保存的數據結構是經過RLP編碼的。

在代碼中,core/database_util.go中封裝了區塊存儲和讀取相關的代碼。

在存儲區塊信息時,會將區塊頭和區塊體分開進行存儲。因此在區塊的結構體中,能夠看到Header和Body兩個結構體。
區塊頭(Header)的存儲格式為:

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

key是由區塊頭的前綴,區塊號和區塊hash構成。value是區塊頭的RLP編碼。

區塊體(Body)的存儲格式為:

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

key是由區塊體前綴,區塊號和區塊hash構成。value是區塊體的RLP編碼。

在database_util.go中,key的前綴可以區分leveldb中存儲的是什麽類型的數據。

    var (
        headHeaderKey = []byte("LastHeader")
        headBlockKey  = []byte("LastBlock")
        headFastKey   = []byte("LastFast")
    
        // Data item prefixes (use single byte to avoid mixing data types, avoid `i`).
        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
        blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
        lookupPrefix        = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata
        bloomBitsPrefix     = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
    
        preimagePrefix = "secure-key-"              // preimagePrefix + hash -> preimage
        configPrefix   = []byte("ethereum-config-") // config prefix for the db
    
        // Chain index prefixes (use `i` + single byte to avoid mixing data types).
        BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
    
        // used by old db, now only used for conversion
        oldReceiptsPrefix = []byte("receipts-")
        oldTxMetaSuffix   = []byte{0x01}
    
        ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error
    
        preimageCounter    = metrics.NewCounter("db/preimage/total")
        preimageHitCounter = metrics.NewCounter("db/preimage/hits")
    )

database_util.go最開始就定義了所有的前綴。這裏的註釋詳細說明了每一個前綴存儲了什麽數據類型。

database_util.go中的其他方法則是對leveldb的操作。其中get方法是讀取數據庫中的內容,write則是向leveldb中寫入數據。

要講一個區塊的信息寫入數據庫,則需要調用其中的WriteBlock方法。

// WriteBlock serializes a block into the database, header and body separately.
    func WriteBlock(db ethdb.Putter, block *types.Block) error {
        // Store the body first to retain database consistency
        if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil {
            return err
        }
        // Store the header too, signaling full block ownership
        if err := WriteHeader(db, block.Header()); err != nil {
            return err
        }
        return nil
    }

這裏我們看到,將一個區塊信息寫入數據庫其實是分別將區塊頭和區塊體寫入數據庫。

首先來看區塊頭的存儲。區塊頭的存儲是由WriteHeader方法完成的。

    // WriteHeader serializes a block header into the database.
    func WriteHeader(db ethdb.Putter, 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 {
            log.Crit("Failed to store hash to number mapping", "err", err)
        }
        key = append(append(headerPrefix, encNum...), hash...)
        if err := db.Put(key, data); err != nil {
            log.Crit("Failed to store header", "err", err)
        }
        return nil
    }

這裏首先對區塊頭進行了RLP編碼,然後將區塊號轉換成為byte格式,開始組裝key。

這裏首先向數據庫中存儲了一條區塊hash->區塊號的鍵值對,然後才將區塊頭的信息寫入數據庫。

接下來是區塊體的存儲。區塊體存儲是由WriteBody方法實現。

// WriteBody serializes the body of a block into the database.
    func WriteBody(db ethdb.Putter, 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.Putter, 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 {
            log.Crit("Failed to store block body", "err", err)
        }
        return nil
    }

WriteBody首先將區塊體的信息進行RLP編碼,然後調用WriteBodyRLP方法將區塊體的信息寫入數據庫。key的組裝方法如之前所述。

交易存儲

交易主要在數據庫中僅存儲交易的Meta信息。

    txHash + txMetaSuffix -> rlpEncode(txMeta)

交易的Meta信息結構體如下:

// TxLookupEntry is a positional metadata to help looking up the data content of
// a transaction or receipt given only its hash.
    type TxLookupEntry struct {
        BlockHash  common.Hash
        BlockIndex uint64
        Index      uint64
    }

這裏,meta信息會存儲塊的hash,塊號和塊上第幾筆交易這些信息。

交易Meta存儲是以交易hash加交易的Meta前綴為key,Meta的RLP編碼為value。

交易寫入數據庫是通過WriteTxLookupEntries方法實現的。

// WriteTxLookupEntries stores a positional metadata for every transaction from
// a block, enabling hash based transaction and receipt lookups.
    func WriteTxLookupEntries(db ethdb.Putter, block *types.Block) error {
        // Iterate over each transaction and encode its metadata
        for i, tx := range block.Transactions() {
            entry := TxLookupEntry{
                BlockHash:  block.Hash(),
                BlockIndex: block.NumberU64(),
                Index:      uint64(i),
            }
            data, err := rlp.EncodeToBytes(entry)
            if err != nil {
                return err
            }
            if err := db.Put(append(lookupPrefix, tx.Hash().Bytes()...), data); err != nil {
                return err
            }
        }
        return nil
    }

這裏,在將交易meta入庫時,會遍歷塊上的所有交易,並構造交易的meta信息,進行RLP編碼。然後以交易hash為key,meta為value進行存儲。

這樣就將一筆交易寫入數據庫中。

從數據庫中讀取交易信息時通過GetTransaction方法獲得的。

// GetTransaction retrieves a specific transaction from the database, along with
// its added positional metadata.
    func GetTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
        // Retrieve the lookup metadata and resolve the transaction from the body
        blockHash, blockNumber, txIndex := GetTxLookupEntry(db, hash)
    
        if blockHash != (common.Hash{}) {
            body := GetBody(db, blockHash, blockNumber)
            if body == nil || len(body.Transactions) <= int(txIndex) {
                log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex)
                return nil, common.Hash{}, 0, 0
            }
            return body.Transactions[txIndex], blockHash, blockNumber, txIndex
        }
        // Old transaction representation, load the transaction and it‘s metadata separately
        data, _ := db.Get(hash.Bytes())
        if len(data) == 0 {
            return nil, common.Hash{}, 0, 0
        }
        var tx types.Transaction
        if err := rlp.DecodeBytes(data, &tx); err != nil {
            return nil, common.Hash{}, 0, 0
        }
        // Retrieve the blockchain positional metadata
        data, _ = db.Get(append(hash.Bytes(), oldTxMetaSuffix...))
        if len(data) == 0 {
            return nil, common.Hash{}, 0, 0
        }
        var entry TxLookupEntry
        if err := rlp.DecodeBytes(data, &entry); err != nil {
            return nil, common.Hash{}, 0, 0
        }
        return &tx, entry.BlockHash, entry.BlockIndex, entry.Index
    }

這個方法會首先通過交易hash從數據庫中獲取交易的meta信息,包括交易所在塊的hash,塊號和第幾筆交易。

接下來使用塊號和塊hash獲取從數據庫中讀取塊的信息。

然後根據第幾筆交易從塊上獲取交易的具體信息。

這裏以太坊將交易的存儲換成了新的存儲方式,即交易的具體信息存儲在塊上,交易hash只對應交易的meta信息,並不包含交易的具體信息。

而以前的交易存儲則是需要存儲交易的具體信息和meta信息。

因此GetTransaction方法會支持原有的數據存儲方式。

以太坊區塊和交易存儲