diff --git a/blockchain/accept.go b/blockchain/accept.go index a11fc94d6..897c4ddbf 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -5,6 +5,8 @@ package blockchain import ( + "fmt" + "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcutil" ) @@ -22,11 +24,17 @@ import ( func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) { // The height of this block is one more than the referenced previous // block. - blockHeight := int32(0) - prevNode := b.index.LookupNode(&block.MsgBlock().Header.PrevBlock) - if prevNode != nil { - blockHeight = prevNode.height + 1 + prevHash := &block.MsgBlock().Header.PrevBlock + prevNode := b.index.LookupNode(prevHash) + if prevNode == nil { + str := fmt.Sprintf("previous block %s is unknown", prevHash) + return false, ruleError(ErrPreviousBlockUnknown, str) + } else if prevNode.KnownInvalid() { + str := fmt.Sprintf("previous block %s is known to be invalid", prevHash) + return false, ruleError(ErrInvalidAncestorBlock, str) } + + blockHeight := prevNode.height + 1 block.SetHeight(blockHeight) // The block must pass all of the validation rules which depend on the @@ -56,6 +64,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) // block chain (could be either a side chain or the main chain). blockHeader := &block.MsgBlock().Header newNode := newBlockNode(blockHeader, blockHeight) + newNode.status |= statusDataStored if prevNode != nil { newNode.parent = prevNode newNode.height = blockHeight diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index 63d021d8d..8fa1fb99e 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -16,6 +16,29 @@ import ( "github.com/btcsuite/btcd/wire" ) +// blockStatus is a bit field representing the validation state of the block. +type blockStatus byte + +const ( + // statusDataStored indicates that the block's payload is stored on disk. + statusDataStored blockStatus = 1 << iota + + // statusValid indicates that the block has been fully validated. + statusValid + + // statusValidateFailed indicates that the block has failed validation. + statusValidateFailed + + // statusInvalidAncestor indicates that one of the block's ancestors has + // has failed validation, thus the block is also invalid. + statusInvalidAncestor + + // statusNone indicates that the block has no validation state flags set. + // + // NOTE: This must be defined last in order to avoid influencing iota. + statusNone blockStatus = 0 +) + // blockNode represents a block within the block chain and is primarily used to // aid in selecting the best chain to be the main chain. The main chain is // stored into the block database. @@ -49,6 +72,9 @@ type blockNode struct { nonce uint32 timestamp int64 merkleRoot chainhash.Hash + + // status is a bitfield representing the validation state of the block + status blockStatus } // initBlockNode initializes a block node from the given header and height. The @@ -167,6 +193,20 @@ func (node *blockNode) CalcPastMedianTime() time.Time { return time.Unix(medianTimestamp, 0) } +// KnownValid returns whether the block is known to be valid. This will return +// false for a valid block that has not been fully validated yet. +func (node *blockNode) KnownValid() bool { + return node.status&statusValid != 0 +} + +// KnownInvalid returns whether the block is known to be invalid. This may be +// because the block itself failed validation or any of its ancestors is +// invalid. This will return false for invalid blocks that have not been proven +// invalid yet. +func (node *blockNode) KnownInvalid() bool { + return node.status&(statusValidateFailed|statusInvalidAncestor) != 0 +} + // blockIndex provides facilities for keeping track of an in-memory index of the // block chain. Although the name block chain suggests a single chain of // blocks, it is actually a tree-shaped structure where any node can have diff --git a/blockchain/chain.go b/blockchain/chain.go index e14056583..83bfe883f 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -497,10 +497,14 @@ func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 { // // This function MUST be called with the chain state lock held (for reads). func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List) { - // Nothing to detach or attach if there is no node. attachNodes := list.New() detachNodes := list.New() - if node == nil { + + // Do not reorganize to a known invalid chain. Ancestors deeper than the + // direct parent are checked below but this is a quick check before doing + // more unnecessary work. + if node.parent.KnownInvalid() { + node.status |= statusInvalidAncestor return detachNodes, attachNodes } @@ -509,10 +513,27 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List // so they are attached in the appropriate order when iterating the list // later. forkNode := b.bestChain.FindFork(node) + invalidChain := false for n := node; n != nil && n != forkNode; n = n.parent { + if n.KnownInvalid() { + invalidChain = true + break + } attachNodes.PushFront(n) } + // If any of the node's ancestors are invalid, unwind attachNodes, marking + // each one as invalid for future reference. + if invalidChain { + var next *list.Element + for e := attachNodes.Front(); e != nil; e = next { + next = e.Next() + n := attachNodes.Remove(e).(*blockNode) + n.status |= statusInvalidAncestor + } + return detachNodes, attachNodes + } + // Start from the end of the main chain and work backwards until the // common ancestor adding each block to the list of nodes to detach from // the main chain. @@ -854,8 +875,17 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // at least a couple of ways accomplish that rollback, but both involve // tweaking the chain and/or database. This approach catches these // issues before ever modifying the chain. + var validationError error for e := attachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) + + // If any previous nodes in attachNodes failed validation, + // mark this one as having an invalid ancestor. + if validationError != nil { + n.status |= statusInvalidAncestor + continue + } + var block *btcutil.Block err := b.db.View(func(dbTx database.Tx) error { var err error @@ -869,14 +899,42 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Store the loaded block for later. attachBlocks = append(attachBlocks, block) + // Skip checks if node has already been fully validated. Although + // checkConnectBlock gets skipped, we still need to update the UTXO + // view. + if n.KnownValid() { + err = view.fetchInputUtxos(b.db, block) + if err != nil { + return err + } + err = view.connectTransactions(block, nil) + if err != nil { + return err + } + continue + } + // Notice the spent txout details are not requested here and // thus will not be generated. This is done because the state // is not being immediately written to the database, so it is // not needed. err = b.checkConnectBlock(n, block, view, nil) if err != nil { + // If the block failed validation mark it as invalid, then + // continue to loop through remaining nodes, marking them as + // having an invalid ancestor. + if _, ok := err.(RuleError); ok { + n.status |= statusValidateFailed + validationError = err + continue + } return err } + n.status |= statusValid + } + + if validationError != nil { + return validationError } // Reset the view for the actual connection code below. This is @@ -975,6 +1033,9 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // most common case. parentHash := &block.MsgBlock().Header.PrevBlock if parentHash.IsEqual(&b.bestChain.Tip().hash) { + // Skip checks if node has already been fully validated. + fastAdd = fastAdd || node.KnownValid() + // Perform several checks to verify the block can be connected // to the main chain without violating any rules and without // actually connecting the block. @@ -984,8 +1045,12 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla if !fastAdd { err := b.checkConnectBlock(node, block, view, &stxos) if err != nil { + if _, ok := err.(RuleError); ok { + node.status |= statusValidateFailed + } return false, err } + node.status |= statusValid } // In the fast add case the code to check the block connection diff --git a/blockchain/chainio.go b/blockchain/chainio.go index e4c95666c..70277edf3 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1060,6 +1060,7 @@ func (b *BlockChain) createChainState() error { genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock) header := &genesisBlock.MsgBlock().Header node := newBlockNode(header, 0) + node.status |= statusDataStored | statusValid b.bestChain.SetTip(node) // Add the new node to the index which is used for faster lookups. @@ -1164,6 +1165,7 @@ func (b *BlockChain) initChainState() error { // and add it to the block index. node := &blockNodes[height] initBlockNode(node, header, height) + node.status |= statusDataStored | statusValid if tip != nil { node.parent = tip node.workSum = node.workSum.Add(tip.workSum, diff --git a/blockchain/error.go b/blockchain/error.go index 0f3f2b591..1e7c879ba 100644 --- a/blockchain/error.go +++ b/blockchain/error.go @@ -99,10 +99,6 @@ const ( // transaction. ErrNoTransactions - // ErrTooManyTransactions indicates the block has more transactions than - // are allowed. - ErrTooManyTransactions - // ErrNoTxInputs indicates a transaction does not have any inputs. A // valid transaction must have at least one input. ErrNoTxInputs @@ -213,6 +209,13 @@ const ( // manually computed witness commitment. ErrWitnessCommitmentMismatch + // ErrPreviousBlockUnknown indicates that the previous block is not known. + ErrPreviousBlockUnknown + + // ErrInvalidAncestorBlock indicates that an ancestor of this block has + // already failed validation. + ErrInvalidAncestorBlock + // ErrPrevBlockNotBest indicates that the block's previous block is not the // current chain tip. This is not a block validation rule, but is required // for block proposals submitted via getblocktemplate RPC. @@ -236,7 +239,6 @@ var errorCodeStrings = map[ErrorCode]string{ ErrForkTooOld: "ErrForkTooOld", ErrCheckpointTimeTooOld: "ErrCheckpointTimeTooOld", ErrNoTransactions: "ErrNoTransactions", - ErrTooManyTransactions: "ErrTooManyTransactions", ErrNoTxInputs: "ErrNoTxInputs", ErrNoTxOutputs: "ErrNoTxOutputs", ErrTxTooBig: "ErrTxTooBig", @@ -262,6 +264,8 @@ var errorCodeStrings = map[ErrorCode]string{ ErrUnexpectedWitness: "ErrUnexpectedWitness", ErrInvalidWitnessCommitment: "ErrInvalidWitnessCommitment", ErrWitnessCommitmentMismatch: "ErrWitnessCommitmentMismatch", + ErrPreviousBlockUnknown: "ErrPreviousBlockUnknown", + ErrInvalidAncestorBlock: "ErrInvalidAncestorBlock", ErrPrevBlockNotBest: "ErrPrevBlockNotBest", } diff --git a/blockchain/error_test.go b/blockchain/error_test.go index dda8ad266..c0e56ab89 100644 --- a/blockchain/error_test.go +++ b/blockchain/error_test.go @@ -16,6 +16,7 @@ func TestErrorCodeStringer(t *testing.T) { }{ {ErrDuplicateBlock, "ErrDuplicateBlock"}, {ErrBlockTooBig, "ErrBlockTooBig"}, + {ErrBlockWeightTooHigh, "ErrBlockWeightTooHigh"}, {ErrBlockVersionTooOld, "ErrBlockVersionTooOld"}, {ErrInvalidTime, "ErrInvalidTime"}, {ErrTimeTooOld, "ErrTimeTooOld"}, @@ -28,7 +29,6 @@ func TestErrorCodeStringer(t *testing.T) { {ErrForkTooOld, "ErrForkTooOld"}, {ErrCheckpointTimeTooOld, "ErrCheckpointTimeTooOld"}, {ErrNoTransactions, "ErrNoTransactions"}, - {ErrTooManyTransactions, "ErrTooManyTransactions"}, {ErrNoTxInputs, "ErrNoTxInputs"}, {ErrNoTxOutputs, "ErrNoTxOutputs"}, {ErrTxTooBig, "ErrTxTooBig"}, @@ -52,6 +52,11 @@ func TestErrorCodeStringer(t *testing.T) { {ErrBadCoinbaseHeight, "ErrBadCoinbaseHeight"}, {ErrScriptMalformed, "ErrScriptMalformed"}, {ErrScriptValidation, "ErrScriptValidation"}, + {ErrUnexpectedWitness, "ErrUnexpectedWitness"}, + {ErrInvalidWitnessCommitment, "ErrInvalidWitnessCommitment"}, + {ErrWitnessCommitmentMismatch, "ErrWitnessCommitmentMismatch"}, + {ErrPreviousBlockUnknown, "ErrPreviousBlockUnknown"}, + {ErrInvalidAncestorBlock, "ErrInvalidAncestorBlock"}, {ErrPrevBlockNotBest, "ErrPrevBlockNotBest"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/blockchain/validate.go b/blockchain/validate.go index ba20d7b37..1e9660496 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -482,11 +482,12 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median "any transactions") } - // A block must not have more transactions than the max block payload. - if numTx > wire.MaxBlockPayload { + // A block must not have more transactions than the max block payload or + // else it is certainly over the weight limit. + if numTx > MaxBlockBaseSize { str := fmt.Sprintf("block contains too many transactions - "+ - "got %d, max %d", numTx, wire.MaxBlockPayload) - return ruleError(ErrTooManyTransactions, str) + "got %d, max %d", numTx, MaxBlockBaseSize) + return ruleError(ErrBlockTooBig, str) } // A block must not exceed the maximum allowed block payload when @@ -644,11 +645,6 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error { // // This function MUST be called with the chain state lock held (for writes). 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 @@ -731,11 +727,6 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode // // This function MUST be called with the chain state lock held (for writes). 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) @@ -822,7 +813,7 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode str := fmt.Sprintf("block's weight metric is "+ "too high - got %v, max %v", blockWeight, MaxBlockWeight) - return ruleError(ErrBlockVersionTooOld, str) + return ruleError(ErrBlockWeightTooHigh, str) } } } diff --git a/rpcserver.go b/rpcserver.go index 1033b20f6..0ba2c4bc5 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1969,7 +1969,9 @@ func chainErrToGBTErrString(err error) string { case blockchain.ErrDuplicateBlock: return "duplicate" case blockchain.ErrBlockTooBig: - return "bad-block-size" + return "bad-blk-length" + case blockchain.ErrBlockWeightTooHigh: + return "bad-blk-weight" case blockchain.ErrBlockVersionTooOld: return "bad-version" case blockchain.ErrInvalidTime: @@ -1994,8 +1996,6 @@ func chainErrToGBTErrString(err error) string { return "checkpoint-time-too-old" case blockchain.ErrNoTransactions: return "bad-txns-none" - case blockchain.ErrTooManyTransactions: - return "bad-txns-toomany" case blockchain.ErrNoTxInputs: return "bad-txns-noinputs" case blockchain.ErrNoTxOutputs: @@ -2040,6 +2040,16 @@ func chainErrToGBTErrString(err error) string { return "bad-script-malformed" case blockchain.ErrScriptValidation: return "bad-script-validate" + case blockchain.ErrUnexpectedWitness: + return "unexpected-witness" + case blockchain.ErrInvalidWitnessCommitment: + return "bad-witness-nonce-size" + case blockchain.ErrWitnessCommitmentMismatch: + return "bad-witness-merkle-match" + case blockchain.ErrPreviousBlockUnknown: + return "prev-blk-not-found" + case blockchain.ErrInvalidAncestorBlock: + return "bad-prevblk" case blockchain.ErrPrevBlockNotBest: return "inconclusive-not-best-prvblk" }