From c6b5f6df0d1efcebd4bd348e7cdd86df22a789d4 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Mon, 2 Jul 2018 15:52:12 -0500 Subject: [PATCH] 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 --- blockchain/accept.go | 2 +- blockchain/chain.go | 228 ++++++++++++++++++++++++++------ blockchain/chainio.go | 235 +-------------------------------- blockchain/chainquery.go | 7 +- blockchain/checkpoints.go | 110 ++++++++------- blockchain/indexers/manager.go | 33 +++-- blockchain/stakenode.go | 2 +- blockchain/upgrade.go | 24 ++++ blockchain/utxoviewpoint.go | 4 +- blockchain/validate.go | 14 +- blockchain/validate_test.go | 4 +- rpcserver.go | 27 ++-- 12 files changed, 317 insertions(+), 373 deletions(-) diff --git a/blockchain/accept.go b/blockchain/accept.go index 69437bb9bc..79adac6521 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -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 } diff --git a/blockchain/chain.go b/blockchain/chain.go index 6c9962b8e1..e9867f2cb1 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -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 - } + // Check orphan cache. + b.orphanLock.RLock() + orphan, existsOrphans := b.orphans[node.hash] + b.orphanLock.RUnlock() + if existsOrphans { + return orphan.block, nil + } - block, err = dcrutil.NewBlockFromBytes(blockBytes) + // 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 }) - if err == nil && block != nil { - return block, nil - } - - return nil, fmt.Errorf("unable to find block %v in cache or db", hash) + 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. diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 2435e6a62b..1fe500ee1f 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -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,92 +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) - } - - // 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 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 - } - +// 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 } @@ -1980,145 +1899,3 @@ func dbFetchBlockByHeight(dbTx database.Tx, height int64) (*dcrutil.Block, error 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 -} diff --git a/blockchain/chainquery.go b/blockchain/chainquery.go index efa49177e6..709fc71e64 100644 --- a/blockchain/chainquery.go +++ b/blockchain/chainquery.go @@ -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 } diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go index b03c9e7748..ed10fec360 100644 --- a/blockchain/checkpoints.go +++ b/blockchain/checkpoints.go @@ -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 } diff --git a/blockchain/indexers/manager.go b/blockchain/indexers/manager.go index 0fb6ebbc18..e9159fe503 100644 --- a/blockchain/indexers/manager.go +++ b/blockchain/indexers/manager.go @@ -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 } diff --git a/blockchain/stakenode.go b/blockchain/stakenode.go index 1c7f364376..1703b2fce2 100644 --- a/blockchain/stakenode.go +++ b/blockchain/stakenode.go @@ -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 } diff --git a/blockchain/upgrade.go b/blockchain/upgrade.go index bd9aef33eb..9a90cc2bf8 100644 --- a/blockchain/upgrade.go +++ b/blockchain/upgrade.go @@ -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) diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index 521cbe7ed9..88549c0f00 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -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 } diff --git a/blockchain/validate.go b/blockchain/validate.go index f5a2df6736..41e50880a7 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -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()) } diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go index 54a05ee8fd..daf54e11d8 100644 --- a/blockchain/validate_test.go +++ b/blockchain/validate_test.go @@ -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 } diff --git a/rpcserver.go b/rpcserver.go index 84fbc43e89..98fe4a7f24 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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 {