added LWMA

This commit is contained in:
wowario 2018-04-15 20:01:18 +03:00
parent 328b7be391
commit 2c959a9b21
No known key found for this signature in database
GPG key ID: 24DCBE762DE9C111
5 changed files with 126 additions and 15 deletions

View file

@ -33,6 +33,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <boost/math/special_functions/round.hpp>
#include "common/int-util.h" #include "common/int-util.h"
#include "crypto/hash.h" #include "crypto/hash.h"
@ -162,4 +163,81 @@ namespace cryptonote {
return (low + time_span - 1) / time_span; 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<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {
const int64_t T = static_cast<int64_t>(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<int64_t>(timestamps[i]) - static_cast<int64_t>(timestamps[i - 1]);
solveTime = std::min<int64_t>((T * 7), std::max<int64_t>(solveTime, (-7 * T)));
difficulty = cumulative_difficulties[i] - cumulative_difficulties[i - 1];
LWMA += solveTime * i / k;
sum_inverse_D += 1 / static_cast<double>(difficulty);
}
harmonic_mean_D = N / sum_inverse_D;
// Keep LWMA sane in case something unforeseen occurs.
if (static_cast<int64_t>(boost::math::round(LWMA)) < T / 20)
LWMA = static_cast<double>(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<uint64_t>(nextDifficulty);
return next_difficulty;
}
} }

View file

@ -53,4 +53,5 @@ namespace cryptonote
*/ */
bool check_hash(const crypto::hash &hash, difficulty_type difficulty); bool check_hash(const crypto::hash &hash, difficulty_type difficulty);
difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds); difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds);
difficulty_type next_difficulty_v2(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds);
} }

View file

@ -44,6 +44,7 @@
#define CURRENT_TRANSACTION_VERSION 2 #define CURRENT_TRANSACTION_VERSION 2
#define CURRENT_BLOCK_MAJOR_VERSION 7 #define CURRENT_BLOCK_MAJOR_VERSION 7
#define CURRENT_BLOCK_MINOR_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_BLOCK_FUTURE_TIME_LIMIT 60*60*2
#define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 10 #define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 10
@ -74,9 +75,11 @@
#define DIFFICULTY_TARGET_V2 300 #define DIFFICULTY_TARGET_V2 300
#define DIFFICULTY_TARGET_V1 300 #define DIFFICULTY_TARGET_V1 300
#define DIFFICULTY_WINDOW_V2 60
#define DIFFICULTY_WINDOW 720 // blocks #define DIFFICULTY_WINDOW 720 // blocks
#define DIFFICULTY_LAG 15 // !!! #define DIFFICULTY_LAG 15 // !!!
#define DIFFICULTY_CUT 60 // timestamps to cut after sorting #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 #define DIFFICULTY_BLOCKS_COUNT DIFFICULTY_WINDOW + DIFFICULTY_LAG

View file

@ -102,7 +102,8 @@ static const struct {
time_t time; time_t time;
} testnet_hard_forks[] = { } testnet_hard_forks[] = {
//{ 1, 1, 0, 1341378000 }, //{ 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)); 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<uint64_t> timestamps; std::vector<uint64_t> timestamps;
std::vector<difficulty_type> difficulties; std::vector<difficulty_type> difficulties;
auto height = m_db->height(); 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 // ND: Speedup
// 1. Keep a list of the last 735 (or less) blocks that is used to compute difficulty, // 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 // 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 // pop the oldest one from the list. This only requires 1x read per height instead
// of doing 735 (DIFFICULTY_BLOCKS_COUNT). // 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; uint64_t index = height - 1;
m_timestamps.push_back(m_db->get_block_timestamp(index)); m_timestamps.push_back(m_db->get_block_timestamp(index));
m_difficulties.push_back(m_db->get_block_cumulative_difficulty(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()); 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_difficulties.erase(m_difficulties.begin());
m_timestamps_and_difficulties_height = height; m_timestamps_and_difficulties_height = height;
@ -802,7 +812,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
} }
else else
{ {
size_t offset = height - std::min < size_t > (height, static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT)); size_t offset = height - std::min < size_t > (height, static_cast<size_t>(difficulty_blocks_count));
if (offset == 0) if (offset == 0)
++offset; ++offset;
@ -818,8 +828,12 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
m_timestamps = timestamps; m_timestamps = timestamps;
m_difficulties = difficulties; m_difficulties = difficulties;
} }
size_t target = get_difficulty_target(); size_t target = DIFFICULTY_TARGET_V2;
if (version == 7) {
return next_difficulty(timestamps, difficulties, target); 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 // 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__); LOG_PRINT_L3("Blockchain::" << __func__);
std::vector<uint64_t> timestamps; std::vector<uint64_t> timestamps;
std::vector<difficulty_type> cumulative_difficulties; std::vector<difficulty_type> 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 // 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 // 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); CRITICAL_REGION_LOCAL(m_blockchain_lock);
// Figure out start and stop offsets for main chain blocks // 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_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height;
size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std::min(static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT), alt_chain.size()); size_t main_chain_count = difficulty_blocks_count - std::min(static_cast<size_t>(difficulty_blocks_count), alt_chain.size());
main_chain_count = std::min(main_chain_count, main_chain_stop_offset); 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; 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? // 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) 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 // and timestamps from it alone
else else
{ {
timestamps.resize(static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT)); timestamps.resize(static_cast<size_t>(difficulty_blocks_count));
cumulative_difficulties.resize(static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT)); cumulative_difficulties.resize(static_cast<size_t>(difficulty_blocks_count));
size_t count = 0; size_t count = 0;
size_t max_i = timestamps.size()-1; size_t max_i = timestamps.size()-1;
// get difficulties and timestamps from most recent blocks in alt chain // 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; timestamps[max_i - count] = it->second.bl.timestamp;
cumulative_difficulties[max_i - count] = it->second.cumulative_difficulty; cumulative_difficulties[max_i - count] = it->second.cumulative_difficulty;
count++; count++;
if(count >= DIFFICULTY_BLOCKS_COUNT) if(count >= difficulty_blocks_count)
break; 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; 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 // calculate the difficulty target for the block and return it
if (version == 7) {
return next_difficulty(timestamps, cumulative_difficulties, target); 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 // This function does a sanity check on basic things that all miner
// transactions have in common, such as: // transactions have in common, such as:
@ -3174,7 +3200,8 @@ bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const
bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const
{ {
LOG_PRINT_L3("Blockchain::" << __func__); 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"); MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours");
return false; return false;

View file

@ -370,6 +370,8 @@ namespace nodetool
{ {
std::set<std::string> full_addrs; std::set<std::string> full_addrs;
if (nettype == cryptonote::TESTNET) { if (nettype == cryptonote::TESTNET) {
full_addrs.insert("167.160.87.144:11180");
full_addrs.insert("5.255.86.129:11180");
} else { } else {
full_addrs.insert("66.70.218.230:34567"); full_addrs.insert("66.70.218.230:34567");
full_addrs.insert("34.209.48.213:34567"); full_addrs.insert("34.209.48.213:34567");