diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 46777f54e..8ebf04c6f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -95,6 +95,7 @@ public: consensus.nPowMaxAdjustDown = 32; // 32% adjustment down consensus.nPowMaxAdjustUp = 16; // 16% adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.nPowAllowMinDifficultyBlocksAfterHeight = boost::none; consensus.vUpgrades[Consensus::BASE_SPROUT].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; @@ -271,6 +272,7 @@ public: consensus.nPowMaxAdjustDown = 32; // 32% adjustment down consensus.nPowMaxAdjustUp = 16; // 16% adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.nPowAllowMinDifficultyBlocksAfterHeight = 299187; consensus.vUpgrades[Consensus::BASE_SPROUT].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; @@ -391,6 +393,7 @@ public: consensus.nPowMaxAdjustDown = 0; // Turn off adjustment down consensus.nPowMaxAdjustUp = 0; // Turn off adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.nPowAllowMinDifficultyBlocksAfterHeight = 0; consensus.vUpgrades[Consensus::BASE_SPROUT].nProtocolVersion = 170002; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; diff --git a/src/consensus/params.h b/src/consensus/params.h index c21607046..26288f243 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -8,6 +8,8 @@ #include "uint256.h" +#include + namespace Consensus { /** @@ -91,6 +93,7 @@ struct Params { NetworkUpgrade vUpgrades[MAX_NETWORK_UPGRADES]; /** Proof of work parameters */ uint256 powLimit; + boost::optional nPowAllowMinDifficultyBlocksAfterHeight; int64_t nPowAveragingWindow; int64_t nPowMaxAdjustDown; int64_t nPowMaxAdjustUp; diff --git a/src/gtest/test_pow.cpp b/src/gtest/test_pow.cpp index ac3ce59e1..4557f6db1 100644 --- a/src/gtest/test_pow.cpp +++ b/src/gtest/test_pow.cpp @@ -68,3 +68,44 @@ TEST(PoW, DifficultyAveraging) { params), GetNextWorkRequired(&blocks[lastBlk], nullptr, params)); } + +TEST(PoW, MinDifficultyRules) { + SelectParams(CBaseChainParams::TESTNET); + const Consensus::Params& params = Params().GetConsensus(); + size_t lastBlk = 2*params.nPowAveragingWindow; + size_t firstBlk = lastBlk - params.nPowAveragingWindow; + + // Start with blocks evenly-spaced and equal difficulty + std::vector blocks(lastBlk+1); + for (int i = 0; i <= lastBlk; i++) { + blocks[i].pprev = i ? &blocks[i - 1] : nullptr; + blocks[i].nHeight = params.nPowAllowMinDifficultyBlocksAfterHeight.get() + i; + blocks[i].nTime = 1269211443 + i * params.nPowTargetSpacing; + blocks[i].nBits = 0x1e7fffff; /* target 0x007fffff000... */ + blocks[i].nChainWork = i ? blocks[i - 1].nChainWork + GetBlockProof(blocks[i - 1]) : arith_uint256(0); + } + + // Create a new block at the target spacing + CBlockHeader next; + next.nTime = blocks[lastBlk].nTime + params.nPowTargetSpacing; + + // Result should be unchanged, modulo integer division precision loss + arith_uint256 bnRes; + bnRes.SetCompact(0x1e7fffff); + bnRes /= params.AveragingWindowTimespan(); + bnRes *= params.AveragingWindowTimespan(); + EXPECT_EQ(GetNextWorkRequired(&blocks[lastBlk], &next, params), bnRes.GetCompact()); + + // Delay last block up to the edge of the min-difficulty limit + next.nTime += params.nPowTargetSpacing * 5; + + // Result should be unchanged, modulo integer division precision loss + EXPECT_EQ(GetNextWorkRequired(&blocks[lastBlk], &next, params), bnRes.GetCompact()); + + // Delay last block over the min-difficulty limit + next.nTime += 1; + + // Result should be the minimum difficulty + EXPECT_EQ(GetNextWorkRequired(&blocks[lastBlk], &next, params), + UintToArith256(params.powLimit).GetCompact()); +} diff --git a/src/miner.cpp b/src/miner.cpp index e22ac2c62..e2e5d2f7e 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -101,6 +101,11 @@ public: void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { pblock->nTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + + // Updating time can change work required on testnet: + if (consensusParams.nPowAllowMinDifficultyBlocksAfterHeight != boost::none) { + pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams); + } } CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) @@ -722,6 +727,11 @@ void static BitcoinMiner() // Update nNonce and nTime pblock->nNonce = ArithToUint256(UintToArith256(pblock->nNonce) + 1); UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); + if (chainparams.GetConsensus().nPowAllowMinDifficultyBlocksAfterHeight != boost::none) + { + // Changing pblock->nTime can change work required on testnet: + hashTarget.SetCompact(pblock->nBits); + } } } } diff --git a/src/pow.cpp b/src/pow.cpp index 18873d4ee..743df08ee 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -24,6 +24,20 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead if (pindexLast == NULL) return nProofOfWorkLimit; + { + // Comparing to pindexLast->nHeight with >= because this function + // returns the work required for the block after pindexLast. + if (params.nPowAllowMinDifficultyBlocksAfterHeight != boost::none && + pindexLast->nHeight >= params.nPowAllowMinDifficultyBlocksAfterHeight.get()) + { + // Special difficulty rule for testnet: + // If the new block's timestamp is more than 6 * 2.5 minutes + // then allow mining of a min-difficulty block. + if (pblock && pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing * 6) + return nProofOfWorkLimit; + } + } + // Find the first block in the averaging interval const CBlockIndex* pindexFirst = pindexLast; arith_uint256 bnTot {0};