diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index 68e5701..3b49cd6 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -52,6 +52,7 @@ const uint64_t DIFFICULTY_TARGET = 60; // seconds const uint64_t EXPECTED_NUMBER_OF_BLOCKS_PER_DAY = 24 * 60 * 60 / DIFFICULTY_TARGET; const size_t DIFFICULTY_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks const size_t DIFFICULTY_WINDOW_V2 = 17; // blocks +const size_t DIFFICULTY_WINDOW_V3 = 66; // blocks const size_t DIFFICULTY_CUT = 60; // timestamps to cut after sorting const size_t DIFFICULTY_LAG = 15; // !!! static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Bad DIFFICULTY_WINDOW or DIFFICULTY_CUT"); diff --git a/src/CryptoNoteCore/Blockchain.cpp b/src/CryptoNoteCore/Blockchain.cpp index 7bfc113..e9ebc21 100644 --- a/src/CryptoNoteCore/Blockchain.cpp +++ b/src/CryptoNoteCore/Blockchain.cpp @@ -694,8 +694,11 @@ difficulty_type Blockchain::getDifficultyForNextBlock() { std::vector commulative_difficulties; uint8_t BlockMajorVersion = getBlockMajorVersionForHeight(static_cast(m_blocks.size())); size_t offset; - if (BlockMajorVersion >= BLOCK_MAJOR_VERSION_2) { + if (BlockMajorVersion == BLOCK_MAJOR_VERSION_2) { offset = m_blocks.size() - std::min(m_blocks.size(), static_cast(m_currency.difficultyBlocksCount2())); + } + else if (BlockMajorVersion >= BLOCK_MAJOR_VERSION_3) { + offset = m_blocks.size() - std::min(m_blocks.size(), static_cast(m_currency.difficultyBlocksCount3())); } else { offset = m_blocks.size() - std::min(m_blocks.size(), static_cast(m_currency.difficultyBlocksCount())); @@ -835,7 +838,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: std::vector commulative_difficulties; uint8_t BlockMajorVersion = getBlockMajorVersionForHeight(static_cast(m_blocks.size())); - if (BlockMajorVersion >= BLOCK_MAJOR_VERSION_2) { + if (BlockMajorVersion == BLOCK_MAJOR_VERSION_2) { if (alt_chain.size() < m_currency.difficultyBlocksCount2()) { std::lock_guard lk(m_blockchain_lock); @@ -875,6 +878,47 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: } } + } + else if (BlockMajorVersion >= BLOCK_MAJOR_VERSION_3) { + + if (alt_chain.size() < m_currency.difficultyBlocksCount3()) { + std::lock_guard lk(m_blockchain_lock); + size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; + size_t main_chain_count = m_currency.difficultyBlocksCount3() - std::min(m_currency.difficultyBlocksCount3(), alt_chain.size()); + main_chain_count = std::min(main_chain_count, main_chain_stop_offset); + size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count; + + if (!main_chain_start_offset) + ++main_chain_start_offset; //skip genesis block + for (; main_chain_start_offset < main_chain_stop_offset; ++main_chain_start_offset) { + timestamps.push_back(m_blocks[main_chain_start_offset].bl.timestamp); + commulative_difficulties.push_back(m_blocks[main_chain_start_offset].cumulative_difficulty); + } + + if (!((alt_chain.size() + timestamps.size()) <= m_currency.difficultyBlocksCount3())) { + logger(ERROR, BRIGHT_RED) << "Internal error, alt_chain.size()[" << alt_chain.size() << "] + timestamps.size()[" << timestamps.size() << + "] NOT <= m_currency.difficultyBlocksCount()[" << m_currency.difficultyBlocksCount3() << ']'; return false; + } + for (auto it : alt_chain) { + timestamps.push_back(it->second.bl.timestamp); + commulative_difficulties.push_back(it->second.cumulative_difficulty); + } + } + else { + timestamps.resize(std::min(alt_chain.size(), m_currency.difficultyBlocksCount3())); + commulative_difficulties.resize(std::min(alt_chain.size(), m_currency.difficultyBlocksCount3())); + size_t count = 0; + size_t max_i = timestamps.size() - 1; + BOOST_REVERSE_FOREACH(auto it, alt_chain) { + timestamps[max_i - count] = it->second.bl.timestamp; + commulative_difficulties[max_i - count] = it->second.cumulative_difficulty; + count++; + if (count >= m_currency.difficultyBlocksCount3()) { + break; + } + } + } + } else { diff --git a/src/CryptoNoteCore/Currency.cpp b/src/CryptoNoteCore/Currency.cpp index 6900bf6..31b9036 100755 --- a/src/CryptoNoteCore/Currency.cpp +++ b/src/CryptoNoteCore/Currency.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers -// Copyright (c) 2016, The Karbowanec developers +// Copyright (c) 2016-2018 zawy12 +// Copyright (c) 2016-2018, The Karbowanec developers // // This file is part of Bytecoin. // @@ -405,108 +406,172 @@ namespace CryptoNote { difficulty_type Currency::nextDifficulty(uint8_t blockMajorVersion, std::vector timestamps, std::vector cumulativeDifficulties) const { - // new difficulty calculation + if (blockMajorVersion >= BLOCK_MAJOR_VERSION_3) { + return nextDifficultyV3(timestamps, cumulativeDifficulties); + } + else if (blockMajorVersion == BLOCK_MAJOR_VERSION_2) { + return nextDifficultyV2(timestamps, cumulativeDifficulties); + } + else { + return nextDifficultyV1(timestamps, cumulativeDifficulties); + } + } + + difficulty_type Currency::nextDifficultyV1(std::vector timestamps, + std::vector cumulativeDifficulties) const { + assert(m_difficultyWindow >= 2); + + if (timestamps.size() > m_difficultyWindow) { + timestamps.resize(m_difficultyWindow); + cumulativeDifficulties.resize(m_difficultyWindow); + } + + size_t length = timestamps.size(); + assert(length == cumulativeDifficulties.size()); + assert(length <= m_difficultyWindow); + if (length <= 1) { + return 1; + } + + sort(timestamps.begin(), timestamps.end()); + + size_t cutBegin, cutEnd; + assert(2 * m_difficultyCut <= m_difficultyWindow - 2); + if (length <= m_difficultyWindow - 2 * m_difficultyCut) { + cutBegin = 0; + cutEnd = length; + } + else { + cutBegin = (length - (m_difficultyWindow - 2 * m_difficultyCut) + 1) / 2; + cutEnd = cutBegin + (m_difficultyWindow - 2 * m_difficultyCut); + } + assert(/*cut_begin >= 0 &&*/ cutBegin + 2 <= cutEnd && cutEnd <= length); + uint64_t timeSpan = timestamps[cutEnd - 1] - timestamps[cutBegin]; + if (timeSpan == 0) { + timeSpan = 1; + } + + difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1] - cumulativeDifficulties[cutBegin]; + assert(totalWork > 0); + + uint64_t low, high; + low = mul128(totalWork, m_difficultyTarget, &high); + if (high != 0 || low + timeSpan - 1 < low) { + return 0; + } + + return (low + timeSpan - 1) / timeSpan; + } + + difficulty_type Currency::nextDifficultyV2(std::vector timestamps, + std::vector cumulativeDifficulties) const { + + // Difficulty calculation v. 2 // based on Zawy difficulty algorithm v1.0 // next Diff = Avg past N Diff * TargetInterval / Avg past N solve times // as described at https://github.com/monero-project/research-lab/issues/3 - // Window time span and total difficulty is taken instead of average as suggested by Eugene + // Window time span and total difficulty is taken instead of average as suggested by Nuclear_chaos - if (blockMajorVersion >= BLOCK_MAJOR_VERSION_2) { + size_t m_difficultyWindow_2 = CryptoNote::parameters::DIFFICULTY_WINDOW_V2; + assert(m_difficultyWindow_2 >= 2); - size_t m_difficultyWindow_2 = CryptoNote::parameters::DIFFICULTY_WINDOW_V2; - assert(m_difficultyWindow_2 >= 2); + if (timestamps.size() > m_difficultyWindow_2) { + timestamps.resize(m_difficultyWindow_2); + cumulativeDifficulties.resize(m_difficultyWindow_2); + } - if (timestamps.size() > m_difficultyWindow_2) { - timestamps.resize(m_difficultyWindow_2); - cumulativeDifficulties.resize(m_difficultyWindow_2); - } + size_t length = timestamps.size(); + assert(length == cumulativeDifficulties.size()); + assert(length <= m_difficultyWindow_2); + if (length <= 1) { + return 1; + } - size_t length = timestamps.size(); - assert(length == cumulativeDifficulties.size()); - assert(length <= m_difficultyWindow_2); - if (length <= 1) { - return 1; - } + sort(timestamps.begin(), timestamps.end()); - sort(timestamps.begin(), timestamps.end()); + uint64_t timeSpan = timestamps.back() - timestamps.front(); + if (timeSpan == 0) { + timeSpan = 1; + } - uint64_t timeSpan = timestamps.back() - timestamps.front(); - if (timeSpan == 0) { - timeSpan = 1; - } + difficulty_type totalWork = cumulativeDifficulties.back() - cumulativeDifficulties.front(); + assert(totalWork > 0); - difficulty_type totalWork = cumulativeDifficulties.back() - cumulativeDifficulties.front(); - assert(totalWork > 0); + // uint64_t nextDiffZ = totalWork * m_difficultyTarget / timeSpan; - // uint64_t nextDiffZ = totalWork * m_difficultyTarget / timeSpan; + uint64_t low, high; + low = mul128(totalWork, m_difficultyTarget, &high); + // blockchain error "Difficulty overhead" if this function returns zero + if (high != 0) { + return 0; + } - uint64_t low, high; - low = mul128(totalWork, m_difficultyTarget, &high); - // blockchain error "Difficulty overhead" if this function returns zero - if (high != 0) { - return 0; - } + uint64_t nextDiffZ = low / timeSpan; - uint64_t nextDiffZ = low / timeSpan; + // minimum limit + if (nextDiffZ < 1) { + nextDiffZ = 1; + } - // minimum limit - if (nextDiffZ <= 100000) { - nextDiffZ = 100000; - } + return nextDiffZ; + } - return nextDiffZ; + difficulty_type Currency::nextDifficultyV3(std::vector timestamps, + std::vector cumulativeDifficulties) const { - // end of new difficulty calculation + // WHM difficulty algorithm by Zawy + // https://github.com/zawy12/difficulty-algorithms/issues/3 + // Linearly - weighted moving average of the solvetimes. + // This is an improved version of Tom Harding's (Deger8) "WT-144". + // This is a basic average that gives more weight to the most recent blocks so it estimates + // *current* difficulty by taking a kind of "slope" into account. A simple average is just + // an estimate of the correct difficulty as it was N / 2 blocks in the past. - } else { + int64_t T = static_cast(m_difficultyTarget); + size_t N = CryptoNote::parameters::DIFFICULTY_WINDOW_V3; - // old difficulty calculation + if (timestamps.size() > N) { + timestamps.resize(N); + cumulativeDifficulties.resize(N); + } + size_t length = timestamps.size(); + assert(length == cumulativeDifficulties.size()); + assert(length <= N); + if (length <= 1) + return 1; - assert(m_difficultyWindow >= 2); + // To get a more accurate solvetime to within + / -~0.2%, use an adjustment factor. + const double_t adjust = pow(0.9989, 500 / N); + const uint64_t k = static_cast((N + 1) / 2 * adjust * T); - if (timestamps.size() > m_difficultyWindow) { - timestamps.resize(m_difficultyWindow); - cumulativeDifficulties.resize(m_difficultyWindow); - } + int64_t t = 0, j = 0; // weighted solve times + int64_t solveTime(0); - size_t length = timestamps.size(); - assert(length == cumulativeDifficulties.size()); - assert(length <= m_difficultyWindow); - if (length <= 1) { - return 1; - } + for (int64_t i = 1; i <= length; i++) { // off-by-one if not <= instead of < + solveTime = static_cast(timestamps[i]) - static_cast(timestamps[i - 1]); + //solveTime = std::min((T * 6), std::max(solveTime, (-5 * T))); + t += solveTime * i; + } - sort(timestamps.begin(), timestamps.end()); + // Keep t reasonable in case strange solvetimes occurred in an unforeseeable way. + if (t < N * (N + 1) / 2 * T / 4) + t = N * (N + 1) / 2 * T / 4; - size_t cutBegin, cutEnd; - assert(2 * m_difficultyCut <= m_difficultyWindow - 2); - if (length <= m_difficultyWindow - 2 * m_difficultyCut) { - cutBegin = 0; - cutEnd = length; - } - else { - cutBegin = (length - (m_difficultyWindow - 2 * m_difficultyCut) + 1) / 2; - cutEnd = cutBegin + (m_difficultyWindow - 2 * m_difficultyCut); - } - assert(/*cut_begin >= 0 &&*/ cutBegin + 2 <= cutEnd && cutEnd <= length); - uint64_t timeSpan = timestamps[cutEnd - 1] - timestamps[cutBegin]; - if (timeSpan == 0) { - timeSpan = 1; - } + difficulty_type totalWork = cumulativeDifficulties.back() - cumulativeDifficulties.front(); + difficulty_type previousDifficulty = cumulativeDifficulties.back() - cumulativeDifficulties.end()[-2]; + assert(totalWork > 0); - difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1] - cumulativeDifficulties[cutBegin]; - assert(totalWork > 0); + uint64_t low, high, nextDifficulty; + low = mul128(totalWork, k, &high); + if (high != 0) + return 0; - uint64_t low, high; - low = mul128(totalWork, m_difficultyTarget, &high); - if (high != 0 || low + timeSpan - 1 < low) { - return 0; - } + nextDifficulty = low / t; - return (low + timeSpan - 1) / timeSpan; - // end of old difficulty calculation - } + // A symmetric 15 % limit on how fast it rises or falls + nextDifficulty = static_cast(std::max(previousDifficulty * 0.85, std::min(nextDifficulty, previousDifficulty / 0.85))); + return nextDifficulty; } bool Currency::checkProofOfWorkV1(Crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, diff --git a/src/CryptoNoteCore/Currency.h b/src/CryptoNoteCore/Currency.h index 58273d1..bb217e6 100755 --- a/src/CryptoNoteCore/Currency.h +++ b/src/CryptoNoteCore/Currency.h @@ -64,6 +64,7 @@ class Currency { size_t difficultyCut() const { return m_difficultyCut; } size_t difficultyBlocksCount() const { return m_difficultyWindow + m_difficultyLag; } size_t difficultyBlocksCount2() const { return CryptoNote::parameters::DIFFICULTY_WINDOW_V2; } + size_t difficultyBlocksCount3() const { return CryptoNote::parameters::DIFFICULTY_WINDOW_V3; } size_t maxBlockSizeInitial() const { return m_maxBlockSizeInitial; } uint64_t maxBlockSizeGrowthSpeedNumerator() const { return m_maxBlockSizeGrowthSpeedNumerator; } @@ -121,6 +122,9 @@ class Currency { bool parseAmount(const std::string& str, uint64_t& amount) const; difficulty_type nextDifficulty(uint8_t blockMajorVersion, std::vector timestamps, std::vector Difficulties) const; + difficulty_type nextDifficultyV1(std::vector timestamps, std::vector Difficulties) const; + difficulty_type nextDifficultyV2(std::vector timestamps, std::vector Difficulties) const; + difficulty_type nextDifficultyV3(std::vector timestamps, std::vector Difficulties) const; bool checkProofOfWorkV1(Crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, Crypto::Hash& proofOfWork) const; bool checkProofOfWorkV2(Crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, Crypto::Hash& proofOfWork) const;