Skip to content

Commit

Permalink
The Simple Difficulty Algorithm (EMA)
Browse files Browse the repository at this point in the history
  • Loading branch information
aivve committed Mar 22, 2018
1 parent 2bb5330 commit 19e30a9
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 84 deletions.
5 changes: 3 additions & 2 deletions src/CryptoNoteConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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");
Expand Down
48 changes: 46 additions & 2 deletions src/CryptoNoteCore/Blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,11 @@ difficulty_type Blockchain::getDifficultyForNextBlock() {
std::vector<difficulty_type> commulative_difficulties;
uint8_t BlockMajorVersion = getBlockMajorVersionForHeight(static_cast<uint32_t>(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<uint64_t>(m_currency.difficultyBlocksCount2()));
}
else if (BlockMajorVersion >= BLOCK_MAJOR_VERSION_3) {
offset = m_blocks.size() - std::min(m_blocks.size(), static_cast<uint64_t>(m_currency.difficultyBlocksCount3()));
}
else {
offset = m_blocks.size() - std::min(m_blocks.size(), static_cast<uint64_t>(m_currency.difficultyBlocksCount()));
Expand Down Expand Up @@ -835,7 +838,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std:
std::vector<difficulty_type> commulative_difficulties;
uint8_t BlockMajorVersion = getBlockMajorVersionForHeight(static_cast<uint32_t>(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<decltype(m_blockchain_lock)> lk(m_blockchain_lock);
Expand Down Expand Up @@ -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<decltype(m_blockchain_lock)> 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 {

Expand Down
204 changes: 124 additions & 80 deletions src/CryptoNoteCore/Currency.cpp
Original file line number Diff line number Diff line change
@@ -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.
//
Expand Down Expand Up @@ -407,108 +408,151 @@ namespace CryptoNote {
difficulty_type Currency::nextDifficulty(uint8_t blockMajorVersion, std::vector<uint64_t> timestamps,
std::vector<difficulty_type> 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<uint64_t> timestamps,
std::vector<difficulty_type> 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<uint64_t> timestamps,
std::vector<difficulty_type> 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<uint64_t> timestamps,
std::vector<difficulty_type> 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<int64_t>(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<int64_t>(timestamps.back()) - static_cast<int64_t>(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<uint64_t>(low / k);

return ++nextDifficulty;

This comment has been minimized.

Copy link
@aivve

aivve Mar 22, 2018

Author Collaborator

++ to kick it from Difficulty 1, +1 won't impact later on big numbers

}

bool Currency::checkProofOfWorkV1(Crypto::cn_context& context, const Block& block, difficulty_type currentDiffic,
Expand Down
4 changes: 4 additions & 0 deletions src/CryptoNoteCore/Currency.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -121,6 +122,9 @@ class Currency {
bool parseAmount(const std::string& str, uint64_t& amount) const;

difficulty_type nextDifficulty(uint8_t blockMajorVersion, std::vector<uint64_t> timestamps, std::vector<difficulty_type> Difficulties) const;
difficulty_type nextDifficultyV1(std::vector<uint64_t> timestamps, std::vector<difficulty_type> Difficulties) const;
difficulty_type nextDifficultyV2(std::vector<uint64_t> timestamps, std::vector<difficulty_type> Difficulties) const;
difficulty_type nextDifficultyV3(std::vector<uint64_t> timestamps, std::vector<difficulty_type> 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;
Expand Down

0 comments on commit 19e30a9

Please sign in to comment.