From 175fd940bb8ca119e1c8e5cc88ebca680ed80366 Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Tue, 5 Sep 2017 16:43:50 -0700 Subject: [PATCH] blockchain: Store block headers in bucket managed by chainio. The bucket contains block headers keyed by the block height encoded as big-endian concatenated with the block hash. This allows block headers to be fetched from the DB in height order with a cursor. --- blockchain/accept.go | 2 +- blockchain/chain.go | 14 -------- blockchain/chainio.go | 79 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/blockchain/accept.go b/blockchain/accept.go index 963a4fd06..1d68b5608 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -54,7 +54,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) // such as making blocks that never become part of the main chain or // blocks that fail to connect available for further analysis. err = b.db.Update(func(dbTx database.Tx) error { - return dbMaybeStoreBlock(dbTx, block) + return dbStoreBlock(dbTx, block) }) if err != nil { return false, err diff --git a/blockchain/chain.go b/blockchain/chain.go index bd24b1e1a..df90d5fd8 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -544,20 +544,6 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List return detachNodes, attachNodes } -// dbMaybeStoreBlock stores the provided block in the database if it's not -// already there. -func dbMaybeStoreBlock(dbTx database.Tx, block *btcutil.Block) error { - hasBlock, err := dbTx.HasBlock(block.Hash()) - if err != nil { - return err - } - if hasBlock { - return nil - } - - return dbTx.StoreBlock(block) -} - // connectBlock handles connecting the passed node/block to the end of the main // (best) chain. // diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 6c629c605..25d35f04e 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -18,7 +18,18 @@ import ( "github.com/btcsuite/btcutil" ) +const ( + // blockHdrSize is the size of a block header. This is simply the + // constant from wire and is only provided here for convenience since + // wire.MaxBlockHeaderPayload is quite long. + blockHdrSize = wire.MaxBlockHeaderPayload +) + var ( + // blockIndexBucketName is the name of the db bucket used to house to the + // block headers and contextual information. + blockIndexBucketName = []byte("blockheaderidx") + // hashIndexBucketName is the name of the db bucket used to house to the // block hash -> block height index. hashIndexBucketName = []byte("hashidx") @@ -1058,6 +1069,7 @@ func dbPutBestState(dbTx database.Tx, snapshot *BestState, workSum *big.Int) err func (b *BlockChain) createChainState() error { // Create a new node from the genesis block and set it as the best node. genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock) + genesisBlock.SetHeight(0) header := &genesisBlock.MsgBlock().Header node := newBlockNode(header, 0) node.status = statusDataStored | statusValid @@ -1077,10 +1089,17 @@ func (b *BlockChain) createChainState() error { // Create the initial the database chain state including creating the // necessary index buckets and inserting the genesis block. err := b.db.Update(func(dbTx database.Tx) error { + meta := dbTx.Metadata() + + // Create the bucket that houses the block index data. + _, err := meta.CreateBucket(blockIndexBucketName) + if err != nil { + return err + } + // Create the bucket that houses the chain block hash to height // index. - meta := dbTx.Metadata() - _, err := meta.CreateBucket(hashIndexBucketName) + _, err = meta.CreateBucket(hashIndexBucketName) if err != nil { return err } @@ -1120,7 +1139,7 @@ func (b *BlockChain) createChainState() error { } // Store the genesis block into the database. - return dbTx.StoreBlock(genesisBlock) + return dbStoreBlock(dbTx, genesisBlock) }) return err } @@ -1273,6 +1292,60 @@ func dbFetchBlockByNode(dbTx database.Tx, node *blockNode) (*btcutil.Block, erro return block, nil } +// dbStoreBlock stores the provided block in the database. The block header is +// written to the block index bucket and full block data is written to ffldb. +func dbStoreBlockHeader(dbTx database.Tx, blockHeader *wire.BlockHeader, height uint32) error { + // Serialize block data to be stored. This is just the serialized header. + w := bytes.NewBuffer(make([]byte, 0, blockHdrSize)) + err := blockHeader.Serialize(w) + if err != nil { + return err + } + value := w.Bytes() + + // Write block header data to block index bucket. + blockHash := blockHeader.BlockHash() + blockIndexBucket := dbTx.Metadata().Bucket(blockIndexBucketName) + key := blockIndexKey(&blockHash, height) + return blockIndexBucket.Put(key, value) +} + +// dbStoreBlock stores the provided block in the database. The block header is +// written to the block index bucket and full block data is written to ffldb. +func dbStoreBlock(dbTx database.Tx, block *btcutil.Block) error { + if block.Height() == btcutil.BlockHeightUnknown { + return fmt.Errorf("cannot store block %s with unknown height", + block.Hash()) + } + + // First store block header in the block index bucket. + err := dbStoreBlockHeader(dbTx, &block.MsgBlock().Header, + uint32(block.Height())) + if err != nil { + return err + } + + // Then store block data in ffldb if we haven't already. + hasBlock, err := dbTx.HasBlock(block.Hash()) + if err != nil { + return err + } + if hasBlock { + return nil + } + return dbTx.StoreBlock(block) +} + +// blockIndexKey generates the binary key for an entry in the block index +// bucket. The key is composed of the block height encoded as a big-endian +// 32-bit unsigned int followed by the 32 byte block hash. +func blockIndexKey(blockHash *chainhash.Hash, blockHeight uint32) []byte { + indexKey := make([]byte, chainhash.HashSize+4) + binary.BigEndian.PutUint32(indexKey[0:4], blockHeight) + copy(indexKey[4:chainhash.HashSize+4], blockHash[:]) + return indexKey +} + // BlockByHeight returns the block at the given height in the main chain. // // This function is safe for concurrent access.