dcrd/blockchain/accept.go
Dave Collins 1c82df5f3a
blockchain: Move upgrade-only code to upgrade.go.
This moves the ticketsSpentInBlock and ticketsRevokedInBlock functions
to the upgrade.go file since they are only used in the upgrade code.
2018-02-02 13:20:21 -06:00

183 lines
6.0 KiB
Go

// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2018 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 (
"encoding/binary"
"fmt"
"math"
"time"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
)
// checkCoinbaseUniqueHeight checks to ensure that for all blocks height > 1
// that the coinbase contains the height encoding to make coinbase hash collisions
// impossible.
func checkCoinbaseUniqueHeight(blockHeight int64, block *dcrutil.Block) error {
// Coinbase TxOut[0] is always tax, TxOut[1] is always
// height + extranonce, so at least two outputs must
// exist.
if len(block.MsgBlock().Transactions[0].TxOut) < 2 {
str := fmt.Sprintf("block %v is missing necessary coinbase "+
"outputs", block.Hash())
return ruleError(ErrFirstTxNotCoinbase, str)
}
// The first 4 bytes of the NullData output must be the
// encoded height of the block, so that every coinbase
// created has a unique transaction hash.
nullData, err := txscript.GetNullDataContent(
block.MsgBlock().Transactions[0].TxOut[1].Version,
block.MsgBlock().Transactions[0].TxOut[1].PkScript)
if err != nil {
str := fmt.Sprintf("block %v txOut 1 has wrong pkScript "+
"type", block.Hash())
return ruleError(ErrFirstTxNotCoinbase, str)
}
if len(nullData) < 4 {
str := fmt.Sprintf("block %v txOut 1 has too short nullData "+
"push to contain height", block.Hash())
return ruleError(ErrFirstTxNotCoinbase, str)
}
// Check the height and ensure it is correct.
cbHeight := binary.LittleEndian.Uint32(nullData[0:4])
if cbHeight != uint32(blockHeight) {
prevBlock := block.MsgBlock().Header.PrevBlock
str := fmt.Sprintf("block %v txOut 1 has wrong height in "+
"coinbase; want %v, got %v; prevBlock %v, header height %v",
block.Hash(), blockHeight, cbHeight, prevBlock,
block.MsgBlock().Header.Height)
return ruleError(ErrCoinbaseHeight, str)
}
return nil
}
// IsFinalizedTransaction determines whether or not a transaction is finalized.
func IsFinalizedTransaction(tx *dcrutil.Tx, blockHeight int64, blockTime time.Time) bool {
// Lock time of zero means the transaction is finalized.
msgTx := tx.MsgTx()
lockTime := msgTx.LockTime
if lockTime == 0 {
return true
}
// The lock time field of a transaction is either a block height at
// which the transaction is finalized or a timestamp depending on if the
// value is before the txscript.LockTimeThreshold. When it is under the
// threshold it is a block height.
var blockTimeOrHeight int64
if lockTime < txscript.LockTimeThreshold {
blockTimeOrHeight = blockHeight
} else {
blockTimeOrHeight = blockTime.Unix()
}
if int64(lockTime) < blockTimeOrHeight {
return true
}
// At this point, the transaction's lock time hasn't occurred yet, but
// the transaction might still be finalized if the sequence number
// for all transaction inputs is maxed out.
for _, txIn := range msgTx.TxIn {
if txIn.Sequence != math.MaxUint32 {
return false
}
}
return true
}
// maybeAcceptBlock potentially accepts a block into the block chain and, if
// accepted, returns whether or not it is on the main chain. 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.
//
// The flags modify the behavior of this function as follows:
// - BFDryRun: The memory chain index will not be pruned and no accept
// notification will be sent since the block is not being accepted.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) (bool, error) {
dryRun := flags&BFDryRun == BFDryRun
// Get a block node for the block previous to this one. Will be nil
// if this is the genesis block.
prevNode, err := b.index.PrevNodeFromBlock(block)
if err != nil {
log.Debugf("getPrevNodeFromBlock: %v", err)
return false, err
}
blockHeight := block.Height()
// The block must pass all of the validation rules which depend on the
// position of the block within the block chain.
err = b.checkBlockContext(block, prevNode, flags)
if err != nil {
return false, err
}
// Prune stake nodes which are no longer needed before creating a new
// node.
if !dryRun {
b.pruner.pruneChainIfNeeded()
}
// Create a new block node for the block and add it to the in-memory
// block chain (could be either a side chain or the main chain).
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader,
stake.FindSpentTicketsInBlock(block.MsgBlock()))
if prevNode != nil {
newNode.parent = prevNode
newNode.height = blockHeight
newNode.workSum.Add(prevNode.workSum, newNode.workSum)
}
// 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.bestNode.height-minMemoryNodes {
newNode.stakeNode, err = b.fetchStakeNode(newNode)
if err != nil {
return false, err
}
newNode.stakeUndoData = newNode.stakeNode.UndoData()
}
// Grab the parent block since it is required throughout the block
// connection process.
parent, err := b.fetchBlockByHash(&newNode.parentHash)
if err != nil {
return false, 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.
isMainChain, err := b.connectBestChain(newNode, block, parent, flags)
if err != nil {
return false, 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.
if !dryRun {
b.chainLock.Unlock()
b.sendNotification(NTBlockAccepted,
&BlockAcceptedNtfnsData{isMainChain, block})
b.chainLock.Lock()
}
return isMainChain, nil
}