blockchain: Enhance migration to v2 block index bucket.

This updates the block index migration to also store entries with
valid status bytes.
This commit is contained in:
Jim Posen 2017-10-09 11:38:49 -07:00 committed by Dave Collins
parent 52cddc19cd
commit 9aa9e79ebf

View file

@ -23,6 +23,16 @@ const (
blockHdrOffset = 12 blockHdrOffset = 12
) )
// blockChainContext represents a particular block's placement in the block
// chain. This is used by the block index migration to track block metadata that
// will be written to disk.
type blockChainContext struct {
parent *chainhash.Hash
children []*chainhash.Hash
height int32
mainChain bool
}
// migrateBlockIndex migrates all block entries from the v1 block index bucket // migrateBlockIndex migrates all block entries from the v1 block index bucket
// to the v2 bucket. The v1 bucket stores all block entries keyed by block hash, // to the v2 bucket. The v1 bucket stores all block entries keyed by block hash,
// whereas the v2 bucket stores the exact same values, but keyed instead by // whereas the v2 bucket stores the exact same values, but keyed instead by
@ -47,15 +57,29 @@ func migrateBlockIndex(db database.DB) error {
return err return err
} }
// Get tip of the main chain.
serializedData := dbTx.Metadata().Get(chainStateKeyName)
state, err := deserializeBestChainState(serializedData)
if err != nil {
return err
}
tip := &state.hash
// Scan the old block index bucket and construct a mapping of each block // Scan the old block index bucket and construct a mapping of each block
// to all child blocks. // to parent block and all child blocks.
childBlocksMap, err := readBlockTree(v1BlockIdxBucket) blocksMap, err := readBlockTree(v1BlockIdxBucket)
if err != nil { if err != nil {
return err return err
} }
// Use the block graph to calculate the height of each block. // Use the block graph to calculate the height of each block.
blockHeights := determineBlockHeights(childBlocksMap) err = determineBlockHeights(blocksMap)
if err != nil {
return err
}
// Find blocks on the main chain with the block graph and current tip.
determineMainChainBlocks(blocksMap, tip)
// Now that we have heights for all blocks, scan the old block index // Now that we have heights for all blocks, scan the old block index
// bucket and insert all rows into the new one. // bucket and insert all rows into the new one.
@ -65,16 +89,26 @@ func migrateBlockIndex(db database.DB) error {
var hash chainhash.Hash var hash chainhash.Hash
copy(hash[:], hashBytes[0:chainhash.HashSize]) copy(hash[:], hashBytes[0:chainhash.HashSize])
chainContext := blocksMap[hash]
height, exists := blockHeights[hash] if chainContext.height == -1 {
if !exists {
return fmt.Errorf("Unable to calculate chain height for "+ return fmt.Errorf("Unable to calculate chain height for "+
"stored block %s", hash) "stored block %s", hash)
} }
// Mark blocks as valid if they are part of the main chain.
status := statusDataStored
if chainContext.mainChain {
status |= statusValid
}
// Write header to v2 bucket // Write header to v2 bucket
key := blockIndexKey(&hash, height) value := make([]byte, blockHdrSize+1)
err := v2BlockIdxBucket.Put(key, headerBytes) copy(value[0:blockHdrSize], headerBytes)
value[blockHdrSize] = byte(status)
key := blockIndexKey(&hash, uint32(chainContext.height))
err := v2BlockIdxBucket.Put(key, value)
if err != nil { if err != nil {
return err return err
} }
@ -93,10 +127,11 @@ func migrateBlockIndex(db database.DB) error {
} }
// readBlockTree reads the old block index bucket and constructs a mapping of // readBlockTree reads the old block index bucket and constructs a mapping of
// each block to all child blocks. This mapping represents the full tree of // each block to its parent block and all child blocks. This mapping represents
// blocks. // the full tree of blocks. This function does not populate the height or
func readBlockTree(v1BlockIdxBucket database.Bucket) (map[chainhash.Hash][]*chainhash.Hash, error) { // mainChain fields of the returned blockChainContext values.
childBlocksMap := make(map[chainhash.Hash][]*chainhash.Hash) func readBlockTree(v1BlockIdxBucket database.Bucket) (map[chainhash.Hash]*blockChainContext, error) {
blocksMap := make(map[chainhash.Hash]*blockChainContext)
err := v1BlockIdxBucket.ForEach(func(_, blockRow []byte) error { err := v1BlockIdxBucket.ForEach(func(_, blockRow []byte) error {
var header wire.BlockHeader var header wire.BlockHeader
endOffset := blockHdrOffset + blockHdrSize endOffset := blockHdrOffset + blockHdrSize
@ -107,41 +142,65 @@ func readBlockTree(v1BlockIdxBucket database.Bucket) (map[chainhash.Hash][]*chai
} }
blockHash := header.BlockHash() blockHash := header.BlockHash()
childBlocksMap[header.PrevBlock] = prevHash := header.PrevBlock
append(childBlocksMap[header.PrevBlock], &blockHash)
if blocksMap[blockHash] == nil {
blocksMap[blockHash] = &blockChainContext{height: -1}
}
if blocksMap[prevHash] == nil {
blocksMap[prevHash] = &blockChainContext{height: -1}
}
blocksMap[blockHash].parent = &prevHash
blocksMap[prevHash].children =
append(blocksMap[prevHash].children, &blockHash)
return nil return nil
}) })
return childBlocksMap, err return blocksMap, err
} }
// determineBlockHeights takes a map of block hashes to a slice of child hashes // determineBlockHeights takes a map of block hashes to a slice of child hashes
// and uses it to compute the height for each block. The function assigns a // and uses it to compute the height for each block. The function assigns a
// height of 0 to the genesis hash and explores the tree of blocks // height of 0 to the genesis hash and explores the tree of blocks
// breadth-first, assigning a height to every block with a path back to the // breadth-first, assigning a height to every block with a path back to the
// genesis block. // genesis block. This function modifies the height field on the blocksMap
func determineBlockHeights(childBlocksMap map[chainhash.Hash][]*chainhash.Hash) map[chainhash.Hash]uint32 { // entries.
blockHeights := make(map[chainhash.Hash]uint32) func determineBlockHeights(blocksMap map[chainhash.Hash]*blockChainContext) error {
queue := list.New() queue := list.New()
// The genesis block is included in childBlocksMap as a child of the zero // The genesis block is included in blocksMap as a child of the zero hash
// hash because that is the value of the PrevBlock field in the genesis // because that is the value of the PrevBlock field in the genesis header.
// header. preGenesisContext, exists := blocksMap[zeroHash]
for _, genesisHash := range childBlocksMap[zeroHash] { if !exists || len(preGenesisContext.children) == 0 {
blockHeights[*genesisHash] = 0 return fmt.Errorf("Unable to find genesis block")
}
for _, genesisHash := range preGenesisContext.children {
blocksMap[*genesisHash].height = 0
queue.PushBack(genesisHash) queue.PushBack(genesisHash)
} }
for e := queue.Front(); e != nil; e = queue.Front() { for e := queue.Front(); e != nil; e = queue.Front() {
queue.Remove(e) queue.Remove(e)
hash := e.Value.(*chainhash.Hash) hash := e.Value.(*chainhash.Hash)
height := blockHeights[*hash] height := blocksMap[*hash].height
// For each block with this one as a parent, assign it a height and // For each block with this one as a parent, assign it a height and
// push to queue for future processing. // push to queue for future processing.
for _, childHash := range childBlocksMap[*hash] { for _, childHash := range blocksMap[*hash].children {
blockHeights[*childHash] = height + 1 blocksMap[*childHash].height = height + 1
queue.PushBack(childHash) queue.PushBack(childHash)
} }
} }
return blockHeights
return nil
}
// determineMainChainBlocks traverses the block graph down from the tip to
// determine which block hashes that are part of the main chain. This function
// modifies the mainChain field on the blocksMap entries.
func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, tip *chainhash.Hash) {
for nextHash := tip; *nextHash != zeroHash; nextHash = blocksMap[*nextHash].parent {
blocksMap[*nextHash].mainChain = true
}
} }