diff --git a/src/chain.h b/src/chain.h --- a/src/chain.h +++ b/src/chain.h @@ -206,7 +206,7 @@ int nVersion; uint256 hashMerkleRoot; unsigned int nTime; - unsigned int nBits; + uint32_t nBits; unsigned int nNonce; //! (memory only) Sequential id assigned to distinguish order in which @@ -217,9 +217,9 @@ unsigned int nTimeMax; void SetNull() { - phashBlock = NULL; - pprev = NULL; - pskip = NULL; + phashBlock = nullptr; + pprev = nullptr; + pskip = nullptr; nHeight = 0; nFile = 0; nDataPos = 0; diff --git a/src/pow.h b/src/pow.h --- a/src/pow.h +++ b/src/pow.h @@ -8,24 +8,23 @@ #include "consensus/params.h" -#include +#include class CBlockHeader; class CBlockIndex; class uint256; -unsigned int GetNextWorkRequired(const CBlockIndex *pindexLast, - const CBlockHeader *pblock, - const Consensus::Params &); -unsigned int CalculateNextWorkRequired(const CBlockIndex *pindexLast, - int64_t nFirstBlockTime, - const Consensus::Params &); +uint32_t GetNextWorkRequired(const CBlockIndex *pindexPrev, + const CBlockHeader *pblock, + const Consensus::Params &); +uint32_t CalculateNextWorkRequired(const CBlockIndex *pindexPrev, + int64_t nFirstBlockTime, + const Consensus::Params &); /** * Check whether a block hash satisfies the proof-of-work requirement specified * by nBits */ -bool CheckProofOfWork(uint256 hash, unsigned int nBits, - const Consensus::Params &); +bool CheckProofOfWork(uint256 hash, uint32_t nBits, const Consensus::Params &); #endif // BITCOIN_POW_H diff --git a/src/pow.cpp b/src/pow.cpp --- a/src/pow.cpp +++ b/src/pow.cpp @@ -10,66 +10,101 @@ #include "primitives/block.h" #include "uint256.h" -unsigned int GetNextWorkRequired(const CBlockIndex *pindexLast, - const CBlockHeader *pblock, - const Consensus::Params ¶ms) { - unsigned int nProofOfWorkLimit = +uint32_t GetNextWorkRequired(const CBlockIndex *pindexPrev, + const CBlockHeader *pblock, + const Consensus::Params ¶ms) { + const uint32_t nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact(); // Genesis block - if (pindexLast == NULL) return nProofOfWorkLimit; + if (pindexPrev == nullptr) { + return nProofOfWorkLimit; + } // Only change once per difficulty adjustment interval - if ((pindexLast->nHeight + 1) % params.DifficultyAdjustmentInterval() != - 0) { - if (params.fPowAllowMinDifficultyBlocks) { - // Special difficulty rule for testnet: - // If the new block's timestamp is more than 2* 10 minutes then - // allow mining of a min-difficulty block. - if (pblock->GetBlockTime() > - pindexLast->GetBlockTime() + params.nPowTargetSpacing * 2) { - return nProofOfWorkLimit; - } - - // Return the last non-special-min-difficulty-rules-block - const CBlockIndex *pindex = pindexLast; - while (pindex->pprev && - pindex->nHeight % params.DifficultyAdjustmentInterval() != - 0 && - pindex->nBits == nProofOfWorkLimit) - pindex = pindex->pprev; - return pindex->nBits; + uint32_t nHeight = pindexPrev->nHeight + 1; + if (nHeight % params.DifficultyAdjustmentInterval() == 0) { + // Go back by what we want to be 14 days worth of blocks + assert(nHeight >= params.DifficultyAdjustmentInterval()); + uint32_t nHeightFirst = nHeight - params.DifficultyAdjustmentInterval(); + const CBlockIndex *pindexFirst = pindexPrev->GetAncestor(nHeightFirst); + assert(pindexFirst); + + return CalculateNextWorkRequired(pindexPrev, + pindexFirst->GetBlockTime(), params); + } + + if (params.fPowAllowMinDifficultyBlocks) { + // Special difficulty rule for testnet: + // If the new block's timestamp is more than 2* 10 minutes then allow + // mining of a min-difficulty block. + if (pblock->GetBlockTime() > + pindexPrev->GetBlockTime() + 2 * params.nPowTargetSpacing) { + return nProofOfWorkLimit; + } + + // Return the last non-special-min-difficulty-rules-block + const CBlockIndex *pindex = pindexPrev; + while (pindex->pprev && + pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && + pindex->nBits == nProofOfWorkLimit) { + pindex = pindex->pprev; } - return pindexLast->nBits; + + return pindex->nBits; + } + + // We can't go bellow the minimum, so early bail. + uint32_t nBits = pindexPrev->nBits; + if (nBits == nProofOfWorkLimit) { + return nProofOfWorkLimit; + } + + // If producing the last 6 block took less than 12h, we keep the same + // difficulty. + const CBlockIndex *pindex6 = pindexPrev->GetAncestor(nHeight - 7); + assert(pindex6); + int64_t mtp6blocks = + pindexPrev->GetMedianTimePast() - pindex6->GetMedianTimePast(); + if (mtp6blocks < 12 * 3600) { + return nBits; } - // Go back by what we want to be 14 days worth of blocks - int nHeightFirst = - pindexLast->nHeight - (params.DifficultyAdjustmentInterval() - 1); - assert(nHeightFirst >= 0); - const CBlockIndex *pindexFirst = pindexLast->GetAncestor(nHeightFirst); - assert(pindexFirst); + // If producing the last 6 block took more than 12h, increase the difficulty + // target by 1/4 (which reduces the difficulty by 20%). This ensure the + // chain do not get stuck in case we lose hashrate abruptly. + arith_uint256 nPow; + nPow.SetCompact(nBits); + nPow += (nPow >> 2); - return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), - params); + // Make sure we do not go bellow allowed values. + const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); + if (nPow > bnPowLimit) nPow = bnPowLimit; + + return nPow.GetCompact(); } -unsigned int CalculateNextWorkRequired(const CBlockIndex *pindexLast, - int64_t nFirstBlockTime, - const Consensus::Params ¶ms) { - if (params.fPowNoRetargeting) return pindexLast->nBits; +uint32_t CalculateNextWorkRequired(const CBlockIndex *pindexPrev, + int64_t nFirstBlockTime, + const Consensus::Params ¶ms) { + if (params.fPowNoRetargeting) { + return pindexPrev->nBits; + } // Limit adjustment step - int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime; - if (nActualTimespan < params.nPowTargetTimespan / 4) + int64_t nActualTimespan = pindexPrev->GetBlockTime() - nFirstBlockTime; + if (nActualTimespan < params.nPowTargetTimespan / 4) { nActualTimespan = params.nPowTargetTimespan / 4; - if (nActualTimespan > params.nPowTargetTimespan * 4) + } + + if (nActualTimespan > params.nPowTargetTimespan * 4) { nActualTimespan = params.nPowTargetTimespan * 4; + } // Retarget const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); arith_uint256 bnNew; - bnNew.SetCompact(pindexLast->nBits); + bnNew.SetCompact(pindexPrev->nBits); bnNew *= nActualTimespan; bnNew /= params.nPowTargetTimespan; @@ -78,7 +113,7 @@ return bnNew.GetCompact(); } -bool CheckProofOfWork(uint256 hash, unsigned int nBits, +bool CheckProofOfWork(uint256 hash, uint32_t nBits, const Consensus::Params ¶ms) { bool fNegative; bool fOverflow; @@ -88,11 +123,14 @@ // Check range if (fNegative || bnTarget == 0 || fOverflow || - bnTarget > UintToArith256(params.powLimit)) + bnTarget > UintToArith256(params.powLimit)) { return false; + } // Check proof of work matches claimed amount - if (UintToArith256(hash) > bnTarget) return false; + if (UintToArith256(hash) > bnTarget) { + return false; + } return true; } diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -79,7 +79,7 @@ std::vector blocks(10000); for (int i = 0; i < 10000; i++) { - blocks[i].pprev = i ? &blocks[i - 1] : NULL; + blocks[i].pprev = i ? &blocks[i - 1] : nullptr; blocks[i].nHeight = i; blocks[i].nTime = 1269211443 + i * params.nPowTargetSpacing; blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */ @@ -98,4 +98,52 @@ } } +static CBlockIndex GetBlockIndex(CBlockIndex *pindexPrev, int64_t nTimeInterval, + uint32_t nBits) { + CBlockIndex block; + block.pprev = pindexPrev; + block.nHeight = pindexPrev->nHeight + 1; + block.nTime = pindexPrev->nTime + nTimeInterval; + block.nBits = nBits; + + return block; +} + +BOOST_AUTO_TEST_CASE(retargeting_test) { + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params ¶ms = Params().GetConsensus(); + + std::vector blocks(1013); + + // Genesis block? + blocks[0] = CBlockIndex(); + blocks[0].nHeight = 0; + blocks[0].nTime = 1269211443; + blocks[0].nBits = 0x207fffff; + + // Pile up some blocks. + for (size_t i = 1; i < 1000; i++) { + blocks[i] = + GetBlockIndex(&blocks[i - 1], params.nPowTargetSpacing, 0x207fffff); + } + + CBlockHeader blkHeaderDummy; + + // We start getting 2h blocks time. For the first 5 blocks, it doesn't + // matter as the MTP is not affected. For the next 5 block, MTP difference + // increases but stays bellow 12h. + for (size_t i = 1000; i < 1010; i++) { + blocks[i] = GetBlockIndex(&blocks[i - 1], 2 * 3600, 0x207fffff); + BOOST_CHECK_EQUAL( + GetNextWorkRequired(&blocks[i], &blkHeaderDummy, params), + 0x207fffff); + } + + // Now we expect the difficulty to decrease. + blocks[1010] = GetBlockIndex(&blocks[1009], 2 * 3600, 0x207fffff); + BOOST_CHECK_EQUAL( + GetNextWorkRequired(&blocks[1010], &blkHeaderDummy, params), + 0x1d00ffff); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3184,32 +3184,38 @@ const Consensus::Params &consensusParams, const CBlockIndex *pindexPrev, int64_t nAdjustedTime) { - const int nHeight = pindexPrev == NULL ? 0 : pindexPrev->nHeight + 1; + const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; + // Check proof of work - if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) + if (block.nBits != + GetNextWorkRequired(pindexPrev, &block, consensusParams)) { return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work"); + } // Check timestamp against prev - if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) + if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) { return state.Invalid(false, REJECT_INVALID, "time-too-old", "block's timestamp is too early"); + } // Check timestamp - if (block.GetBlockTime() > nAdjustedTime + 2 * 60 * 60) + if (block.GetBlockTime() > nAdjustedTime + 2 * 60 * 60) { return state.Invalid(false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); + } // Reject outdated version blocks when 95% (75% on testnet) of the network // has upgraded: // check for version 2, 3 and 4 upgrades if ((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) || (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) || - (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) + (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) { return state.Invalid( false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); + } return true; }