dcrd/blockchain/accept.go
Dave Collins 2dac209198
blockchain: Use lastest major version deps.
This udpates the blockchain module to use the lastest module major
versions.

While here, it also corrects a few typos and updates some test function
names to more accurately reflect their purpose.

The updated direct dependencies are as follows:

- github.com/decred/dcrd/blockchain/stake/v2@v2.0.1
- github.com/decred/dcrd/chaincfg/v2@v2.2.0
- github.com/decred/dcrd/database/v2@v2.0.0
- github.com/decred/dcrd/dcrutil/v2@v2.0.0
- github.com/decred/dcrd/txscript/v2@v2.0.0
2019-08-08 13:10:20 -05:00

155 lines
5.8 KiB
Go

// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"github.com/decred/dcrd/blockchain/stake/v2"
"github.com/decred/dcrd/database/v2"
"github.com/decred/dcrd/dcrutil/v2"
)
// maybeAcceptBlock potentially accepts a block into the block chain and, if
// accepted, returns the length of the fork the block extended. It performs
// several validation checks which depend on its position within the block chain
// before adding it. The block is expected to have already gone through
// ProcessBlock before calling this function with it. In the case the block
// extends the best chain or is now the tip of the best chain due to causing a
// reorganize, the fork length will be 0.
//
// The flags are also passed to checkBlockPositional, checkBlockContext and
// connectBestChain. See their documentation for how the flags modify their
// behavior.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) (int64, error) {
// This function should never be called with orphan blocks or the
// genesis block.
prevHash := &block.MsgBlock().Header.PrevBlock
prevNode := b.index.LookupNode(prevHash)
if prevNode == nil {
str := fmt.Sprintf("previous block %s is not known", prevHash)
return 0, ruleError(ErrMissingParent, str)
}
// There is no need to validate the block if an ancestor is already
// known to be invalid.
if b.index.NodeStatus(prevNode).KnownInvalid() {
str := fmt.Sprintf("previous block %s is known to be invalid",
prevHash)
return 0, ruleError(ErrInvalidAncestorBlock, str)
}
// The block must pass all of the validation rules which depend on having
// the headers of all ancestors available, but do not rely on having the
// full block data of all ancestors available.
err := b.checkBlockPositional(block, prevNode, flags)
if err != nil {
return 0, err
}
// The block must pass all of the validation rules which depend on having
// the full block data for all of its ancestors available.
err = b.checkBlockContext(block, prevNode, flags)
if err != nil {
return 0, err
}
// Prune stake nodes which are no longer needed before creating a new
// node.
b.pruner.pruneChainIfNeeded()
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the main chain or
// blocks that fail to connect available for further analysis.
err = b.db.Update(func(dbTx database.Tx) error {
return dbMaybeStoreBlock(dbTx, block)
})
if err != nil {
return 0, err
}
// Create a new block node for the block and add it to the block index.
// The block could either be on a side chain or the main chain, but it
// starts off as a side chain regardless.
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, prevNode)
newNode.populateTicketInfo(stake.FindSpentTicketsInBlock(block.MsgBlock()))
newNode.status = statusDataStored
b.index.AddNode(newNode)
// Ensure the new block index entry is written to the database.
err = b.flushBlockIndex()
if err != nil {
return 0, err
}
// Notify the caller when the block intends to extend the main chain,
// the chain believes it is current, and the block has passed all of the
// sanity and contextual checks, such as having valid proof of work,
// valid merkle and stake roots, and only containing allowed votes and
// revocations.
//
// This allows the block to be relayed before doing the more expensive
// connection checks, because even though the block might still fail
// to connect and becomes the new main chain tip, that is quite rare in
// practice since a lot of work was expended to create a block that
// satisfies the proof of work requirement.
//
// Notice that the chain lock is not released before sending the
// notification. This is intentional and must not be changed without
// understanding why!
if b.isCurrent() && b.bestChain.Tip() == prevNode {
b.sendNotification(NTNewTipBlockChecked, block)
}
// Fetching a stake node could enable a new DoS vector, so restrict
// this only to blocks that are recent in history.
if newNode.height < b.bestChain.Tip().height-minMemoryNodes {
newNode.stakeNode, err = b.fetchStakeNode(newNode)
if err != nil {
return 0, err
}
}
// Grab the parent block since it is required throughout the block
// connection process.
parent, err := b.fetchBlockByNode(newNode.parent)
if err != nil {
return 0, err
}
// Connect the passed block to the chain while respecting proper chain
// selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts.
forkLen, err := b.connectBestChain(newNode, block, parent, flags)
if err != nil {
return 0, err
}
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// inventory to other peers unless it was already relayed above
// via NTNewTipBlockChecked.
bestHeight := b.bestChain.Tip().height
b.chainLock.Unlock()
b.sendNotification(NTBlockAccepted, &BlockAcceptedNtfnsData{
BestHeight: bestHeight,
ForkLen: forkLen,
Block: block,
})
b.chainLock.Lock()
return forkLen, nil
}