diff --git a/blockchain/accept.go b/blockchain/accept.go index d7c51511..c5b07d52 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -4,11 +4,7 @@ package blockchain -import ( - "fmt" - - "github.com/btcsuite/btcutil" -) +import "github.com/btcsuite/btcutil" // maybeAcceptBlock potentially accepts a block into the memory block chain. // It performs several validation checks which depend on its position within @@ -16,11 +12,12 @@ import ( // through ProcessBlock before calling this function with it. // // The flags modify the behavior of this function as follows: -// - BFFastAdd: The somewhat expensive BIP0034 validation is not performed. // - BFDryRun: The memory chain index will not be pruned and no accept // notification will be sent since the block is not being accepted. +// +// The flags are also passed to checkBlockContext and connectBestChain. See +// their documentation for how the flags modify their behavior. func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) error { - fastAdd := flags&BFFastAdd == BFFastAdd dryRun := flags&BFDryRun == BFDryRun // Get a block node for the block previous to this one. Will be nil @@ -39,112 +36,12 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) } block.SetHeight(blockHeight) - blockHeader := &block.MsgBlock().Header - if !fastAdd { - // Ensure the difficulty specified in the block header matches - // the calculated difficulty based on the previous block and - // difficulty retarget rules. - expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode, - block.MsgBlock().Header.Timestamp) - if err != nil { - return err - } - blockDifficulty := blockHeader.Bits - if blockDifficulty != expectedDifficulty { - str := "block difficulty of %d is not the expected value of %d" - str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty) - return ruleError(ErrUnexpectedDifficulty, str) - } - - // Ensure the timestamp for the block header is after the - // median time of the last several blocks (medianTimeBlocks). - medianTime, err := b.calcPastMedianTime(prevNode) - if err != nil { - log.Errorf("calcPastMedianTime: %v", err) - return err - } - if !blockHeader.Timestamp.After(medianTime) { - str := "block timestamp of %v is not after expected %v" - str = fmt.Sprintf(str, blockHeader.Timestamp, - medianTime) - return ruleError(ErrTimeTooOld, str) - } - - // Ensure all transactions in the block are finalized. - for _, tx := range block.Transactions() { - if !IsFinalizedTransaction(tx, blockHeight, - blockHeader.Timestamp) { - str := fmt.Sprintf("block contains "+ - "unfinalized transaction %v", tx.Sha()) - return ruleError(ErrUnfinalizedTx, str) - } - } - - } - - // Ensure chain matches up to predetermined checkpoints. - blockHash := block.Sha() - if !b.verifyCheckpoint(blockHeight, blockHash) { - str := fmt.Sprintf("block at height %d does not match "+ - "checkpoint hash", blockHeight) - return ruleError(ErrBadCheckpoint, str) - } - - // Find the previous checkpoint and prevent blocks which fork the main - // chain before it. This prevents storage of new, otherwise valid, - // blocks which build off of old blocks that are likely at a much easier - // difficulty and therefore could be used to waste cache and disk space. - checkpointBlock, err := b.findPreviousCheckpoint() + // 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 err } - if checkpointBlock != nil && blockHeight < checkpointBlock.Height() { - str := fmt.Sprintf("block at height %d forks the main chain "+ - "before the previous checkpoint at height %d", - blockHeight, checkpointBlock.Height()) - return ruleError(ErrForkTooOld, str) - } - - if !fastAdd { - // Reject version 2 blocks once a majority of the network has - // upgraded. This is part of BIP0066. - if blockHeader.Version < 3 && b.isMajorityVersion(3, prevNode, - b.chainParams.BlockRejectNumRequired) { - - str := "new blocks with version %d are no longer valid" - str = fmt.Sprintf(str, blockHeader.Version) - return ruleError(ErrBlockVersionTooOld, str) - } - - // Reject version 1 blocks once a majority of the network has - // upgraded. This is part of BIP0034. - if blockHeader.Version < 2 && b.isMajorityVersion(2, prevNode, - b.chainParams.BlockRejectNumRequired) { - - str := "new blocks with version %d are no longer valid" - str = fmt.Sprintf(str, blockHeader.Version) - return ruleError(ErrBlockVersionTooOld, str) - } - - // Ensure coinbase starts with serialized block heights for - // blocks whose version is the serializedHeightVersion or - // newer once a majority of the network has upgraded. This is - // part of BIP0034. - if ShouldHaveSerializedBlockHeight(blockHeader) && - b.isMajorityVersion(serializedHeightVersion, prevNode, - b.chainParams.BlockEnforceNumRequired) { - - expectedHeight := int64(0) - if prevNode != nil { - expectedHeight = prevNode.height + 1 - } - coinbaseTx := block.Transactions()[0] - err := checkSerializedHeight(coinbaseTx, expectedHeight) - if err != nil { - return err - } - } - } // Prune block nodes which are no longer needed before creating // a new node. @@ -157,7 +54,8 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) // 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). - newNode := newBlockNode(blockHeader, blockHash, blockHeight) + blockHeader := &block.MsgBlock().Header + newNode := newBlockNode(blockHeader, block.Sha(), blockHeight) if prevNode != nil { newNode.parent = prevNode newNode.height = blockHeight diff --git a/blockchain/validate.go b/blockchain/validate.go index 991826bd..8d2dd721 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -304,13 +304,12 @@ func CheckTransactionSanity(tx *btcutil.Tx) error { // difficulty is in min/max range and that the block hash is less than the // target difficulty as claimed. // -// // The flags modify the behavior of this function as follows: // - BFNoPoWCheck: The check to ensure the block hash is less than the target // difficulty is not performed. -func checkProofOfWork(block *btcutil.Block, powLimit *big.Int, flags BehaviorFlags) error { +func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error { // The target difficulty must be larger than zero. - target := CompactToBig(block.MsgBlock().Header.Bits) + target := CompactToBig(header.Bits) if target.Sign() <= 0 { str := fmt.Sprintf("block target difficulty of %064x is too low", target) @@ -328,7 +327,8 @@ func checkProofOfWork(block *btcutil.Block, powLimit *big.Int, flags BehaviorFla // to avoid proof of work checks is set. if flags&BFNoPoWCheck != BFNoPoWCheck { // The block hash must be less than the claimed target. - hashNum := ShaHashToBig(block.Sha()) + hash := header.BlockSha() + hashNum := ShaHashToBig(&hash) if hashNum.Cmp(target) > 0 { str := fmt.Sprintf("block hash of %064x is higher than "+ "expected max of %064x", hashNum, target) @@ -343,7 +343,7 @@ func checkProofOfWork(block *btcutil.Block, powLimit *big.Int, flags BehaviorFla // difficulty is in min/max range and that the block hash is less than the // target difficulty as claimed. func CheckProofOfWork(block *btcutil.Block, powLimit *big.Int) error { - return checkProofOfWork(block, powLimit, BFNone) + return checkProofOfWork(&block.MsgBlock().Header, powLimit, BFNone) } // CountSigOps returns the number of signature operations for all transaction @@ -436,14 +436,58 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, txStore TxStore) (int, e return totalSigOps, nil } +// checkBlockHeaderSanity performs some preliminary checks on a block header to +// ensure it is sane before continuing with processing. These checks are +// context free. +// +// The flags do not modify the behavior of this function directly, however they +// are needed to pass along to checkProofOfWork. +func checkBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error { + // Ensure the proof of work bits in the block header is in min/max range + // and the block hash is less than the target value described by the + // bits. + err := checkProofOfWork(header, powLimit, flags) + if err != nil { + return err + } + + // A block timestamp must not have a greater precision than one second. + // This check is necessary because Go time.Time values support + // nanosecond precision whereas the consensus rules only apply to + // seconds and it's much nicer to deal with standard Go time values + // instead of converting to seconds everywhere. + if !header.Timestamp.Equal(time.Unix(header.Timestamp.Unix(), 0)) { + str := fmt.Sprintf("block timestamp of %v has a higher "+ + "precision than one second", header.Timestamp) + return ruleError(ErrInvalidTime, str) + } + + // Ensure the block time is not too far in the future. + maxTimestamp := timeSource.AdjustedTime().Add(time.Second * + MaxTimeOffsetSeconds) + if header.Timestamp.After(maxTimestamp) { + str := fmt.Sprintf("block timestamp of %v is too far in the "+ + "future", header.Timestamp) + return ruleError(ErrTimeTooNew, str) + } + + return nil +} + // checkBlockSanity performs some preliminary checks on a block to ensure it is // sane before continuing with block processing. These checks are context free. // // The flags do not modify the behavior of this function directly, however they -// are needed to pass along to checkProofOfWork. +// are needed to pass along to checkBlockHeaderSanity. func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error { - // A block must have at least one transaction. msgBlock := block.MsgBlock() + header := &msgBlock.Header + err := checkBlockHeaderSanity(header, powLimit, timeSource, flags) + if err != nil { + return err + } + + // A block must have at least one transaction. numTx := len(msgBlock.Transactions) if numTx == 0 { return ruleError(ErrNoTransactions, "block does not contain "+ @@ -466,35 +510,6 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median return ruleError(ErrBlockTooBig, str) } - // Ensure the proof of work bits in the block header is in min/max range - // and the block hash is less than the target value described by the - // bits. - err := checkProofOfWork(block, powLimit, flags) - if err != nil { - return err - } - - // A block timestamp must not have a greater precision than one second. - // This check is necessary because Go time.Time values support - // nanosecond precision whereas the consensus rules only apply to - // seconds and it's much nicer to deal with standard Go time values - // instead of converting to seconds everywhere. - header := &block.MsgBlock().Header - if !header.Timestamp.Equal(time.Unix(header.Timestamp.Unix(), 0)) { - str := fmt.Sprintf("block timestamp of %v has a higher "+ - "precision than one second", header.Timestamp) - return ruleError(ErrInvalidTime, str) - } - - // Ensure the block time is not too far in the future. - maxTimestamp := timeSource.AdjustedTime().Add(time.Second * - MaxTimeOffsetSeconds) - if header.Timestamp.After(maxTimestamp) { - str := fmt.Sprintf("block timestamp of %v is too far in the "+ - "future", header.Timestamp) - return ruleError(ErrTimeTooNew, str) - } - // The first transaction in a block must be a coinbase. transactions := block.Transactions() if !IsCoinBase(transactions[0]) { @@ -574,10 +589,162 @@ func CheckBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median return checkBlockSanity(block, powLimit, timeSource, BFNone) } -// ExtractCoinbaseHeight attempts to extract the height of the block -// from the scriptSig of a coinbase transaction. Coinbase heights -// are only present in blocks of version 2 or later. This was added as part of -// BIP0034. +// checkBlockHeaderContext peforms several validation checks on the block header +// which depend on its position within the block chain. +// +// The flags modify the behavior of this function as follows: +// - BFFastAdd: All checks except those involving comparing the header against +// the checkpoints are not performed. +func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error { + // The genesis block is valid by definition. + if prevNode == nil { + return nil + } + + fastAdd := flags&BFFastAdd == BFFastAdd + if !fastAdd { + // Ensure the difficulty specified in the block header matches + // the calculated difficulty based on the previous block and + // difficulty retarget rules. + expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode, + header.Timestamp) + if err != nil { + return err + } + blockDifficulty := header.Bits + if blockDifficulty != expectedDifficulty { + str := "block difficulty of %d is not the expected value of %d" + str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty) + return ruleError(ErrUnexpectedDifficulty, str) + } + + // Ensure the timestamp for the block header is after the + // median time of the last several blocks (medianTimeBlocks). + medianTime, err := b.calcPastMedianTime(prevNode) + if err != nil { + log.Errorf("calcPastMedianTime: %v", err) + return err + } + if !header.Timestamp.After(medianTime) { + str := "block timestamp of %v is not after expected %v" + str = fmt.Sprintf(str, header.Timestamp, medianTime) + return ruleError(ErrTimeTooOld, str) + } + } + + // The height of this block is one more than the referenced previous + // block. + blockHeight := prevNode.height + 1 + + // Ensure chain matches up to predetermined checkpoints. + blockHash := header.BlockSha() + if !b.verifyCheckpoint(blockHeight, &blockHash) { + str := fmt.Sprintf("block at height %d does not match "+ + "checkpoint hash", blockHeight) + return ruleError(ErrBadCheckpoint, str) + } + + // Find the previous checkpoint and prevent blocks which fork the main + // chain before it. This prevents storage of new, otherwise valid, + // blocks which build off of old blocks that are likely at a much easier + // difficulty and therefore could be used to waste cache and disk space. + checkpointBlock, err := b.findPreviousCheckpoint() + if err != nil { + return err + } + if checkpointBlock != nil && blockHeight < checkpointBlock.Height() { + str := fmt.Sprintf("block at height %d forks the main chain "+ + "before the previous checkpoint at height %d", + blockHeight, checkpointBlock.Height()) + return ruleError(ErrForkTooOld, str) + } + + if !fastAdd { + // Reject version 2 blocks once a majority of the network has + // upgraded. This is part of BIP0066. + if header.Version < 3 && b.isMajorityVersion(3, prevNode, + b.chainParams.BlockRejectNumRequired) { + + str := "new blocks with version %d are no longer valid" + str = fmt.Sprintf(str, header.Version) + return ruleError(ErrBlockVersionTooOld, str) + } + + // Reject version 1 blocks once a majority of the network has + // upgraded. This is part of BIP0034. + if header.Version < 2 && b.isMajorityVersion(2, prevNode, + b.chainParams.BlockRejectNumRequired) { + + str := "new blocks with version %d are no longer valid" + str = fmt.Sprintf(str, header.Version) + return ruleError(ErrBlockVersionTooOld, str) + } + } + + return nil +} + +// checkBlockContext peforms several validation checks on the block which depend +// on its position within the block chain. +// +// The flags modify the behavior of this function as follows: +// - BFFastAdd: The transaction are not checked to see if they are finalized +// and the somewhat expensive BIP0034 validation is not performed. +// +// The flags are also passed to checkBlockHeaderContext. See its documentation +// for how the flags modify its behavior. +func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode, flags BehaviorFlags) error { + // The genesis block is valid by definition. + if prevNode == nil { + return nil + } + + // Perform all block header related validation checks. + header := &block.MsgBlock().Header + err := b.checkBlockHeaderContext(header, prevNode, flags) + if err != nil { + return err + } + + fastAdd := flags&BFFastAdd == BFFastAdd + if !fastAdd { + // The height of this block is one more than the referenced + // previous block. + blockHeight := prevNode.height + 1 + + // Ensure all transactions in the block are finalized. + for _, tx := range block.Transactions() { + if !IsFinalizedTransaction(tx, blockHeight, + header.Timestamp) { + + str := fmt.Sprintf("block contains unfinalized "+ + "transaction %v", tx.Sha()) + return ruleError(ErrUnfinalizedTx, str) + } + } + + // Ensure coinbase starts with serialized block heights for + // blocks whose version is the serializedHeightVersion or newer + // once a majority of the network has upgraded. This is part of + // BIP0034. + if ShouldHaveSerializedBlockHeight(header) && + b.isMajorityVersion(serializedHeightVersion, prevNode, + b.chainParams.BlockEnforceNumRequired) { + + coinbaseTx := block.Transactions()[0] + err := checkSerializedHeight(coinbaseTx, blockHeight) + if err != nil { + return err + } + } + } + + return nil +} + +// ExtractCoinbaseHeight attempts to extract the height of the block from the +// scriptSig of a coinbase transaction. Coinbase heights are only present in +// blocks of version 2 or later. This was added as part of BIP0034. func ExtractCoinbaseHeight(coinbaseTx *btcutil.Tx) (int64, error) { sigScript := coinbaseTx.MsgTx().TxIn[0].SignatureScript if len(sigScript) < 1 {