blockchain: Refactor db main chain idx to blk idx.

This refactors the code that relies on the database main chain index to
use the in-memory block index now that the full index is in memory.

This is a major optimization for several functions because they no
longer have to first consult the database (which is incredibly slow in
many cases due to the way leveldb splits all of the information across
files) to perform lookups and determine if blocks are in the main chain.

It should also be noted that even though the main chain index is no
longer used as of this commit, the code which writes the main chain
index entries to the database in connectBlock, disconnectBlock, and
createChainState have not been removed because, once that is done, it
will no longer be possible to downgrade and thus those changes must be
performed along with a database migration and associated version bump.

An overview of the changes are as follows:

- Update all code which previously used the db to use the main chain by
  height map instead
- Update several internal functions which previously accepted the
  hash to instead accept the block node as the parameter
  - Rename fetchMainChainBlockByHash to fetchMainChainBlockByNode
  - Rename fetchBlockByHash to fetchBlockByNode
  - Rename dbFetchBlockByHash to dbFetchBlockByNode
  - Update all callers to use the new names and semantics
- Optimize HeaderByHeight to use block index/height map instead of db
  - Move to chain.go since it no longer involves database I/O
- Optimize BlockByHash to use block index instead of db
  - Move to chain.go since it no longer involves database I/O
- Optimize BlockByHeight to use block index/height map instead of db
  - Move to chain.go since it no longer involves database I/O
- Optimize MainChainHasBlock to use block index instead of db
  - Move to chain.go since it no longer involves database I/O
  - Removed error return since it can no longer fail
- Optimize BlockHeightByHash to use block index instead of db
  - Move to chain.go since it no longer involves database I/O
- Optimize BlockHashByHeight to use block index instead of db
  - Move to chain.go since it no longer involves database I/O
- Optimize HeightRange to use block index instead of db
  - Move to chain.go since it no longer involves database I/O
- Remove several unused functions related to the main chain index
  - dbFetchHeaderByHash
  - DBFetchHeaderByHeight
  - dbFetchBlockByHeight
  - DBFetchBlockByHeight
  - dbMainChainHasBlock
  - DBMainChainHasBlock
- Rework IsCheckpointCandidate to use block index
- Modify the upgrade code to expose the old dbFetchBlockByHeight
  function only in the local scope since upgrades require the ability to
  read the old format
- Update indexers to fetch the blocks themselves while only querying
  chain for chain-related details
This commit is contained in:
Dave Collins 2018-07-02 15:52:12 -05:00
parent 9b08b582d7
commit c6b5f6df0d
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
12 changed files with 318 additions and 374 deletions

View File

@ -189,7 +189,7 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags)
// Grab the parent block since it is required throughout the block
// connection process.
parent, err := b.fetchBlockByHash(&newNode.parent.hash)
parent, err := b.fetchBlockByNode(newNode.parent)
if err != nil {
return 0, err
}

View File

@ -472,70 +472,66 @@ func (b *BlockChain) TipGeneration() ([]chainhash.Hash, error) {
return nodeHashes, nil
}
// fetchMainChainBlockByHash returns the block from the main chain with the
// given hash. It first attempts to use cache and then falls back to loading it
// from the database.
// fetchMainChainBlockByNode returns the block from the main chain associated
// with the given node. It first attempts to use cache and then falls back to
// loading it from the database.
//
// An error is returned if the block is either not found or not in the main
// chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) fetchMainChainBlockByHash(hash *chainhash.Hash) (*dcrutil.Block, error) {
// This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) fetchMainChainBlockByNode(node *blockNode) (*dcrutil.Block, error) {
b.mainchainBlockCacheLock.RLock()
block, ok := b.mainchainBlockCache[*hash]
block, ok := b.mainchainBlockCache[node.hash]
b.mainchainBlockCacheLock.RUnlock()
if ok {
return block, nil
}
// Ensure the block in the main chain.
if !node.inMainChain {
str := fmt.Sprintf("block %s is not in the main chain", node.hash)
return nil, errNotInMainChain(str)
}
// Load the block from the database.
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByHash(dbTx, hash)
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
return block, err
}
// fetchBlockByHash returns the block with the given hash from all known sources
// such as the internal caches and the database. This function returns blocks
// regardless or whether or not they are part of the main chain.
// fetchBlockByNode returns the block associated with the given node all known
// sources such as the internal caches and the database. This function returns
// blocks regardless or whether or not they are part of the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) fetchBlockByHash(hash *chainhash.Hash) (*dcrutil.Block, error) {
// Check orphan cache.
b.orphanLock.RLock()
orphan, existsOrphans := b.orphans[*hash]
b.orphanLock.RUnlock()
if existsOrphans {
return orphan.block, nil
}
func (b *BlockChain) fetchBlockByNode(node *blockNode) (*dcrutil.Block, error) {
// Check main chain cache.
b.mainchainBlockCacheLock.RLock()
block, ok := b.mainchainBlockCache[*hash]
block, ok := b.mainchainBlockCache[node.hash]
b.mainchainBlockCacheLock.RUnlock()
if ok {
return block, nil
}
// Attempt to load the block from the database.
err := b.db.View(func(dbTx database.Tx) error {
// NOTE: This does not use the dbFetchBlockByHash function since that
// function only works with main chain blocks.
blockBytes, err := dbTx.FetchBlock(hash)
if err != nil {
return err
}
block, err = dcrutil.NewBlockFromBytes(blockBytes)
return err
})
if err == nil && block != nil {
return block, nil
// Check orphan cache.
b.orphanLock.RLock()
orphan, existsOrphans := b.orphans[node.hash]
b.orphanLock.RUnlock()
if existsOrphans {
return orphan.block, nil
}
return nil, fmt.Errorf("unable to find block %v in cache or db", hash)
// Load the block from the database.
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
return block, err
}
// pruneStakeNodes removes references to old stake nodes which should no
@ -1112,7 +1108,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
block := nextBlockToDetach
if block == nil {
var err error
block, err = b.fetchMainChainBlockByHash(&n.hash)
block, err = b.fetchMainChainBlockByNode(n)
if err != nil {
return err
}
@ -1126,7 +1122,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// Grab the parent of the current block and also save a reference to it
// as the next block to detach so it doesn't need to be loaded again on
// the next iteration.
parent, err := b.fetchMainChainBlockByHash(&n.parent.hash)
parent, err := b.fetchMainChainBlockByNode(n.parent)
if err != nil {
return err
}
@ -1171,7 +1167,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
forkNode = newBest
var err error
forkBlock, err = b.fetchMainChainBlockByHash(&forkNode.hash)
forkBlock, err = b.fetchMainChainBlockByNode(forkNode)
if err != nil {
return err
}
@ -1195,7 +1191,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// attached or the previous one that was attached for subsequent blocks
// to optimize.
n := e.Value.(*blockNode)
block, err := b.fetchBlockByHash(&n.hash)
block, err := b.fetchBlockByNode(n)
if err != nil {
return err
}
@ -1360,7 +1356,7 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest
"common parent for forced reorg")
}
newBestBlock, err := b.fetchBlockByHash(&newBest)
newBestBlock, err := b.fetchBlockByNode(newBestNode)
if err != nil {
return err
}
@ -1370,12 +1366,11 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest
view.SetBestHash(&b.bestNode.parent.hash)
view.SetStakeViewpoint(ViewpointPrevValidInitial)
formerBestBlock, err := b.fetchBlockByHash(&formerBest)
formerBestBlock, err := b.fetchBlockByNode(formerBestNode)
if err != nil {
return err
}
commonParentBlock, err := b.fetchMainChainBlockByHash(
&formerBestNode.parent.hash)
commonParentBlock, err := b.fetchMainChainBlockByNode(formerBestNode.parent)
if err != nil {
return err
}
@ -1679,6 +1674,155 @@ func (b *BlockChain) HeaderByHash(hash *chainhash.Hash) (wire.BlockHeader, error
return node.Header(), nil
}
// HeaderByHeight returns the block header at the given height in the main
// chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeaderByHeight(height int64) (wire.BlockHeader, error) {
b.heightLock.RLock()
node := b.mainNodesByHeight[height]
b.heightLock.RUnlock()
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return wire.BlockHeader{}, errNotInMainChain(str)
}
return node.Header(), nil
}
// BlockByHash searches the internal chain block stores and the database in an
// attempt to find the requested block and returns it. This function returns
// blocks regardless of whether or not they are part of the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*dcrutil.Block, error) {
node := b.index.LookupNode(hash)
if node == nil {
return nil, fmt.Errorf("block %s is not known", hash)
}
// Return the block from either cache or the database.
return b.fetchBlockByNode(node)
}
// BlockByHeight returns the block at the given height in the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHeight(height int64) (*dcrutil.Block, error) {
b.heightLock.RLock()
node := b.mainNodesByHeight[height]
b.heightLock.RUnlock()
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return nil, errNotInMainChain(str)
}
// Return the block from either cache or the database. Note that this is
// not using fetchMainChainBlockByNode since the main chain check has
// already been done.
return b.fetchBlockByNode(node)
}
// MainChainHasBlock returns whether or not the block with the given hash is in
// the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) bool {
node := b.index.LookupNode(hash)
b.chainLock.RLock()
hasBlock := node != nil && node.inMainChain
b.chainLock.RUnlock()
return hasBlock
}
// BlockHeightByHash returns the height of the block with the given hash in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int64, error) {
node := b.index.LookupNode(hash)
b.chainLock.RLock()
if node == nil || !node.inMainChain {
b.chainLock.RUnlock()
str := fmt.Sprintf("block %s is not in the main chain", hash)
return 0, errNotInMainChain(str)
}
b.chainLock.RUnlock()
return node.height, nil
}
// BlockHashByHeight returns the hash of the block at the given height in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHashByHeight(height int64) (*chainhash.Hash, error) {
b.heightLock.RLock()
node := b.mainNodesByHeight[height]
b.heightLock.RUnlock()
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return nil, errNotInMainChain(str)
}
return &node.hash, nil
}
// HeightRange returns a range of block hashes for the given start and end
// heights. It is inclusive of the start height and exclusive of the end
// height. In other words, it is the half open range [startHeight, endHeight).
//
// The end height will be limited to the current main chain height.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeightRange(startHeight, endHeight int64) ([]chainhash.Hash, error) {
// Ensure requested heights are sane.
if startHeight < 0 {
return nil, fmt.Errorf("start height of fetch range must not "+
"be less than zero - got %d", startHeight)
}
if endHeight < startHeight {
return nil, fmt.Errorf("end height of fetch range must not "+
"be less than the start height - got start %d, end %d",
startHeight, endHeight)
}
// There is nothing to do when the start and end heights are the same,
// so return now to avoid the chain lock.
if startHeight == endHeight {
return nil, nil
}
// When the requested start height is after the most recent best chain
// height, there is nothing to do.
b.chainLock.RLock()
tip := b.bestNode
b.chainLock.RUnlock()
latestHeight := tip.height
if startHeight > latestHeight {
return nil, nil
}
// Limit the ending height to the latest height of the chain.
if endHeight > latestHeight+1 {
endHeight = latestHeight + 1
}
// Fetch requested hashes.
hashes := make([]chainhash.Hash, endHeight-startHeight)
b.heightLock.RLock()
iterNode := b.mainNodesByHeight[endHeight-1]
b.heightLock.RUnlock()
for i := startHeight; i < endHeight; i++ {
// Since the desired result is from the starting node to the
// ending node in forward order, but they are iterated in
// reverse, add them in reverse order.
hashes[endHeight-i-1] = iterNode.hash
iterNode = iterNode.parent
}
return hashes, nil
}
// locateInventory returns the node of the block after the first known block in
// the locator along with the number of subsequent nodes needed to either reach
// the provided stop hash or the provided max number of entries.

View File

@ -1856,13 +1856,13 @@ func (b *BlockChain) initChainState(interrupt <-chan struct{}) error {
}
// Load the best and parent blocks and cache them.
utilBlock, err := dbFetchBlockByHash(dbTx, &tip.hash)
utilBlock, err := dbFetchBlockByNode(dbTx, tip)
if err != nil {
return err
}
b.mainchainBlockCache[tip.hash] = utilBlock
if tip.parent != nil {
parentBlock, err := dbFetchBlockByHash(dbTx, &tip.parent.hash)
parentBlock, err := dbFetchBlockByNode(dbTx, tip.parent)
if err != nil {
return err
}
@ -1882,67 +1882,11 @@ func (b *BlockChain) initChainState(interrupt <-chan struct{}) error {
return err
}
// dbFetchHeaderByHash uses an existing database transaction to retrieve the
// block header for the provided hash.
func dbFetchHeaderByHash(dbTx database.Tx, hash *chainhash.Hash) (*wire.BlockHeader, error) {
headerBytes, err := dbTx.FetchBlockHeader(hash)
if err != nil {
return nil, err
}
var header wire.BlockHeader
err = header.Deserialize(bytes.NewReader(headerBytes))
if err != nil {
return nil, err
}
return &header, nil
}
// dbFetchHeaderByHeight uses an existing database transaction to retrieve the
// block header for the provided height.
func dbFetchHeaderByHeight(dbTx database.Tx, height int64) (*wire.BlockHeader, error) {
hash, err := dbFetchHashByHeight(dbTx, height)
if err != nil {
return nil, err
}
return dbFetchHeaderByHash(dbTx, hash)
}
// DBFetchHeaderByHeight is the exported version of dbFetchHeaderByHeight.
func DBFetchHeaderByHeight(dbTx database.Tx, height int64) (*wire.BlockHeader, error) {
return dbFetchHeaderByHeight(dbTx, height)
}
// HeaderByHeight is the exported version of dbFetchHeaderByHeight that
// internally creates a database transaction to do the lookup.
func (b *BlockChain) HeaderByHeight(height int64) (*wire.BlockHeader, error) {
var header *wire.BlockHeader
err := b.db.View(func(dbTx database.Tx) error {
var errLocal error
header, errLocal = dbFetchHeaderByHeight(dbTx, height)
return errLocal
})
if err != nil {
return nil, err
}
return header, nil
}
// dbFetchBlockByHash uses an existing database transaction to retrieve the raw
// block for the provided hash, deserialize it, retrieve the appropriate height
// from the index, and return a dcrutil.Block with the height set.
func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*dcrutil.Block, error) {
// Check if the block is in the main chain.
if !dbMainChainHasBlock(dbTx, hash) {
str := fmt.Sprintf("block %s is not in the main chain", hash)
return nil, errNotInMainChain(str)
}
// dbFetchBlockByNode uses an existing database transaction to retrieve the raw
// block for the provided node, deserialize it, and return a dcrutil.Block.
func dbFetchBlockByNode(dbTx database.Tx, node *blockNode) (*dcrutil.Block, error) {
// Load the raw block bytes from the database.
blockBytes, err := dbTx.FetchBlock(hash)
blockBytes, err := dbTx.FetchBlock(&node.hash)
if err != nil {
return nil, err
}
@ -1955,170 +1899,3 @@ func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*dcrutil.Block,
return block, nil
}
// dbFetchBlockByHeight uses an existing database transaction to retrieve the
// raw block for the provided height, deserialize it, and return a dcrutil.Block
// with the height set.
func dbFetchBlockByHeight(dbTx database.Tx, height int64) (*dcrutil.Block, error) {
// First find the hash associated with the provided height in the index.
hash, err := dbFetchHashByHeight(dbTx, height)
if err != nil {
return nil, err
}
// Load the raw block bytes from the database.
blockBytes, err := dbTx.FetchBlock(hash)
if err != nil {
return nil, err
}
// Create the encapsulated block and set the height appropriately.
block, err := dcrutil.NewBlockFromBytes(blockBytes)
if err != nil {
return nil, err
}
return block, nil
}
// DBFetchBlockByHeight is the exported version of dbFetchBlockByHeight.
func DBFetchBlockByHeight(dbTx database.Tx, height int64) (*dcrutil.Block, error) {
return dbFetchBlockByHeight(dbTx, height)
}
// dbMainChainHasBlock uses an existing database transaction to return whether
// or not the main chain contains the block identified by the provided hash.
func dbMainChainHasBlock(dbTx database.Tx, hash *chainhash.Hash) bool {
hashIndex := dbTx.Metadata().Bucket(dbnamespace.HashIndexBucketName)
return hashIndex.Get(hash[:]) != nil
}
// DBMainChainHasBlock is the exported version of dbMainChainHasBlock.
func DBMainChainHasBlock(dbTx database.Tx, hash *chainhash.Hash) bool {
return dbMainChainHasBlock(dbTx, hash)
}
// MainChainHasBlock returns whether or not the block with the given hash is in
// the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) (bool, error) {
var exists bool
err := b.db.View(func(dbTx database.Tx) error {
exists = dbMainChainHasBlock(dbTx, hash)
return nil
})
return exists, err
}
// BlockHeightByHash returns the height of the block with the given hash in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int64, error) {
var height int64
err := b.db.View(func(dbTx database.Tx) error {
var err error
height, err = dbFetchHeightByHash(dbTx, hash)
return err
})
return height, err
}
// BlockHashByHeight returns the hash of the block at the given height in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHashByHeight(blockHeight int64) (*chainhash.Hash, error) {
var hash *chainhash.Hash
err := b.db.View(func(dbTx database.Tx) error {
var err error
hash, err = dbFetchHashByHeight(dbTx, blockHeight)
return err
})
return hash, err
}
// BlockByHeight returns the block at the given height in the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHeight(blockHeight int64) (*dcrutil.Block, error) {
var block *dcrutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByHeight(dbTx, blockHeight)
return err
})
return block, err
}
// BlockByHash searches the internal chain block stores and the database in an
// attempt to find the requested block and returns it. This function returns
// blocks regardless of whether or not they are part of the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*dcrutil.Block, error) {
b.chainLock.RLock()
block, err := b.fetchBlockByHash(hash)
b.chainLock.RUnlock()
return block, err
}
// HeightRange returns a range of block hashes for the given start and end
// heights. It is inclusive of the start height and exclusive of the end
// height. The end height will be limited to the current main chain height.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeightRange(startHeight, endHeight int64) ([]chainhash.Hash, error) {
// Ensure requested heights are sane.
if startHeight < 0 {
return nil, fmt.Errorf("start height of fetch range must not "+
"be less than zero - got %d", startHeight)
}
if endHeight < startHeight {
return nil, fmt.Errorf("end height of fetch range must not "+
"be less than the start height - got start %d, end %d",
startHeight, endHeight)
}
// There is nothing to do when the start and end heights are the same,
// so return now to avoid the chain lock and a database transaction.
if startHeight == endHeight {
return nil, nil
}
// Grab a lock on the chain to prevent it from changing due to a reorg
// while building the hashes.
b.chainLock.RLock()
defer b.chainLock.RUnlock()
// When the requested start height is after the most recent best chain
// height, there is nothing to do.
latestHeight := b.bestNode.height
if startHeight > latestHeight {
return nil, nil
}
// Limit the ending height to the latest height of the chain.
if endHeight > latestHeight+1 {
endHeight = latestHeight + 1
}
// Fetch as many as are available within the specified range.
var hashList []chainhash.Hash
err := b.db.View(func(dbTx database.Tx) error {
hashes := make([]chainhash.Hash, 0, endHeight-startHeight)
for i := startHeight; i < endHeight; i++ {
hash, err := dbFetchHashByHeight(dbTx, i)
if err != nil {
return err
}
hashes = append(hashes, *hash)
}
// Set the list to be returned to the constructed list.
hashList = hashes
return nil
})
return hashList, err
}

View File

@ -48,13 +48,11 @@ func (b *BlockChain) ChainTips() []dcrjson.GetChainTipsResult {
}
b.index.RUnlock()
b.chainLock.Lock()
bestTip := b.bestNode
b.chainLock.Unlock()
// Generate the results sorted by descending height.
sort.Sort(sort.Reverse(nodeHeightSorter(chainTips)))
results := make([]dcrjson.GetChainTipsResult, len(chainTips))
b.chainLock.RLock()
bestTip := b.bestNode
for i, tip := range chainTips {
// Find the fork point in order calculate the branch length later.
fork := tip
@ -101,5 +99,6 @@ func (b *BlockChain) ChainTips() []dcrjson.GetChainTipsResult {
result.Status = "valid-headers"
}
}
b.chainLock.RUnlock()
return results
}

View File

@ -7,10 +7,10 @@ package blockchain
import (
"fmt"
"time"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
)
@ -225,69 +225,63 @@ func (b *BlockChain) IsCheckpointCandidate(block *dcrutil.Block) (bool, error) {
return false, fmt.Errorf("checkpoints are disabled")
}
var isCandidate bool
err := b.db.View(func(dbTx database.Tx) error {
// A checkpoint must be in the main chain.
blockHeight, err := dbFetchHeightByHash(dbTx, block.Hash())
if err != nil {
// Only return an error if it's not due to the block not
// being in the main chain.
if !isNotInMainChainErr(err) {
return err
}
return nil
}
// A checkpoint must be in the main chain.
node := b.index.LookupNode(block.Hash())
if node == nil || !node.inMainChain {
return false, nil
}
// Ensure the height of the passed block and the entry for the
// block in the main chain match. This should always be the
// case unless the caller provided an invalid block.
if blockHeight != block.Height() {
return fmt.Errorf("passed block height of %d does not "+
"match the main chain height of %d",
block.Height(), blockHeight)
}
// Ensure the height of the passed block and the entry for the block in
// the main chain match. This should always be the case unless the
// caller provided an invalid block.
if node.height != block.Height() {
return false, fmt.Errorf("passed block height of %d does not "+
"match the main chain height of %d", block.Height(),
node.height)
}
// A checkpoint must be at least CheckpointConfirmations blocks
// before the end of the main chain.
mainChainHeight := b.bestNode.height
if blockHeight > (mainChainHeight - CheckpointConfirmations) {
return nil
}
// A checkpoint must be at least CheckpointConfirmations blocks before
// the end of the main chain.
tip := b.bestNode
if node.height > (tip.height - CheckpointConfirmations) {
return false, nil
}
// Get the previous block header.
prevHash := &block.MsgBlock().Header.PrevBlock
prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash)
if err != nil {
return err
}
// A checkpoint must be have at least one block after it.
//
// This should always succeed since the check above already made sure it
// is CheckpointConfirmations back, but be safe in case the constant
// changes.
b.heightLock.RLock()
nextNode := b.mainNodesByHeight[node.height+1]
b.heightLock.RUnlock()
if nextNode == nil {
return false, nil
}
// Get the next block header.
nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1)
if err != nil {
return err
}
// A checkpoint must be have at least one block before it.
if node.parent == nil {
return false, nil
}
// A checkpoint must have timestamps for the block and the
// blocks on either side of it in order (due to the median time
// allowance this is not always the case).
prevTime := prevHeader.Timestamp
curTime := block.MsgBlock().Header.Timestamp
nextTime := nextHeader.Timestamp
if prevTime.After(curTime) || nextTime.Before(curTime) {
return nil
}
// A checkpoint must have timestamps for the block and the blocks on
// either side of it in order (due to the median time allowance this is
// not always the case).
prevTime := time.Unix(node.parent.timestamp, 0)
curTime := block.MsgBlock().Header.Timestamp
nextTime := time.Unix(nextNode.timestamp, 0)
if prevTime.After(curTime) || nextTime.Before(curTime) {
return false, nil
}
// A checkpoint must have transactions that only contain
// standard scripts.
for _, tx := range block.Transactions() {
if isNonstandardTransaction(tx) {
return nil
}
// A checkpoint must have transactions that only contain
// standard scripts.
for _, tx := range block.Transactions() {
if isNonstandardTransaction(tx) {
return false, nil
}
}
// All of the checks passed, so the block is a candidate.
isCandidate = true
return nil
})
return isCandidate, err
// All of the checks passed, so the block is a candidate.
return true, nil
}

View File

@ -236,6 +236,16 @@ func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error {
return nil
}
// dbFetchBlockByHash uses an existing database transaction to retrieve the raw
// block for the provided hash, deserialize it, and return a dcrutil.Block.
func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*dcrutil.Block, error) {
blockBytes, err := dbTx.FetchBlock(hash)
if err != nil {
return nil, err
}
return dcrutil.NewBlockFromBytes(blockBytes)
}
// Init initializes the enabled indexes. This is called during chain
// initialization and primarily consists of catching up all indexes to the
// current best chain tip. This is necessary since each index can be disabled
@ -310,12 +320,11 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
var interrupted bool
initialHeight := height
err = m.db.Update(func(dbTx database.Tx) error {
for !blockchain.DBMainChainHasBlock(dbTx, hash) {
for !chain.MainChainHasBlock(hash) {
// Get the block, unless it's already cached.
var block *dcrutil.Block
if cachedBlock == nil && height > 0 {
block, err = blockchain.DBFetchBlockByHeight(dbTx,
int64(height))
block, err = dbFetchBlockByHash(dbTx, hash)
if err != nil {
return err
}
@ -325,8 +334,8 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
// Load the parent block for the height since it is
// required to remove it.
parent, err := blockchain.DBFetchBlockByHeight(dbTx,
int64(height)-1)
parentHash := &block.MsgBlock().Header.PrevBlock
parent, err := dbFetchBlockByHash(dbTx, parentHash)
if err != nil {
return err
}
@ -435,8 +444,11 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
err = m.db.Update(func(dbTx database.Tx) error {
// Get the parent of the block, unless it's already cached.
if cachedParent == nil && height > 0 {
parent, err = blockchain.DBFetchBlockByHeight(
dbTx, int64(height-1))
parentHash, err := chain.BlockHashByHeight(int64(height - 1))
if err != nil {
return err
}
parent, err = dbFetchBlockByHash(dbTx, parentHash)
if err != nil {
return err
}
@ -446,8 +458,11 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
// Load the block for the height since it is required to index
// it.
block, err = blockchain.DBFetchBlockByHeight(dbTx,
int64(height))
hash, err := chain.BlockHashByHeight(int64(height))
if err != nil {
return err
}
block, err = dbFetchBlockByHash(dbTx, hash)
if err != nil {
return err
}

View File

@ -42,7 +42,7 @@ func (b *BlockChain) fetchNewTicketsForNode(node *blockNode) ([]chainhash.Hash,
"ancestor is genesis block")
}
matureBlock, errBlock := b.fetchBlockByHash(&matureNode.hash)
matureBlock, errBlock := b.fetchBlockByNode(matureNode)
if errBlock != nil {
return nil, errBlock
}

View File

@ -113,6 +113,30 @@ func upgradeToVersion2(db database.DB, chainParams *chaincfg.Params, dbInfo *dat
// Hardcoded so updates to the global values do not affect old upgrades.
chainStateKeyName := []byte("chainstate")
// This is a legacy function that relied on information in the database that
// is no longer available in more recent code.
dbFetchBlockByHeight := func(dbTx database.Tx, height int64) (*dcrutil.Block, error) {
// First find the hash associated with the provided height in the index.
hash, err := dbFetchHashByHeight(dbTx, height)
if err != nil {
return nil, err
}
// Load the raw block bytes from the database.
blockBytes, err := dbTx.FetchBlock(hash)
if err != nil {
return nil, err
}
// Create the encapsulated block and set the height appropriately.
block, err := dcrutil.NewBlockFromBytes(blockBytes)
if err != nil {
return nil, err
}
return block, nil
}
log.Infof("Initializing upgrade to database version 2")
progressLogger := progresslog.NewBlockProgressLogger("Upgraded", log)

View File

@ -1095,11 +1095,11 @@ func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoi
// chain.
if treeValid {
view.SetStakeViewpoint(ViewpointPrevValidRegular)
block, err := b.fetchMainChainBlockByHash(&tip.hash)
block, err := b.fetchMainChainBlockByNode(tip)
if err != nil {
return nil, err
}
parent, err := b.fetchMainChainBlockByHash(&tip.parent.hash)
parent, err := b.fetchMainChainBlockByNode(tip.parent)
if err != nil {
return nil, err
}

View File

@ -2605,7 +2605,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
if prevNode.hash == tip.hash {
// Grab the parent block since it is required throughout the block
// connection process.
parent, err := b.fetchMainChainBlockByHash(&prevNode.hash)
parent, err := b.fetchMainChainBlockByNode(prevNode)
if err != nil {
return ruleError(ErrMissingParent, err.Error())
}
@ -2634,7 +2634,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
block := nextBlockToDetach
if block == nil {
var err error
block, err = b.fetchMainChainBlockByHash(&n.hash)
block, err = b.fetchMainChainBlockByNode(n)
if err != nil {
return err
}
@ -2645,7 +2645,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
block.Hash())
}
parent, err := b.fetchMainChainBlockByHash(&n.parent.hash)
parent, err := b.fetchMainChainBlockByNode(n.parent)
if err != nil {
return err
}
@ -2676,7 +2676,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
if attachNodes.Len() == 0 {
// Grab the parent block since it is required throughout the block
// connection process.
parent, err := b.fetchMainChainBlockByHash(&prevNode.hash)
parent, err := b.fetchMainChainBlockByNode(prevNode)
if err != nil {
return ruleError(ErrMissingParent, err.Error())
}
@ -2693,14 +2693,14 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
// attached or the previous one that was attached for subsequent blocks
// to optimize.
n := e.Value.(*blockNode)
block, err := b.fetchBlockByHash(&n.hash)
block, err := b.fetchBlockByNode(n)
if err != nil {
return err
}
parent := prevAttachBlock
if parent == nil {
var err error
parent, err = b.fetchMainChainBlockByHash(&n.parent.hash)
parent, err = b.fetchMainChainBlockByNode(n.parent)
if err != nil {
return err
}
@ -2722,7 +2722,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error {
// Grab the parent block since it is required throughout the block
// connection process.
parent, err := b.fetchBlockByHash(&prevNode.hash)
parent, err := b.fetchBlockByNode(prevNode)
if err != nil {
return ruleError(ErrMissingParent, err.Error())
}

View File

@ -92,7 +92,7 @@ func TestBlockchainSpendJournal(t *testing.T) {
str := fmt.Sprintf("no block at height %d exists", 1)
return errNotInMainChain(str)
}
parent, err := dbFetchBlockByHash(dbTx, &parentNode.hash)
parent, err := dbFetchBlockByNode(dbTx, parentNode)
if err != nil {
return err
}
@ -103,7 +103,7 @@ func TestBlockchainSpendJournal(t *testing.T) {
str := fmt.Sprintf("no block at height %d exists", i)
return errNotInMainChain(str)
}
block, err := dbFetchBlockByHash(dbTx, &node.hash)
block, err := dbFetchBlockByNode(dbTx, node)
if err != nil {
return err
}

View File

@ -1549,17 +1549,14 @@ func handleEstimateStakeDiff(s *rpcServer, cmd interface{}, closeChan <-chan str
nextAdjustment := ((bestHeight / activeNetParams.StakeDiffWindowSize) +
1) * activeNetParams.StakeDiffWindowSize
totalTickets := 0
err = s.server.db.View(func(dbTx database.Tx) error {
for i := lastAdjustment; i <= bestHeight; i++ {
bh, err := blockchain.DBFetchHeaderByHeight(dbTx, i)
if err != nil {
return err
}
totalTickets += int(bh.FreshStake)
for i := lastAdjustment; i <= bestHeight; i++ {
bh, err := chain.HeaderByHeight(i)
if err != nil {
return nil, rpcInternalError(err.Error(), "Could not "+
"estimate next stake difficulty")
}
return nil
})
totalTickets += int(bh.FreshStake)
}
blocksSince := float64(bestHeight - lastAdjustment + 1)
remaining := float64(nextAdjustment - bestHeight - 1)
averagePerBlock := float64(totalTickets) / blocksSince
@ -1969,14 +1966,11 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
best := s.chain.BestSnapshot()
// See if this block is an orphan and adjust Confirmations accordingly.
onMainChain, _ := s.chain.MainChainHasBlock(hash)
// Get next block hash unless there are none.
var nextHashString string
blockHeader := &blk.MsgBlock().Header
confirmations := int64(-1)
if onMainChain {
if s.chain.MainChainHasBlock(hash) {
if int64(blockHeader.Height) < best.Height {
nextHash, err := s.chain.BlockHashByHeight(int64(blockHeader.Height + 1))
if err != nil {
@ -2120,14 +2114,11 @@ func handleGetBlockHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct
best := s.chain.BestSnapshot()
// See if this block is an orphan and adjust Confirmations accordingly.
onMainChain, _ := s.chain.MainChainHasBlock(hash)
// Get next block hash unless there are none.
var nextHashString string
confirmations := int64(-1)
height := int64(blockHeader.Height)
if onMainChain {
if s.chain.MainChainHasBlock(hash) {
if height < best.Height {
nextHash, err := s.chain.BlockHashByHeight(height + 1)
if err != nil {