diff --git a/accept.go b/accept.go index f9cddb947..fc4887678 100644 --- a/accept.go +++ b/accept.go @@ -16,8 +16,11 @@ import ( // // 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. 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 // if this is the genesis block. @@ -105,7 +108,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) if !fastAdd { // Reject version 1 blocks once a majority of the network has // upgraded. This is part of BIP0034. - if blockHeader.Version == 1 { + if blockHeader.Version < 2 { if b.isMajorityVersion(2, prevNode, b.netParams.BlockV1RejectNumRequired, b.netParams.BlockV1RejectNumToCheck) { @@ -143,9 +146,11 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) // Prune block nodes which are no longer needed before creating // a new node. - err = b.pruneBlockNodes() - if err != nil { - return err + if !dryRun { + err = b.pruneBlockNodes() + if err != nil { + return err + } } // Create a new block node for the block and add it to the in-memory @@ -168,7 +173,9 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) // 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. - b.sendNotification(NTBlockAccepted, block) + if !dryRun { + b.sendNotification(NTBlockAccepted, block) + } return nil } diff --git a/chain.go b/chain.go index b9eb97ad0..312c6b10b 100644 --- a/chain.go +++ b/chain.go @@ -69,7 +69,7 @@ type blockNode struct { inMainChain bool // Some fields from block headers to aid in best chain selection. - version uint32 + version int32 bits uint32 timestamp time.Time } @@ -605,7 +605,7 @@ func (b *BlockChain) pruneBlockNodes() error { // isMajorityVersion determines if a previous number of blocks in the chain // starting with startNode are at least the minimum passed version. -func (b *BlockChain) isMajorityVersion(minVer uint32, startNode *blockNode, numRequired, numToCheck uint64) bool { +func (b *BlockChain) isMajorityVersion(minVer int32, startNode *blockNode, numRequired, numToCheck uint64) bool { numFound := uint64(0) iterNode := startNode for i := uint64(0); i < numToCheck && iterNode != nil; i++ { @@ -811,7 +811,11 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block) erro // disconnected must be in reverse order (think of popping them off // the end of the chain) and nodes the are being attached must be in forwards // order (think pushing them onto the end of the chain). -func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error { +// +// The flags modify the behavior of this function as follows: +// - BFDryRun: Only the checks which ensure the reorganize can be completed +// successfully are performed. The chain is not reorganized. +func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags BehaviorFlags) error { // Ensure all of the needed side chain blocks are in the cache. for e := attachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) @@ -842,6 +846,12 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error } } + // Skip disconnecting and connecting the blocks when running with the + // dry run flag set. + if flags&BFDryRun == BFDryRun { + return nil + } + // Disconnect blocks from the main chain. for e := detachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) @@ -892,8 +902,12 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // The flags modify the behavior of this function as follows: // - BFFastAdd: Avoids the call to checkConnectBlock which does several // expensive transaction validation operations. +// - BFDryRun: Prevents the block from being connected and avoids modifying the +// state of the memory chain index. Also, any log messages related to +// modifying the state are avoided. func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) error { fastAdd := flags&BFFastAdd == BFFastAdd + dryRun := flags&BFDryRun == BFDryRun // We haven't selected a best chain yet or we are extending the main // (best) chain with a new block. This is the most common case. @@ -910,6 +924,11 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla } } + // Don't connect the block if performing a dry run. + if dryRun { + return nil + } + // Connect the block to the main chain. err := b.connectBlock(node, block) if err != nil { @@ -932,7 +951,9 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // become the main chain, but in either case we need the block stored // for future processing, so add the block to the side chain holding // cache. - log.Debugf("Adding block %v to side chain cache", node.hash) + if !dryRun { + log.Debugf("Adding block %v to side chain cache", node.hash) + } b.blockCache[*node.hash] = block b.index[*node.hash] = node @@ -940,9 +961,27 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla node.inMainChain = false node.parent.children = append(node.parent.children, node) + // Remove the block from the side chain cache and disconnect it from the + // parent node when the function returns when running in dry run mode. + if dryRun { + defer func() { + children := node.parent.children + children = removeChildNode(children, node) + node.parent.children = children + + delete(b.index, *node.hash) + delete(b.blockCache, *node.hash) + }() + } + // We're extending (or creating) a side chain, but the cumulative // work for this new side chain is not enough to make it the new chain. if node.workSum.Cmp(b.bestChain.workSum) <= 0 { + // Skip Logging info when the dry run flag is set. + if dryRun { + return nil + } + // Find the fork point. fork := node for ; fork.parent != nil; fork = fork.parent { @@ -975,8 +1014,11 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla detachNodes, attachNodes := b.getReorganizeNodes(node) // Reorganize the chain. - log.Infof("REORGANIZE: Block %v is causing a reorganize.", node.hash) - err := b.reorganizeChain(detachNodes, attachNodes) + if !dryRun { + log.Infof("REORGANIZE: Block %v is causing a reorganize.", + node.hash) + } + err := b.reorganizeChain(detachNodes, attachNodes, flags) if err != nil { return err } diff --git a/error.go b/error.go index 94a5e8aed..974d33c9d 100644 --- a/error.go +++ b/error.go @@ -17,6 +17,10 @@ const ( // exists. ErrDuplicateBlock ErrorCode = iota + // ErrBlockTooBig indicates the serialized block size exceeds the + // maximum allowed size. + ErrBlockTooBig + // ErrBlockVersionTooOld indicates the block version is too old and is // no longer accepted since the majority of the network has upgraded // to a newer version. @@ -67,6 +71,10 @@ 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 @@ -75,6 +83,10 @@ const ( // valid transaction must have at least one output. ErrNoTxOutputs + // ErrTxTooBig indicates a transaction exceeds the maximum allowed size + // when serialized. + ErrTxTooBig + // ErrBadTxOutValue indicates an output value for a transaction is // invalid in some way such as being out of range. ErrBadTxOutValue @@ -167,6 +179,7 @@ const ( // Map of ErrorCode values back to their constant names for pretty printing. var errorCodeStrings = map[ErrorCode]string{ ErrDuplicateBlock: "ErrDuplicateBlock", + ErrBlockTooBig: "ErrBlockTooBig", ErrBlockVersionTooOld: "ErrBlockVersionTooOld", ErrInvalidTime: "ErrInvalidTime", ErrTimeTooOld: "ErrTimeTooOld", @@ -178,8 +191,10 @@ var errorCodeStrings = map[ErrorCode]string{ ErrBadCheckpoint: "ErrBadCheckpoint", ErrForkTooOld: "ErrForkTooOld", ErrNoTransactions: "ErrNoTransactions", + ErrTooManyTransactions: "ErrTooManyTransactions", ErrNoTxInputs: "ErrNoTxInputs", ErrNoTxOutputs: "ErrNoTxOutputs", + ErrTxTooBig: "ErrTxTooBig", ErrBadTxOutValue: "ErrBadTxOutValue", ErrDuplicateTxInputs: "ErrDuplicateTxInputs", ErrBadTxInput: "ErrBadTxInput", diff --git a/error_test.go b/error_test.go index 37bfade8f..cee2ea06e 100644 --- a/error_test.go +++ b/error_test.go @@ -16,6 +16,7 @@ func TestErrorCodeStringer(t *testing.T) { want string }{ {btcchain.ErrDuplicateBlock, "ErrDuplicateBlock"}, + {btcchain.ErrBlockTooBig, "ErrBlockTooBig"}, {btcchain.ErrBlockVersionTooOld, "ErrBlockVersionTooOld"}, {btcchain.ErrInvalidTime, "ErrInvalidTime"}, {btcchain.ErrTimeTooOld, "ErrTimeTooOld"}, @@ -27,8 +28,10 @@ func TestErrorCodeStringer(t *testing.T) { {btcchain.ErrBadCheckpoint, "ErrBadCheckpoint"}, {btcchain.ErrForkTooOld, "ErrForkTooOld"}, {btcchain.ErrNoTransactions, "ErrNoTransactions"}, + {btcchain.ErrTooManyTransactions, "ErrTooManyTransactions"}, {btcchain.ErrNoTxInputs, "ErrNoTxInputs"}, {btcchain.ErrNoTxOutputs, "ErrNoTxOutputs"}, + {btcchain.ErrTxTooBig, "ErrTxTooBig"}, {btcchain.ErrBadTxOutValue, "ErrBadTxOutValue"}, {btcchain.ErrDuplicateTxInputs, "ErrDuplicateTxInputs"}, {btcchain.ErrBadTxInput, "ErrBadTxInput"}, diff --git a/process.go b/process.go index 5d3d0b7e7..540701ddc 100644 --- a/process.go +++ b/process.go @@ -26,6 +26,11 @@ const ( // not be performed. BFNoPoWCheck + // BFDryRun may be set to indicate the block should not modify the chain + // or memory chain index. This is useful to test that a block is valid + // without modifying the current state. + BFDryRun + // BFNone is a convenience value to specifically indicate no flags. BFNone BehaviorFlags = 0 ) @@ -110,6 +115,7 @@ func (b *BlockChain) processOrphans(hash *btcwire.ShaHash, flags BehaviorFlags) // when the error is nil. func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) { fastAdd := flags&BFFastAdd == BFFastAdd + dryRun := flags&BFDryRun == BFDryRun blockHash, err := block.Sha() if err != nil { @@ -179,9 +185,11 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo // Handle orphan blocks. prevHash := &blockHeader.PrevBlock if !prevHash.IsEqual(zeroHash) && !b.blockExists(prevHash) { - log.Infof("Adding orphan block %v with parent %v", blockHash, - prevHash) - b.addOrphanBlock(block) + if !dryRun { + log.Infof("Adding orphan block %v with parent %v", + blockHash, prevHash) + b.addOrphanBlock(block) + } return true, nil } @@ -193,14 +201,18 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo return false, err } - // Accept any orphan blocks that depend on this block (they are no - // longer orphans) and repeat for those accepted blocks until there are - // no more. - err = b.processOrphans(blockHash, flags) - if err != nil { - return false, err + // Don't process any orphans or log when the dry run flag is set. + if !dryRun { + // Accept any orphan blocks that depend on this block (they are + // no longer orphans) and repeat for those accepted blocks until + // there are no more. + err := b.processOrphans(blockHash, flags) + if err != nil { + return false, err + } + + log.Debugf("Accepted block %v", blockHash) } - log.Debugf("Accepted block %v", blockHash) return false, nil } diff --git a/txlookup.go b/txlookup.go index b27734141..de6036ef7 100644 --- a/txlookup.go +++ b/txlookup.go @@ -49,6 +49,9 @@ func connectTransactions(txStore TxStore, block *btcutil.Block) error { originHash := &txIn.PreviousOutpoint.Hash originIndex := txIn.PreviousOutpoint.Index if originTx, exists := txStore[*originHash]; exists { + if originIndex > uint32(len(originTx.Spent)) { + continue + } originTx.Spent[originIndex] = true } } @@ -82,6 +85,9 @@ func disconnectTransactions(txStore TxStore, block *btcutil.Block) error { originIndex := txIn.PreviousOutpoint.Index originTx, exists := txStore[*originHash] if exists && originTx.Tx != nil && originTx.Err == nil { + if originIndex > uint32(len(originTx.Spent)) { + continue + } originTx.Spent[originIndex] = false } } diff --git a/validate.go b/validate.go index c253fd192..127e4672f 100644 --- a/validate.go +++ b/validate.go @@ -189,10 +189,14 @@ func CheckTransactionSanity(tx *btcutil.Tx) error { return ruleError(ErrNoTxOutputs, "transaction has no outputs") } - // NOTE: bitcoind does size limits checking here, but the size limits - // have already been checked by btcwire for incoming transactions. - // Also, btcwire checks the size limits on send too, so there is no need - // to double check it here. + // A transaction must not exceed the maximum allowed block payload when + // serialized. + serializedTxSize := tx.MsgTx().SerializeSize() + if serializedTxSize > btcwire.MaxBlockPayload { + str := fmt.Sprintf("serialized transaction is too big - got "+ + "%d, max %d", serializedTxSize, btcwire.MaxBlockPayload) + return ruleError(ErrTxTooBig, str) + } // Ensure the transaction amounts are in range. Each transaction // output must not be negative or more than the max allowed per @@ -414,10 +418,29 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, txStore TxStore) (int, e // The flags do not modify the behavior of this function directly, however they // are needed to pass along to checkProofOfWork. func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, flags BehaviorFlags) error { - // NOTE: bitcoind does size limits checking here, but the size limits - // have already been checked by btcwire for incoming blocks. Also, - // btcwire checks the size limits on send too, so there is no need - // to double check it here. + // A block must have at least one transaction. + msgBlock := block.MsgBlock() + numTx := len(msgBlock.Transactions) + if numTx == 0 { + return ruleError(ErrNoTransactions, "block does not contain "+ + "any transactions") + } + + // A block must not have more transactions than the max block payload. + if numTx > btcwire.MaxBlockPayload { + str := fmt.Sprintf("block contains too many transactions - "+ + "got %d, max %d", numTx, btcwire.MaxBlockPayload) + return ruleError(ErrTooManyTransactions, str) + } + + // A block must not exceed the maximum allowed block payload when + // serialized. + serializedSize := msgBlock.SerializeSize() + if serializedSize > btcwire.MaxBlockPayload { + str := fmt.Sprintf("serialized block is too big - got %d, "+ + "max %d", serializedSize, btcwire.MaxBlockPayload) + 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 @@ -446,14 +469,8 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, flags BehaviorFla return ruleError(ErrTimeTooNew, str) } - // A block must have at least one transaction. - transactions := block.Transactions() - if len(transactions) == 0 { - return ruleError(ErrNoTransactions, "block does not contain "+ - "any transactions") - } - // The first transaction in a block must be a coinbase. + transactions := block.Transactions() if !IsCoinBase(transactions[0]) { return ruleError(ErrFirstTxNotCoinbase, "first transaction in "+ "block is not a coinbase")