Open zawy12 opened 6 years ago
Code from other coins for my future reference: Niobio
difficulty_type Currency::nextDifficultyV5(std::vector<uint64_t> timestamps, std::vector<difficulty_type> cumulativeDifficulties) const {
// Slipstream EMA difficulty algorithm
// Copyright (c) 2018 Zawy
// EMA & LWMA math by Jacob Eliosoff and Tom Harding.
// https://github.com/zawy12/difficulty-algorithms/issues/27
// Cryptonote-like coins must see the link above for additional changes.
// After startup, timestamps & cumulativeDifficulties vectors should be size N+1.
double N = CryptoNote::parameters::DIFFICULTY_WINDOW_V4 - 1;
double T = m_difficultyTarget;
double FTL = static_cast<int64_t>(CryptoNote::parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V5);
double next_D, ST, D, tSMA, sumD, sumST;
if (timestamps.size() >= (N + 1)) {
// After startup, the following should be the norm.
timestamps.resize(N + 1); cumulativeDifficulties.resize(N + 1);
// For new coins height < N+1, give away first 4 blocks or use smaller N
} else if (timestamps.size() >= 4) {
N = timestamps.size() - 1;
} else {
return 100;
}
// Calculate fast EMA using most recent 2 blocks.
// +6xT prevents D dropping too far after attack to prevent on-off attack oscillations.
// -FTL prevents maliciously raising D. ST=solvetime.
ST = std::max(-FTL, std::min(double(timestamps[N] - timestamps[N - 1]), 6 * T));
D = cumulativeDifficulties[N] - cumulativeDifficulties[N - 1];
next_D = roundOffProtection(D * 9 * T / (8 * T + ST * 1.058));
// Calculate a tempered SMA.
sumD = cumulativeDifficulties[N] - cumulativeDifficulties[0];
sumST = timestamps[N] - timestamps[0];
tSMA = roundOffProtection(sumD * 2 * T / (N * T + sumST));
// Do slow EMA if fast EMA is outside +/- 14% from tSMA.
if (next_D > (tSMA * 1.14) || next_D < (tSMA * 0.877)) {
next_D = roundOffProtection(D * 28 * T / (27 * T + ST * 1.02));
}
return static_cast<uint64_t>(0.9935 * next_D);
}
Wownero
// Slipstream EMA difficulty algorithm
// Copyright (c) 2018 Zawy
// EMA math by Jacob Eliosoff and Tom Harding.
// https://github.com/zawy12/difficulty-algorithms/issues/27
// Cryptonote-like coins must see the link above for additional changes.
// Round Off Protection. D must normally be > 100 and always < 10 T.
// This is used because some devs believe double may pose a round-off risk.
double ROP(double RR) {
if( ceil(RR + 0.01) > ceil(RR) ) { RR = ceil(RR + 0.03); }
return RR;
}
difficulty_type next_difficulty_v3(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties) {
// After startup, timestamps & cumulative_difficulties vectors should be size N+1.
double N = DIFFICULTY_WINDOW_V2;
double T = DIFFICULTY_TARGET_V2;
double FTL = static_cast<int64_t>(CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V3); // < 3xT
double next_D, ST, D, tSMA, sumD, sumST;
// For new coins height < N+1, give away first 4 blocks or use smaller N
if (timestamps.size() < 4) { return 100; }
else if (timestamps.size() < N+1) { N = timestamps.size() - 1; }
// After startup, the following should be the norm.
else { timestamps.resize(N+1); cumulative_difficulties.resize(N+1); }
// Calculate fast EMA using most recent 2 blocks.
// +6xT prevents D dropping too far after attack to prevent on-off attack oscillations.
// -FTL prevents maliciously raising D. ST=solvetime.
ST = std::max(-FTL, std::min(double(timestamps[N] - timestamps[N-1]), 6*T));
// Most recent solvetime applies to previous difficulty, not the most recent one.
D = cumulative_difficulties[N] - cumulative_difficulties[N-1];
next_D = ROP(D*9/(8+ST/T/0.945));
// Calculate a tempered SMA. Don't shift the difficulties back 1 as in EMA.
sumD = cumulative_difficulties[N] - cumulative_difficulties[0];
sumST = timestamps[N] - timestamps[0];
tSMA = ROP(sumD/(0.5*N+0.5*sumST/T));
// Do slow EMA if fast EMA is outside +/- 14% from tSMA.
if (next_D > tSMA*1.14 or next_D < tSMA/1.14) {
next_D = ROP( D*28/(27+ST/T/0.98));
}
return static_cast<uint64_t>(0.9935*next_D);
}
}
Technohacker
difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {
// After startup, timestamps & cumulative_difficulties vectors should be size N+1.
double N = DIFFICULTY_WINDOW;
double T = target_seconds;
double FTL = static_cast<int64_t>(CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT);
double next_D, ST, D, tSMA, sumD, sumST;
// For new coins height < N+1, give away first 4 blocks or use smaller N
if (timestamps.size() < 4) { return 100; }
else if (timestamps.size() < N+1) { N = timestamps.size() - 1; }
// After startup, the following should be the norm.
else { timestamps.resize(N+1); cumulative_difficulties.resize(N+1); }
// Calculate fast EMA using most recent 2 blocks.
// +6xT prevents D dropping too far after attack to prevent on-off attack oscillations.
// -FTL prevents maliciously raising D. ST=solvetime.
ST = std::max(-FTL, std::min(double(timestamps[N] - timestamps[N-1]), 6*T));
// Most recent solvetime applies to previous difficulty, not the most recent one.
D = cumulative_difficulties[N-1] - cumulative_difficulties[N-2];
next_D = ROP(D*9/(8+ST/T/0.945));
// Calculate a tempered SMA. Don't shift the difficulties back 1 as in EMA.
sumD = cumulative_difficulties[N] - cumulative_difficulties[0];
sumST = timestamps[N] - timestamps[0];
tSMA = ROP(sumD/(0.5*N+0.5*sumST/T));
// Do slow EMA if fast EMA is outside +/- 14% from tSMA.
if (next_D > tSMA*1.14 or next_D < tSMA/1.14) {
next_D = ROP( D*28/(27+ST/T/0.98));
}
return static_cast<uint64_t>(0.9935*next_D);
}
Do not use this algorithm
The problem is that there is a sweet spot in attacking, shown here:
================== Come back to check before doing any pull or merge. Code compiles and is currently on 2 to 5 testnets. Hopefully the testnets will concur with my spreadsheet today 5/22/2018.
Change history: 5/21/2018:
5/22/2018:
There were some instances where LWMA was not performing optimally see LWMA failures. This is an attempt to solve the problem. Large coins should use the Simple EMA. This one should be the best for small coins. This supersedes LWMA, D-LWMA, and Dynamic EMA.
Slipstream EMA Description Slipstream EMA estimates current hashrate in order to set difficulty to get the correct solvetime. It gives more weight to the most recent solvetimes. It is designed for small coin protection against timestamp manipulation, hash-attacks, delays, and oscillations. It rises 10% per block when an attack begins, so most attacks can't benefit from staying on more than 2 blocks. It uses a very fast Exponential Moving Average to respond to hashrate changes as long as the result is within a +/- 14% range around a slow tempered Simple Moving Average. If it goes outside that range, it switches to a medium-speed EMA to prevent it from rising too high or falling too far. Slipstream refers to it having a strong preference to being "behind" the SMA within +/- 14%. It resists going outside that range, and is anxious to return to it.
Stable hash rate comparison
Typical attack comparison
Attacks that cause oscillations
Notice how much higher Digishield goes, causing delays.![image](https://user-images.githubusercontent.com/18004719/40313593-41e90e28-5ce4-11e8-9236-3a281ffae195.png)
Justification for each setting
I'm against a dev such as myself selecting any constant in difficulty algorithms. It should be an objective setting. But due to the non-linearity of of miners jumping on or off based on some rough threshold (like +/- 25%), and based on miners being able to change behavior when the algorithm can't, it's mathematically difficult to develop an algorithm that can figure out the best settings. It seems to need a an A.I. Lacking the skill, I've needed to rely on experience and observation to choose several settings. These are supposedly close to what an A.I. would conclude Some of the constants are not chosen by me other than to do the correct math, but the important ones are. The important ones are 6xT ,+/- 14%, N=60, 50%, N=28, and N=9.