diff --git a/accept.go b/accept.go index 8908aa8f..f69bf8be 100644 --- a/accept.go +++ b/accept.go @@ -14,7 +14,11 @@ import ( // 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. -func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error { +// The fastAdd argument modifies the behavior of the function by avoiding the +// somewhat expensive operation: BIP34 validation, it also passes the argument +// down to connectBestChain() + +func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, fastAdd bool) error { // Get a block node for the block previous to this one. Will be nil // if this is the genesis block. prevNode, err := b.getPrevNodeFromBlock(block) @@ -31,43 +35,49 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error { } block.SetHeight(blockHeight) - // Ensure the difficulty specified in the block header matches the - // calculated difficulty based on the previous block and difficulty - // retarget rules. blockHeader := block.MsgBlock().Header - expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode, block) - 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(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(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()) + 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) + 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(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(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(str) + } + } + + } // Ensure chain matches up to predetermined checkpoints. // It's safe to ignore the error on Sha since it's already cached. blockHash, _ := block.Sha() @@ -83,59 +93,66 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error { return RuleError(str) } - // Reject version 1 blocks once a majority of the network has upgraded. - // Rules: - // 95% (950 / 1000) for main network - // 75% (75 / 100) for the test network - // This is part of BIP_0034. - if blockHeader.Version == 1 { - minRequired := uint64(950) - numToCheck := uint64(1000) - if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet { - minRequired = 75 - numToCheck = 100 - } - if b.isMajorityVersion(2, prevNode, minRequired, numToCheck) { - str := "new blocks with version %d are no longer valid" - str = fmt.Sprintf(str, blockHeader.Version) - return RuleError(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. - // Rules: - // 75% (750 / 1000) for main network - // 51% (51 / 100) for the test network - // This is part of BIP_0034. - if blockHeader.Version >= serializedHeightVersion { - minRequired := uint64(750) - numToCheck := uint64(1000) - if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet { - minRequired = 51 - numToCheck = 100 - } - if b.isMajorityVersion(serializedHeightVersion, prevNode, - minRequired, numToCheck) { - - expectedHeight := int64(0) - if prevNode != nil { - expectedHeight = prevNode.height + 1 + if !fastAdd { + // Reject version 1 blocks once a majority of the network has + // upgraded. + // Rules: + // 95% (950 / 1000) for main network + // 75% (75 / 100) for the test network + // This is part of BIP_0034. + if blockHeader.Version == 1 { + minRequired := uint64(950) + numToCheck := uint64(1000) + if b.btcnet == btcwire.TestNet3 || b.btcnet == + btcwire.TestNet { + minRequired = 75 + numToCheck = 100 } - coinbaseTx := block.Transactions()[0] - err := checkSerializedHeight(coinbaseTx, expectedHeight) - if err != nil { - return err + if b.isMajorityVersion(2, prevNode, minRequired, + numToCheck) { + str := "new blocks with version %d are no longer valid" + str = fmt.Sprintf(str, blockHeader.Version) + return RuleError(str) } } - } - // Prune block nodes which are no longer needed before creating a new - // node. - err = b.pruneBlockNodes() - if err != nil { - return err + // Ensure coinbase starts with serialized block heights for + // blocks whose version is the serializedHeightVersion or + // newer once a majority of the network has upgraded. + // Rules: + // 75% (750 / 1000) for main network + // 51% (51 / 100) for the test network + // This is part of BIP_0034. + if blockHeader.Version >= serializedHeightVersion { + minRequired := uint64(750) + numToCheck := uint64(1000) + if b.btcnet == btcwire.TestNet3 || b.btcnet == + btcwire.TestNet { + minRequired = 51 + numToCheck = 100 + } + if b.isMajorityVersion(serializedHeightVersion, + prevNode, minRequired, numToCheck) { + + 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. + err = b.pruneBlockNodes() + if err != nil { + return err + } } // Create a new block node for the block and add it to the in-memory @@ -150,7 +167,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error { // 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. - err = b.connectBestChain(newNode, block) + err = b.connectBestChain(newNode, block, fastAdd) if err != nil { return err } diff --git a/chain.go b/chain.go index 3f078459..44bdf9d2 100644 --- a/chain.go +++ b/chain.go @@ -874,7 +874,10 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // chain. However, it may also be extending (or creating) a side chain (fork) // which may or may not end up becoming the main chain depending on which fork // cumulatively has the most proof of work. -func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block) error { +// The fastAdd argument avoids the call to checkConnectBlock which does +// several expensive transaction validation operations. + +func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fastAdd bool) error { // 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. if b.bestChain == nil || node.parent.hash.IsEqual(b.bestChain.hash) { @@ -883,13 +886,15 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block) err // be necessary to get this node to the main chain) without // violating any rules and without actually connecting the // block. - err := b.checkConnectBlock(node, block) - if err != nil { - return err + if !fastAdd { + err := b.checkConnectBlock(node, block) + if err != nil { + return err + } } // Connect the block to the main chain. - err = b.connectBlock(node, block) + err := b.connectBlock(node, block) if err != nil { return err } @@ -901,6 +906,10 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block) err return nil } + if fastAdd { + bsha, _ := block.Sha() + log.Warnf("fastAdd set in the side chain case? %v\n", bsha) + } // We're extending (or creating) a side chain which may or may not // become the main chain, but in either case we need the block stored diff --git a/chain_test.go b/chain_test.go index 8f1d1050..c167e8c6 100644 --- a/chain_test.go +++ b/chain_test.go @@ -47,7 +47,7 @@ func TestHaveBlock(t *testing.T) { btcchain.TstSetCoinbaseMaturity(1) for i := 1; i < len(blocks); i++ { - err = chain.ProcessBlock(blocks[i]) + err = chain.ProcessBlock(blocks[i], false) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return @@ -55,7 +55,7 @@ func TestHaveBlock(t *testing.T) { } // Insert an orphan block. - if err := chain.ProcessBlock(btcutil.NewBlock(&Block100000)); err != nil { + if err := chain.ProcessBlock(btcutil.NewBlock(&Block100000), false); err != nil { t.Errorf("Unable to process block: %v", err) return } diff --git a/doc.go b/doc.go index 00932b3a..f31a71e4 100644 --- a/doc.go +++ b/doc.go @@ -112,7 +112,7 @@ intentionally causes an error by attempting to process a duplicate block. // Process a block. For this example, we are going to intentionally // cause an error by trying to process the genesis block which already // exists. - err = chain.ProcessBlock(genesisBlock) + err = chain.ProcessBlock(genesisBlock, false) if err != nil { fmt.Printf("Failed to process block: %v\n", err) return diff --git a/process.go b/process.go index 89ad0726..9cdf7be0 100644 --- a/process.go +++ b/process.go @@ -74,7 +74,7 @@ func (b *BlockChain) processOrphans(hash *btcwire.ShaHash) error { i-- // Potentially accept the block into the block chain. - err := b.maybeAcceptBlock(orphan.block) + err := b.maybeAcceptBlock(orphan.block, false) if err != nil { return err } @@ -92,7 +92,7 @@ func (b *BlockChain) processOrphans(hash *btcwire.ShaHash) error { // the block chain. It includes functionality such as rejecting duplicate // blocks, ensuring blocks follow all rules, orphan handling, and insertion into // the block chain along with best chain selection and reorganization. -func (b *BlockChain) ProcessBlock(block *btcutil.Block) error { +func (b *BlockChain) ProcessBlock(block *btcutil.Block, fastAdd bool) error { blockHash, err := block.Sha() if err != nil { return err @@ -138,22 +138,23 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block) error { blockHeader.Timestamp, checkpointTime) return RuleError(str) } - - // Even though the checks prior to now have already ensured the - // proof of work exceeds the claimed amount, the claimed amount - // is a field in the block header which could be forged. This - // check ensures the proof of work is at least the minimum - // expected based on elapsed time since the last checkpoint and - // maximum adjustment allowed by the retarget rules. - duration := blockHeader.Timestamp.Sub(checkpointTime) - requiredTarget := CompactToBig(b.calcEasiestDifficulty( - checkpointHeader.Bits, duration)) - currentTarget := CompactToBig(blockHeader.Bits) - if currentTarget.Cmp(requiredTarget) > 0 { - str := fmt.Sprintf("block target difficulty of %064x "+ - "is too low when compared to the previous "+ - "checkpoint", currentTarget) - return RuleError(str) + if !fastAdd { + // Even though the checks prior to now have already ensured the + // proof of work exceeds the claimed amount, the claimed amount + // is a field in the block header which could be forged. This + // check ensures the proof of work is at least the minimum + // expected based on elapsed time since the last checkpoint and + // maximum adjustment allowed by the retarget rules. + duration := blockHeader.Timestamp.Sub(checkpointTime) + requiredTarget := CompactToBig(b.calcEasiestDifficulty( + checkpointHeader.Bits, duration)) + currentTarget := CompactToBig(blockHeader.Bits) + if currentTarget.Cmp(requiredTarget) > 0 { + str := fmt.Sprintf("block target difficulty of %064x "+ + "is too low when compared to the previous "+ + "checkpoint", currentTarget) + return RuleError(str) + } } } @@ -172,7 +173,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block) error { // The block has passed all context independent checks and appears sane // enough to potentially accept it into the block chain. - err = b.maybeAcceptBlock(block) + err = b.maybeAcceptBlock(block, fastAdd) if err != nil { return err } diff --git a/reorganization_test.go b/reorganization_test.go index dbb902bb..df67d435 100644 --- a/reorganization_test.go +++ b/reorganization_test.go @@ -58,7 +58,7 @@ func TestReorganization(t *testing.T) { btcchain.TstSetCoinbaseMaturity(1) for i := 1; i < len(blocks); i++ { - err = chain.ProcessBlock(blocks[i]) + err = chain.ProcessBlock(blocks[i], false) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return