From 2c959a9b211486a549eb61a074c4c2edbf155b61 Mon Sep 17 00:00:00 2001 From: wowario Date: Sun, 15 Apr 2018 20:01:18 +0300 Subject: [PATCH] added LWMA --- src/cryptonote_basic/difficulty.cpp | 78 +++++++++++++++++++++++++++++ src/cryptonote_basic/difficulty.h | 1 + src/cryptonote_config.h | 3 ++ src/cryptonote_core/blockchain.cpp | 57 +++++++++++++++------ src/p2p/net_node.inl | 2 + 5 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index cb2a00a12..4df313b26 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "common/int-util.h" #include "crypto/hash.h" @@ -162,4 +163,81 @@ namespace cryptonote { return (low + time_span - 1) / time_span; } +// LWMA difficulty algorithm +// Background: https://github.com/zawy12/difficulty-algorithms/issues/3 +// Copyright (c) 2017-2018 Zawy (pseudocode) +// MIT license http://www.opensource.org/licenses/mit-license.php +// Copyright (c) 2018 Wownero Inc., a Monero Enterprise Alliance partner company +// Copyright (c) 2018 The Karbowanec developers (initial code) +// Copyright (c) 2018 Haven Protocol (refinements) +// Degnr8, Karbowanec, Masari, Bitcoin Gold, Bitcoin Candy, and Haven have contributed. + +// This algorithm is: next_difficulty = harmonic_mean(Difficulties) * T / LWMA(Solvetimes) +// The harmonic_mean(Difficulties) = 1/average(Targets) so it is also: +// next_target = avg(Targets) * LWMA(Solvetimes) / T. +// This is "the best algorithm" because it has lowest root-mean-square error between +// needed & actual difficulty during hash attacks while having the lowest standard +// deviation during stable hashrate. That is, it's the fastest for a given stability and vice versa. +// Do not use "if solvetime < 1 then solvetime = 1" which allows a catastrophic exploit. +// Do not sort timestamps. "Solvetimes" and "LWMA" variables must allow negatives. +// Do not use MTP as most recent block. Do not use (POW)Limits, filtering, or tempering. +// Do not forget to set N (aka DIFFICULTY_WINDOW in Cryptonote) to recommendation below. +// The nodes' future time limit (FTL) aka CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT needs to +// be reduced from 60*60*2 to 500 seconds to prevent timestamp manipulation from miner's with +// > 50% hash power. If this is too small, it can be increased to 1000 at a cost in protection. + +// Cryptonote clones: #define DIFFICULTY_BLOCKS_COUNT_V2 DIFFICULTY_WINDOW_V2 + 1 + + + difficulty_type next_difficulty_v2(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { + + const int64_t T = static_cast(target_seconds); + + size_t N = DIFFICULTY_WINDOW_V2; + + // Return a difficulty of 1 for first 3 blocks if it's the start of the chain. + if (timestamps.size() < 4) { + return 1; + } + // Otherwise, use a smaller N if the start of the chain is less than N+1. + else if ( timestamps.size() < N+1 ) { + N = timestamps.size() - 1; + } + // Otherwise make sure timestamps and cumulative_difficulties are correct size. + else { + timestamps.resize(N+1); + cumulative_difficulties.resize(N+1); + } + // To get an average solvetime to within +/- ~0.1%, use an adjustment factor. + // adjust=0.999 for 80 < N < 120(?) + const double adjust = 0.998; + // The divisor k normalizes the LWMA sum to a standard LWMA. + const double k = N * (N + 1) / 2; + + double LWMA(0), sum_inverse_D(0), harmonic_mean_D(0), nextDifficulty(0); + int64_t solveTime(0); + uint64_t difficulty(0), next_difficulty(0); + + // Loop through N most recent blocks. N is most recently solved block. + for (size_t i = 1; i <= N; i++) { + solveTime = static_cast(timestamps[i]) - static_cast(timestamps[i - 1]); + solveTime = std::min((T * 7), std::max(solveTime, (-7 * T))); + difficulty = cumulative_difficulties[i] - cumulative_difficulties[i - 1]; + LWMA += solveTime * i / k; + sum_inverse_D += 1 / static_cast(difficulty); + } + + harmonic_mean_D = N / sum_inverse_D; + + // Keep LWMA sane in case something unforeseen occurs. + if (static_cast(boost::math::round(LWMA)) < T / 20) + LWMA = static_cast(T / 20); + + nextDifficulty = harmonic_mean_D * T / LWMA * adjust; + + // No limits should be employed, but this is correct way to employ a 20% symmetrical limit: + // nextDifficulty=max(previous_Difficulty*0.8,min(previous_Difficulty/0.8, next_Difficulty)); + next_difficulty = static_cast(nextDifficulty); + return next_difficulty; + } } diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index b06538467..9da643f39 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -53,4 +53,5 @@ namespace cryptonote */ bool check_hash(const crypto::hash &hash, difficulty_type difficulty); difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); + difficulty_type next_difficulty_v2(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index a1ff43b14..84d624381 100755 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -44,6 +44,7 @@ #define CURRENT_TRANSACTION_VERSION 2 #define CURRENT_BLOCK_MAJOR_VERSION 7 #define CURRENT_BLOCK_MINOR_VERSION 7 +#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2 300*2 #define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 #define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 10 @@ -74,9 +75,11 @@ #define DIFFICULTY_TARGET_V2 300 #define DIFFICULTY_TARGET_V1 300 +#define DIFFICULTY_WINDOW_V2 60 #define DIFFICULTY_WINDOW 720 // blocks #define DIFFICULTY_LAG 15 // !!! #define DIFFICULTY_CUT 60 // timestamps to cut after sorting +#define DIFFICULTY_BLOCKS_COUNT_V2 DIFFICULTY_WINDOW_V2 + 1 // added +1 to make N=N #define DIFFICULTY_BLOCKS_COUNT DIFFICULTY_WINDOW + DIFFICULTY_LAG diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 299d16863..5f19be319 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -102,7 +102,8 @@ static const struct { time_t time; } testnet_hard_forks[] = { //{ 1, 1, 0, 1341378000 }, - { 7, 1, 0, 1519605000 } + { 7, 1, 0, 1519605000 }, + { 8, 10, 0, 1523255371 }, }; static const uint64_t testnet_hard_fork_version_1_till = ((uint64_t)(1)); @@ -780,20 +781,29 @@ difficulty_type Blockchain::get_difficulty_for_next_block() std::vector timestamps; std::vector difficulties; auto height = m_db->height(); + + uint8_t version = get_current_hard_fork_version(); + size_t difficulty_blocks_count; + if (version == 7) { + difficulty_blocks_count = DIFFICULTY_BLOCKS_COUNT; + } else { + difficulty_blocks_count = DIFFICULTY_BLOCKS_COUNT_V2; + } + // ND: Speedup // 1. Keep a list of the last 735 (or less) blocks that is used to compute difficulty, // then when the next block difficulty is queried, push the latest height data and // pop the oldest one from the list. This only requires 1x read per height instead // of doing 735 (DIFFICULTY_BLOCKS_COUNT). - if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1)) + if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1) && m_timestamps.size() >= difficulty_blocks_count) { uint64_t index = height - 1; m_timestamps.push_back(m_db->get_block_timestamp(index)); m_difficulties.push_back(m_db->get_block_cumulative_difficulty(index)); - while (m_timestamps.size() > DIFFICULTY_BLOCKS_COUNT) + while (m_timestamps.size() > difficulty_blocks_count) m_timestamps.erase(m_timestamps.begin()); - while (m_difficulties.size() > DIFFICULTY_BLOCKS_COUNT) + while (m_difficulties.size() > difficulty_blocks_count) m_difficulties.erase(m_difficulties.begin()); m_timestamps_and_difficulties_height = height; @@ -802,7 +812,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block() } else { - size_t offset = height - std::min < size_t > (height, static_cast(DIFFICULTY_BLOCKS_COUNT)); + size_t offset = height - std::min < size_t > (height, static_cast(difficulty_blocks_count)); if (offset == 0) ++offset; @@ -818,8 +828,12 @@ difficulty_type Blockchain::get_difficulty_for_next_block() m_timestamps = timestamps; m_difficulties = difficulties; } - size_t target = get_difficulty_target(); - return next_difficulty(timestamps, difficulties, target); + size_t target = DIFFICULTY_TARGET_V2; + if (version == 7) { + return next_difficulty(timestamps, difficulties, target); + } else { + return next_difficulty_v2(timestamps, difficulties, target); + } } //------------------------------------------------------------------ // This function removes blocks from the blockchain until it gets to the @@ -967,16 +981,23 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: LOG_PRINT_L3("Blockchain::" << __func__); std::vector timestamps; std::vector cumulative_difficulties; + uint8_t version = get_current_hard_fork_version(); + size_t difficulty_blocks_count; + if (version == 7) { + difficulty_blocks_count = DIFFICULTY_BLOCKS_COUNT; + } else { + difficulty_blocks_count = DIFFICULTY_BLOCKS_COUNT_V2; + } // if the alt chain isn't long enough to calculate the difficulty target // based on its blocks alone, need to get more blocks from the main chain - if(alt_chain.size()< DIFFICULTY_BLOCKS_COUNT) + if(alt_chain.size()< difficulty_blocks_count) { CRITICAL_REGION_LOCAL(m_blockchain_lock); // Figure out start and stop offsets for main chain blocks size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; - size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std::min(static_cast(DIFFICULTY_BLOCKS_COUNT), alt_chain.size()); + size_t main_chain_count = difficulty_blocks_count - std::min(static_cast(difficulty_blocks_count), 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; @@ -991,7 +1012,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: } // make sure we haven't accidentally grabbed too many blocks...maybe don't need this check? - CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= DIFFICULTY_BLOCKS_COUNT, false, "Internal error, alt_chain.size()[" << alt_chain.size() << "] + vtimestampsec.size()[" << timestamps.size() << "] NOT <= DIFFICULTY_WINDOW[]" << DIFFICULTY_BLOCKS_COUNT); + CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= difficulty_blocks_count, false, "Internal error, alt_chain.size()[" << alt_chain.size() << "] + vtimestampsec.size()[" << timestamps.size() << "] NOT <= DIFFICULTY_WINDOW[]" << DIFFICULTY_BLOCKS_COUNT); for (auto it : alt_chain) { @@ -1003,8 +1024,8 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: // and timestamps from it alone else { - timestamps.resize(static_cast(DIFFICULTY_BLOCKS_COUNT)); - cumulative_difficulties.resize(static_cast(DIFFICULTY_BLOCKS_COUNT)); + timestamps.resize(static_cast(difficulty_blocks_count)); + cumulative_difficulties.resize(static_cast(difficulty_blocks_count)); size_t count = 0; size_t max_i = timestamps.size()-1; // get difficulties and timestamps from most recent blocks in alt chain @@ -1013,7 +1034,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: timestamps[max_i - count] = it->second.bl.timestamp; cumulative_difficulties[max_i - count] = it->second.cumulative_difficulty; count++; - if(count >= DIFFICULTY_BLOCKS_COUNT) + if(count >= difficulty_blocks_count) break; } } @@ -1022,8 +1043,13 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: size_t target = get_ideal_hard_fork_version(bei.height) < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; // calculate the difficulty target for the block and return it - return next_difficulty(timestamps, cumulative_difficulties, target); + if (version == 7) { + return next_difficulty(timestamps, cumulative_difficulties, target); + } else { + return next_difficulty_v2(timestamps, cumulative_difficulties, target); + } } + //------------------------------------------------------------------ // This function does a sanity check on basic things that all miner // transactions have in common, such as: @@ -3174,7 +3200,8 @@ bool Blockchain::check_block_timestamp(std::vector& timestamps, const bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const { LOG_PRINT_L3("Blockchain::" << __func__); - if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) + uint64_t cryptonote_block_future_time_limit = get_current_hard_fork_version() < 7 ? CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT : CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2; + if(b.timestamp > get_adjusted_time() + cryptonote_block_future_time_limit) { MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); return false; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index d94e48413..17dcd8ef1 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -370,6 +370,8 @@ namespace nodetool { std::set full_addrs; if (nettype == cryptonote::TESTNET) { + full_addrs.insert("167.160.87.144:11180"); + full_addrs.insert("5.255.86.129:11180"); } else { full_addrs.insert("66.70.218.230:34567"); full_addrs.insert("34.209.48.213:34567");