From 19e30a9e672d6ccc29dc19dafa7a9e1b5621a84a Mon Sep 17 00:00:00 2001 From: aivve Date: Thu, 22 Mar 2018 09:41:20 +0200 Subject: [PATCH] The Simple Difficulty Algorithm (EMA) https://github.com/zawy12/difficulty-algorithms/issues/21 https://github.com/seredat/karbowanec/issues/29#issue-304070070 --- src/CryptoNoteConfig.h | 5 +- src/CryptoNoteCore/Blockchain.cpp | 48 ++++++- src/CryptoNoteCore/Currency.cpp | 204 ++++++++++++++++++------------ src/CryptoNoteCore/Currency.h | 4 + 4 files changed, 177 insertions(+), 84 deletions(-) diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index 4873933b3b8..c74af56a2b2 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -25,12 +25,13 @@ namespace CryptoNote { namespace parameters { +const uint64_t DIFFICULTY_TARGET = 240; // seconds const uint64_t CRYPTONOTE_MAX_BLOCK_NUMBER = 500000000; const size_t CRYPTONOTE_MAX_BLOCK_BLOB_SIZE = 500000000; const size_t CRYPTONOTE_MAX_TX_SIZE = 1000000000; const uint64_t CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 111; // addresses start with "K" const size_t CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW = 10; -const uint64_t CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT = 60 * 60 * 2; +const uint64_t CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT = DIFFICULTY_TARGET * 7; const size_t BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW = 60; @@ -51,10 +52,10 @@ const size_t CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12; const uint64_t MINIMUM_FEE = UINT64_C(100000000); const uint64_t DEFAULT_DUST_THRESHOLD = UINT64_C(0); -const uint64_t DIFFICULTY_TARGET = 240; // 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 = 7; // 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 7bfc113cd6b..e9ebc21a55a 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 05556b34dba..19de41658ad 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. // @@ -407,108 +408,151 @@ namespace CryptoNote { difficulty_type Currency::nextDifficulty(uint8_t blockMajorVersion, std::vector timestamps, std::vector cumulativeDifficulties) const { - // new difficulty calculation - // 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 - - if (blockMajorVersion >= BLOCK_MAJOR_VERSION_2) { + 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); + } + } - size_t m_difficultyWindow_2 = CryptoNote::parameters::DIFFICULTY_WINDOW_V2; - assert(m_difficultyWindow_2 >= 2); + difficulty_type Currency::nextDifficultyV1(std::vector timestamps, + std::vector cumulativeDifficulties) const { + assert(m_difficultyWindow >= 2); - if (timestamps.size() > m_difficultyWindow_2) { - timestamps.resize(m_difficultyWindow_2); - cumulativeDifficulties.resize(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_2); - if (length <= 1) { - return 1; - } + size_t length = timestamps.size(); + assert(length == cumulativeDifficulties.size()); + assert(length <= m_difficultyWindow); + 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; - } + 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(); - assert(totalWork > 0); + difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1] - cumulativeDifficulties[cutBegin]; + assert(totalWork > 0); - // uint64_t nextDiffZ = totalWork * m_difficultyTarget / timeSpan; + uint64_t low, high; + low = mul128(totalWork, m_difficultyTarget, &high); + if (high != 0 || low + timeSpan - 1 < low) { + 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; - } + return (low + timeSpan - 1) / timeSpan; + } - uint64_t nextDiffZ = low / timeSpan; + difficulty_type Currency::nextDifficultyV2(std::vector timestamps, + std::vector cumulativeDifficulties) const { - // minimum limit - if (nextDiffZ <= 100000) { - nextDiffZ = 100000; - } + // 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 Nuclear_chaos - return nextDiffZ; + size_t m_difficultyWindow_2 = CryptoNote::parameters::DIFFICULTY_WINDOW_V2; + assert(m_difficultyWindow_2 >= 2); - // end of new difficulty calculation + if (timestamps.size() > m_difficultyWindow_2) { + timestamps.resize(m_difficultyWindow_2); + cumulativeDifficulties.resize(m_difficultyWindow_2); + } - } else { + size_t length = timestamps.size(); + assert(length == cumulativeDifficulties.size()); + assert(length <= m_difficultyWindow_2); + if (length <= 1) { + return 1; + } - // old difficulty calculation + sort(timestamps.begin(), timestamps.end()); - assert(m_difficultyWindow >= 2); + uint64_t timeSpan = timestamps.back() - timestamps.front(); + if (timeSpan == 0) { + timeSpan = 1; + } - if (timestamps.size() > m_difficultyWindow) { - timestamps.resize(m_difficultyWindow); - cumulativeDifficulties.resize(m_difficultyWindow); - } + difficulty_type totalWork = cumulativeDifficulties.back() - cumulativeDifficulties.front(); + assert(totalWork > 0); - size_t length = timestamps.size(); - assert(length == cumulativeDifficulties.size()); - assert(length <= m_difficultyWindow); - if (length <= 1) { - return 1; - } + // uint64_t nextDiffZ = totalWork * m_difficultyTarget / timeSpan; - sort(timestamps.begin(), timestamps.end()); + uint64_t low, high; + low = mul128(totalWork, m_difficultyTarget, &high); + // blockchain error "Difficulty overhead" if this function returns zero + if (high != 0) { + return 0; + } - 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; - } + uint64_t nextDiffZ = low / timeSpan; - difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1] - cumulativeDifficulties[cutBegin]; - assert(totalWork > 0); + // minimum limit + if (nextDiffZ < 1) { + nextDiffZ = 1; + } - uint64_t low, high; - low = mul128(totalWork, m_difficultyTarget, &high); - if (high != 0 || low + timeSpan - 1 < low) { - return 0; - } + return nextDiffZ; + } - return (low + timeSpan - 1) / timeSpan; - // end of old difficulty calculation - } + difficulty_type Currency::nextDifficultyV3(std::vector timestamps, + std::vector cumulativeDifficulties) const { + // The Simple Difficulty Algorithm + // This is a simple form of the EMA, and is nearly as good as the best algorithm. + // Jacob Eliosoff discovered the right kind of EMA that is ideal for DAs. + // Tom Harding found a simple inverted form of it for integer math. + // Zawy selected the following N and timestamp handling. + + const double_t adjust = 0.983; + int64_t T = static_cast(m_difficultyTarget); + size_t N = CryptoNote::parameters::DIFFICULTY_WINDOW_V3; + + 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; + + int64_t ST = static_cast(timestamps.back()) - static_cast(timestamps.end()[-2]); + difficulty_type previousDifficulty = cumulativeDifficulties.back() - cumulativeDifficulties.end()[-2]; + double k = N + ST / T / adjust - 1; + + uint64_t low, high, nextDifficulty; + low = mul128(previousDifficulty, N, &high); + if (high != 0) + return 0; + + nextDifficulty = static_cast(low / k); + + 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 58273d176ca..bb217e656a6 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;