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 {