diff --git a/README.md b/README.md index 2c9bf4d2..0860666f 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,6 @@ intentionally causes an error by attempting to process a duplicate block. ## TODO - Increase test coverage -- Add testnet specific rules - Profile and optimize - Expose some APIs for block verification (without actually inserting it) and transaction input lookups diff --git a/difficulty.go b/difficulty.go index c39182ec..4497da13 100644 --- a/difficulty.go +++ b/difficulty.go @@ -190,7 +190,17 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) // Choose the correct proof of work limit for the active network. powLimit := b.netParams().powLimit - // TODO(davec): Testnet has special rules. + // The test network rules allow minimum difficulty blocks after more + // than twice the desired amount of time needed to generate a block has + // elapsed. + switch b.btcnet { + case btcwire.TestNet: + fallthrough + case btcwire.TestNet3: + if durationVal > int64(targetSpacing)*2 { + return BigToCompact(powLimit) + } + } // Since easier difficulty equates to higher numbers, the easiest // difficulty for a given duration is the largest value possible given @@ -210,6 +220,36 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) return BigToCompact(newTarget) } +// findPrevTestNetDifficulty returns the difficulty of the previous block which +// did not have the special testnet minimum difficulty rule applied. +func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) (uint32, error) { + // Search backwards through the chain for the last block without + // the special rule applied. + powLimitBits := BigToCompact(b.netParams().powLimit) + iterNode := startNode + for iterNode != nil && iterNode.height%blocksPerRetarget != 0 && iterNode.bits == powLimitBits { + // Get the previous block node. This function is used over + // simply accessing iterNode.parent directly as it will + // dynamically create previous block nodes as needed. This + // helps allow only the pieces of the chain that are needed + // to remain in memory. + var err error + iterNode, err = b.getPrevNodeFromNode(iterNode) + if err != nil { + log.Errorf("getPrevNodeFromNode: %v", err) + return 0, err + } + } + + // Return the found difficulty or the minimum difficulty if no + // appropriate block was found. + lastBits := powLimitBits + if iterNode != nil { + lastBits = iterNode.bits + } + return lastBits, nil +} + // calcNextRequiredDifficulty calculates the required difficulty for the block // after the passed previous block node based on the difficulty retarget rules. func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, block *btcutil.Block) (uint32, error) { @@ -224,8 +264,40 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, block *btcu // Return the previous block's difficulty requirements if this block // is not at a difficulty retarget interval. if (lastNode.height+1)%blocksPerRetarget != 0 { - // TODO(davec): Testnet has special rules. - return lastNode.bits, nil + // The difficulty rules differ between networks. + switch b.btcnet { + // The test network rules allow minimum difficulty blocks after + // more than twice the desired amount of time needed to generate + // a block has elapsed. + case btcwire.TestNet: + fallthrough + case btcwire.TestNet3: + // Return minimum difficulty when more than twice the + // desired amount of time needed to generate a block has + // elapsed. + newBlockTime := block.MsgBlock().Header.Timestamp + allowMinTime := lastNode.timestamp.Add(targetSpacing * 2) + if newBlockTime.After(allowMinTime) { + return BigToCompact(powLimit), nil + } + + // The block was mined within the desired timeframe, so + // return the difficulty for the last block which did + // not have the special minimum difficulty rule applied. + prevBits, err := b.findPrevTestNetDifficulty(lastNode) + if err != nil { + return 0, err + } + return prevBits, nil + + // For the main network (or any unrecognized networks), simply + // return the previous block's difficulty. + case btcwire.MainNet: + fallthrough + default: + // Return the previous block's difficulty requirements. + return lastNode.bits, nil + } } // Get the block node at the previous retarget (targetTimespan days diff --git a/test_coverage.txt b/test_coverage.txt index ac6ba4c3..ae333a52 100644 --- a/test_coverage.txt +++ b/test_coverage.txt @@ -2,33 +2,33 @@ github.com/conformal/btcchain/chain.go BlockChain.removeOrphanBlock 100.00% (12/12) github.com/conformal/btcchain/chain.go BlockChain.getOrphanRoot 100.00% (7/7) github.com/conformal/btcchain/checkpoints.go init 100.00% (6/6) -github.com/conformal/btcchain/difficulty.go ShaHashToBig 100.00% (5/5) github.com/conformal/btcchain/merkle.go hashMerkleBranches 100.00% (5/5) -github.com/conformal/btcchain/merkle.go nextPowerOfTwo 100.00% (4/4) +github.com/conformal/btcchain/difficulty.go ShaHashToBig 100.00% (5/5) github.com/conformal/btcchain/chain.go newBlockNode 100.00% (4/4) +github.com/conformal/btcchain/merkle.go nextPowerOfTwo 100.00% (4/4) github.com/conformal/btcchain/difficulty.go calcWork 100.00% (3/3) github.com/conformal/btcchain/process.go BlockChain.blockExists 100.00% (3/3) github.com/conformal/btcchain/chain.go New 100.00% (2/2) github.com/conformal/btcchain/checkpoints.go newShaHashFromStr 100.00% (2/2) +github.com/conformal/btcchain/timesorter.go timeSorter.Less 100.00% (1/1) github.com/conformal/btcchain/validate.go calcBlockSubsidy 100.00% (1/1) github.com/conformal/btcchain/timesorter.go timeSorter.Swap 100.00% (1/1) github.com/conformal/btcchain/checkpoints.go BlockChain.DisableCheckpoints 100.00% (1/1) -github.com/conformal/btcchain/timesorter.go timeSorter.Less 100.00% (1/1) github.com/conformal/btcchain/timesorter.go timeSorter.Len 100.00% (1/1) -github.com/conformal/btcchain/log.go init 100.00% (1/1) github.com/conformal/btcchain/log.go DisableLog 100.00% (1/1) +github.com/conformal/btcchain/log.go init 100.00% (1/1) github.com/conformal/btcchain/merkle.go BuildMerkleTreeStore 94.12% (16/17) github.com/conformal/btcchain/chain.go BlockChain.getReorganizeNodes 92.86% (13/14) github.com/conformal/btcchain/process.go BlockChain.processOrphans 91.67% (11/12) github.com/conformal/btcchain/txlookup.go disconnectTransactions 90.91% (10/11) github.com/conformal/btcchain/txlookup.go BlockChain.fetchTxList 88.57% (31/35) github.com/conformal/btcchain/scriptval.go validateAllTxIn 87.88% (29/33) -github.com/conformal/btcchain/chain.go BlockChain.calcPastMedianTime 87.50% (14/16) github.com/conformal/btcchain/scriptval.go checkBlockScripts 87.50% (7/8) github.com/conformal/btcchain/chain.go BlockChain.connectBestChain 86.96% (20/23) github.com/conformal/btcchain/validate.go countSigOps 86.67% (13/15) github.com/conformal/btcchain/chain.go BlockChain.connectBlock 83.33% (10/12) github.com/conformal/btcchain/validate.go isCoinBase 83.33% (5/6) +github.com/conformal/btcchain/chain.go BlockChain.calcPastMedianTime 82.35% (14/17) github.com/conformal/btcchain/chain.go BlockChain.reorganizeChain 80.77% (21/26) github.com/conformal/btcchain/chain.go BlockChain.isMajorityVersion 80.00% (8/10) github.com/conformal/btcchain/txlookup.go BlockChain.fetchInputTransactions 78.26% (18/23) @@ -51,17 +51,18 @@ github.com/conformal/btcchain/validate.go BlockChain.checkBIP0030 57.14% (8/1 github.com/conformal/btcchain/chain.go BlockChain.loadBlockNode 50.00% (11/22) github.com/conformal/btcchain/checkpoints.go BlockChain.LatestCheckpoint 50.00% (2/4) github.com/conformal/btcchain/notifications.go BlockChain.sendNotification 50.00% (2/4) -github.com/conformal/btcchain/accept.go BlockChain.maybeAcceptBlock 49.23% (32/65) +github.com/conformal/btcchain/accept.go BlockChain.maybeAcceptBlock 46.38% (32/69) github.com/conformal/btcchain/chain.go BlockChain.getPrevNodeFromNode 33.33% (4/12) github.com/conformal/btcchain/checkpoints.go BlockChain.verifyCheckpoint 33.33% (2/6) github.com/conformal/btcchain/validate.go isFinalizedTransaction 23.08% (3/13) github.com/conformal/btcchain/checkpoints.go BlockChain.findLatestKnownCheckpoint 18.18% (2/11) -github.com/conformal/btcchain/difficulty.go BlockChain.calcNextRequiredDifficulty 13.79% (4/29) +github.com/conformal/btcchain/difficulty.go BlockChain.calcNextRequiredDifficulty 15.00% (6/40) github.com/conformal/btcchain/checkpoints.go BlockChain.IsCheckpointCandidate 0.00% (0/32) github.com/conformal/btcchain/validate.go countP2SHSigOps 0.00% (0/26) github.com/conformal/btcchain/difficulty.go BigToCompact 0.00% (0/16) +github.com/conformal/btcchain/difficulty.go BlockChain.calcEasiestDifficulty 0.00% (0/14) github.com/conformal/btcchain/validate.go checkSerializedHeight 0.00% (0/12) -github.com/conformal/btcchain/difficulty.go BlockChain.calcEasiestDifficulty 0.00% (0/10) +github.com/conformal/btcchain/difficulty.go BlockChain.findPrevTestNetDifficulty 0.00% (0/12) github.com/conformal/btcchain/chain.go removeChildNode 0.00% (0/8) github.com/conformal/btcchain/log.go SetLogWriter 0.00% (0/7) github.com/conformal/btcchain/checkpoints.go isNonstandardTransaction 0.00% (0/5) @@ -69,10 +70,10 @@ github.com/conformal/btcchain/checkpoints.go BlockChain.checkpointData 0.00% github.com/conformal/btcchain/validate.go isTransactionSpent 0.00% (0/4) github.com/conformal/btcchain/notifications.go NotificationType.String 0.00% (0/3) github.com/conformal/btcchain/chain.go addChildrenWork 0.00% (0/3) -github.com/conformal/btcchain/log.go UseLogger 0.00% (0/1) -github.com/conformal/btcchain/chain.go BlockChain.DisableVerify 0.00% (0/1) github.com/conformal/btcchain/process.go RuleError.Error 0.00% (0/1) -github.com/conformal/btcchain/log.go newLogClosure 0.00% (0/1) +github.com/conformal/btcchain/chain.go BlockChain.DisableVerify 0.00% (0/1) +github.com/conformal/btcchain/log.go UseLogger 0.00% (0/1) github.com/conformal/btcchain/log.go logClosure.String 0.00% (0/1) -github.com/conformal/btcchain ------------------------------------- 59.26% (576/972) +github.com/conformal/btcchain/log.go newLogClosure 0.00% (0/1) +github.com/conformal/btcchain ------------------------------------- 57.57% (578/1004)