From 53068fb220ad8ee4a73373f8d8db62ab70b4ee50 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Sat, 22 May 2021 14:10:45 +0100 Subject: [PATCH] Introduce Dogecoin difficulty calculations --- src/Makefile.am | 2 + src/Makefile.test.include | 1 + src/dogecoin.cpp | 78 +++++++++++++++++++++++++++ src/dogecoin.h | 14 +++++ src/pow.cpp | 33 +++++++++--- src/test/dogecoin_tests.cpp | 104 ++++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 src/dogecoin.cpp create mode 100644 src/dogecoin.h create mode 100644 src/test/dogecoin_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 0331571a1..8e8ce6646 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -134,6 +134,8 @@ BITCOIN_CORE_H = \ core_memusage.h \ cuckoocache.h \ dbwrapper.h \ + dogecoin.cpp \ + dogecoin.h \ flatfile.h \ fs.h \ httprpc.h \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 2b6de6cce..31738769f 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -232,6 +232,7 @@ BITCOIN_TESTS =\ test/cuckoocache_tests.cpp \ test/denialofservice_tests.cpp \ test/descriptor_tests.cpp \ + test/dogecoin_tests.cpp \ test/flatfile_tests.cpp \ test/fs_tests.cpp \ test/getarg_tests.cpp \ diff --git a/src/dogecoin.cpp b/src/dogecoin.cpp new file mode 100644 index 000000000..7af47d5d4 --- /dev/null +++ b/src/dogecoin.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2015-2021 The Dogecoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include + +// Dogecoin: Normally minimum difficulty blocks can only occur in between +// retarget blocks. However, once we introduce Digishield every block is +// a retarget, so we need to handle minimum difficulty on all blocks. +bool AllowDigishieldMinDifficultyForBlock(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params) +{ + // check if the chain allows minimum difficulty blocks + if (!params.fPowAllowMinDifficultyBlocks) + return false; + + // check if the chain allows minimum difficulty blocks on recalc blocks + if (pindexLast->nHeight < 157500) + // if (!params.fPowAllowDigishieldMinDifficultyBlocks) + return false; + + // Allow for a minimum block time if the elapsed time > 2*nTargetSpacing + return (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2); +} + +unsigned int CalculateDogecoinNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params) +{ + if (params.fPowNoRetargeting) + return pindexLast->nBits; + + int nHeight = pindexLast->nHeight + 1; + bool fNewDifficultyProtocol = (nHeight >= 145000); + // bool fNewDifficultyProtocol = (nHeight >= params.GetDigiShieldForkBlock()); + const int64_t nRetargetTimespan = fNewDifficultyProtocol + ? 60 // params.DigiShieldTargetTimespan() + : params.nPowTargetTimespan; + + int64_t nTimespan = pindexLast->GetBlockTime() - nFirstBlockTime; + int64_t nMaxTimespan; + int64_t nMinTimespan; + + if (fNewDifficultyProtocol) //DigiShield implementation - thanks to RealSolid & WDC for this code + { + // amplitude filter - thanks to daft27 for this code + nTimespan = nRetargetTimespan + (nTimespan - nRetargetTimespan) / 8; + + nMinTimespan = nRetargetTimespan - (nRetargetTimespan / 4); + nMaxTimespan = nRetargetTimespan + (nRetargetTimespan / 2); + } else if (nHeight > 10000) { + nMinTimespan = nRetargetTimespan / 4; + nMaxTimespan = nRetargetTimespan * 4; + } else if (nHeight > 5000) { + nMinTimespan = nRetargetTimespan / 8; + nMaxTimespan = nRetargetTimespan * 4; + } else { + nMinTimespan = nRetargetTimespan / 16; + nMaxTimespan = nRetargetTimespan * 4; + } + + // Limit adjustment step + if (nTimespan < nMinTimespan) + nTimespan = nMinTimespan; + else if (nTimespan > nMaxTimespan) + nTimespan = nMaxTimespan; + + // Retarget + const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); + arith_uint256 bnNew; + bnNew.SetCompact(pindexLast->nBits); + bnNew *= nTimespan; + bnNew /= nRetargetTimespan; + + if (bnNew > bnPowLimit) + bnNew = bnPowLimit; + + return bnNew.GetCompact(); +} diff --git a/src/dogecoin.h b/src/dogecoin.h new file mode 100644 index 000000000..134caa629 --- /dev/null +++ b/src/dogecoin.h @@ -0,0 +1,14 @@ +// Copyright (c) 2015-2017 The Dogecoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_DOGECOIN_H +#define BITCOIN_DOGECOIN_H + +#include +#include + +bool AllowDigishieldMinDifficultyForBlock(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params); +unsigned int CalculateDogecoinNextWorkRequired(const CBlockIndex* pindexLast, int64_t nLastRetargetTime, const Consensus::Params& params); + +#endif // BITCOIN_DOGECOIN_H diff --git a/src/pow.cpp b/src/pow.cpp index 70c61e77d..de8ba9be2 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -15,21 +16,39 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead assert(pindexLast != nullptr); unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact(); + // Dogecoin: Special rules for minimum difficulty blocks with Digishield + if (AllowDigishieldMinDifficultyForBlock(pindexLast, pblock, params)) + { + // Special difficulty rule for testnet: + // If the new block's timestamp is more than 2* nTargetSpacing minutes + // then allow mining of a min-difficulty block. + return nProofOfWorkLimit; + } + // Only change once per difficulty adjustment interval - if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0) + bool fNewDifficultyProtocol = (pindexLast->nHeight+1 >= 145000); + const int64_t difficultyAdjustmentInterval = fNewDifficultyProtocol + ? 1 + : params.DifficultyAdjustmentInterval(); + const int64_t powTargetSpacing = fNewDifficultyProtocol + ? 60 + : params.nPowTargetSpacing; + + if ((pindexLast->nHeight+1) % 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) + if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + powTargetSpacing*2) { return nProofOfWorkLimit; + } else { // Return the last non-special-min-difficulty-rules-block const CBlockIndex* pindex = pindexLast; - while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit) + while (pindex->pprev && pindex->nHeight % difficultyAdjustmentInterval != 0 && pindex->nBits == nProofOfWorkLimit) pindex = pindex->pprev; return pindex->nBits; } @@ -39,9 +58,9 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead // Litecoin: This fixes an issue where a 51% attack can change difficulty at will. // Go back the full period unless it's the first retarget after genesis. Code courtesy of Art Forz - int blockstogoback = params.DifficultyAdjustmentInterval()-1; - if ((pindexLast->nHeight+1) != params.DifficultyAdjustmentInterval()) - blockstogoback = params.DifficultyAdjustmentInterval(); + int blockstogoback = difficultyAdjustmentInterval-1; + if ((pindexLast->nHeight+1) != difficultyAdjustmentInterval) + blockstogoback = difficultyAdjustmentInterval; // Go back by what we want to be 14 days worth of blocks int nHeightFirst = pindexLast->nHeight - blockstogoback; @@ -49,7 +68,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst); assert(pindexFirst); - return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params); + return CalculateDogecoinNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params); } unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params) diff --git a/src/test/dogecoin_tests.cpp b/src/test/dogecoin_tests.cpp new file mode 100644 index 000000000..b446ff685 --- /dev/null +++ b/src/test/dogecoin_tests.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2015-2021 The Dogecoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include + +#include + +#include + +BOOST_FIXTURE_TEST_SUITE(dogecoin_tests, TestingSetup) + +BOOST_AUTO_TEST_CASE(get_next_work_difficulty_limit) +{ + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params& params = Params().GetConsensus(); + + CBlockIndex pindexLast; + int64_t nLastRetargetTime = 1386474927; // Block # 1 + + pindexLast.nHeight = 239; + pindexLast.nTime = 1386475638; // Block #239 + pindexLast.nBits = 0x1e0ffff0; + BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1e00ffff); +} + +BOOST_AUTO_TEST_CASE(get_next_work_pre_digishield) +{ + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params& params = Params().GetConsensus(); + + CBlockIndex pindexLast; + int64_t nLastRetargetTime = 1386942008; // Block 9359 + + pindexLast.nHeight = 9599; + pindexLast.nTime = 1386954113; + pindexLast.nBits = 0x1c1a1206; + BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1c15ea59); +} + +BOOST_AUTO_TEST_CASE(get_next_work_digishield) +{ + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params& params = Params().GetConsensus(); + + CBlockIndex pindexLast; + int64_t nLastRetargetTime = 1395094427; + + // First hard-fork at 145,000, which applies to block 145,001 onwards + pindexLast.nHeight = 145000; + pindexLast.nTime = 1395094679; + pindexLast.nBits = 0x1b499dfd; + BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b671062); +} + +BOOST_AUTO_TEST_CASE(get_next_work_digishield_modulated_upper) +{ + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params& params = Params().GetConsensus(); + + CBlockIndex pindexLast; + int64_t nLastRetargetTime = 1395100835; + + // Test the upper bound on modulated time using mainnet block #145,107 + pindexLast.nHeight = 145107; + pindexLast.nTime = 1395101360; + pindexLast.nBits = 0x1b3439cd; + BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b4e56b3); +} + +BOOST_AUTO_TEST_CASE(get_next_work_digishield_modulated_lower) +{ + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params& params = Params().GetConsensus(); + + CBlockIndex pindexLast; + int64_t nLastRetargetTime = 1395380517; + + // Test the lower bound on modulated time using mainnet block #149,423 + pindexLast.nHeight = 149423; + pindexLast.nTime = 1395380447; + pindexLast.nBits = 0x1b446f21; + BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b335358); +} + +BOOST_AUTO_TEST_CASE(get_next_work_digishield_rounding) +{ + SelectParams(CBaseChainParams::MAIN); + const Consensus::Params& params = Params().GetConsensus(); + + CBlockIndex pindexLast; + int64_t nLastRetargetTime = 1395094679; + + // Test case for correct rounding of modulated time - this depends on + // handling of integer division, and is not obvious from the code + pindexLast.nHeight = 145001; + pindexLast.nTime = 1395094727; + pindexLast.nBits = 0x1b671062; + BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b6558a4); +} + +BOOST_AUTO_TEST_SUITE_END()