From 6315cea70c6ceda543addef3d82ed8d67c8cba9c Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Tue, 5 Sep 2017 16:58:49 -0700 Subject: [PATCH] blockchain: Load all block headers into block index on init. Currently only the blocks in the active chain are loaded into the block index on initialization. This instead iterates over the entire block index bucket in LevelDB and loads all nodes. --- blockchain/chainio.go | 77 +++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 25d35f04e..66b552de3 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1171,41 +1171,68 @@ func (b *BlockChain) initChainState() error { // for them versus a whole bunch of little ones to reduce // pressure on the GC. log.Infof("Loading block index. This might take a while...") - bestHeight := int32(state.height) - blockNodes := make([]blockNode, bestHeight+1) - var tip *blockNode - for height := int32(0); height <= bestHeight; height++ { - header, err := dbFetchHeaderByHeight(dbTx, height) + + blockIndexBucket := dbTx.Metadata().Bucket(blockIndexBucketName) + + // Determine how many blocks will be loaded into the index so we can + // allocate the right amount. + var blockCount int32 + cursor := blockIndexBucket.Cursor() + for ok := cursor.First(); ok; ok = cursor.Next() { + blockCount++ + } + blockNodes := make([]blockNode, blockCount) + + var i int32 + var lastNode *blockNode + cursor = blockIndexBucket.Cursor() + for ok := cursor.First(); ok; ok = cursor.Next() { + var header wire.BlockHeader + headerBytes := cursor.Value() + err := header.Deserialize(bytes.NewReader(headerBytes)) if err != nil { return err } // Initialize the block node for the block, connect it, // and add it to the block index. - node := &blockNodes[height] - initBlockNode(node, header, height) + node := &blockNodes[i] + initBlockNode(node, &header, 0) node.status = statusDataStored | statusValid - if tip != nil { - node.parent = tip - node.workSum = node.workSum.Add(tip.workSum, - node.workSum) - } - b.index.AddNode(node) + if lastNode == nil { + if node.hash != *b.chainParams.GenesisHash { + return AssertError(fmt.Sprintf("initChainState: Expected "+ + "first entry in block index to be genesis block, "+ + "found %s", header.BlockHash())) + } + } else { + // Since we iterate block headers in order of height, if the + // blocks are mostly linear there is a very good chance the + // previous header processed is the parent. + parent := lastNode + if header.PrevBlock != parent.hash { + parent = b.index.LookupNode(&header.PrevBlock) + } + if parent == nil { + return AssertError(fmt.Sprintf("initChainState: Could "+ + "not find parent for block %s", header.BlockHash())) + } - // This node is now the end of the best chain. - tip = node + node.parent = parent + node.height = parent.height + 1 + node.workSum = node.workSum.Add(parent.workSum, node.workSum) + } + + b.index.AddNode(node) + lastNode = node + i++ } - // Ensure the resulting best chain matches the stored best state - // hash and set the best chain view accordingly. - if tip == nil || tip.hash != state.hash { - var tipHash chainhash.Hash - if tip != nil { - tipHash = tip.hash - } - return AssertError(fmt.Sprintf("initChainState: block "+ - "index chain tip %s does not match stored "+ - "best state %s", tipHash, state.hash)) + // Set the best chain view to the stored best state. + tip := b.index.LookupNode(&state.hash) + if tip == nil { + return AssertError(fmt.Sprintf("initChainState: cannot find "+ + "chain tip %s in block index", state.hash)) } b.bestChain.SetTip(tip)