This adds code to migrate the existing block index in ffldb to the new format managed by the blockchain package and updates the code to use the new infrastructure.
234 lines
7.8 KiB
Go
234 lines
7.8 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/database"
|
|
"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("PrevNodeFromBlock: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
// This function should never be called with orphan blocks or the
|
|
// genesis block.
|
|
if prevNode == nil {
|
|
prevHash := &block.MsgBlock().Header.PrevBlock
|
|
str := fmt.Sprintf("previous block %s is not known", prevHash)
|
|
return false, 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() {
|
|
prevHash := &block.MsgBlock().Header.PrevBlock
|
|
str := fmt.Sprintf("previous block %s is known to be invalid",
|
|
prevHash)
|
|
return false, ruleError(ErrInvalidAncestorBlock, str)
|
|
}
|
|
|
|
// 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 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)
|
|
|
|
// 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.
|
|
//
|
|
// Also, store the associated block index entry when not running in dry
|
|
// run mode.
|
|
err = b.db.Update(func(dbTx database.Tx) error {
|
|
if err := dbMaybeStoreBlock(dbTx, block); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !dryRun {
|
|
if err := dbPutBlockNode(dbTx, newNode); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Remove the node from the block index and disconnect it from the
|
|
// parent node when running in dry run mode.
|
|
if dryRun {
|
|
defer func() {
|
|
b.index.RemoveNode(newNode)
|
|
}()
|
|
}
|
|
|
|
// 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
|
|
}
|