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.
This commit is contained in:
Jim Posen 2017-09-05 16:43:50 -07:00 committed by Dave Collins
parent 358aed20b7
commit 175fd940bb
3 changed files with 77 additions and 18 deletions

View file

@ -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

View file

@ -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.
//

View file

@ -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.