-
Notifications
You must be signed in to change notification settings - Fork 216
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add LWMA difficulty adjustment algorithm
This PR adds in a difficulty adjustment algorithm. Motivation and Context We need to adjust the difficulty per block. This PR adds the LWMA algorithm as per zawy12/difficulty-algorithms#3
- Loading branch information
Showing
4 changed files
with
203 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
// LWMA-1 for BTC & Zcash clones | ||
// Copyright (c) 2017-2019 The Bitcoin Gold developers, Zawy, iamstenman (Microbitcoin) | ||
// MIT License | ||
// Algorithm by Zawy, a modification of WT-144 by Tom Harding | ||
// References: | ||
// https://github.com/zawy12/difficulty-algorithms/issues/3#issuecomment-442129791 | ||
// https://github.com/zcash/zcash/issues/4021 | ||
|
||
use crate::proof_of_work::difficulty::{self, Difficulty, DifficultyAdjustment}; | ||
use std::{cmp, collections::VecDeque}; | ||
|
||
// target time of 1 minute | ||
const TARGET: u64 = 60; | ||
// we should cap the amount of entries in the VecDeques to this number. | ||
const BLOCK_WINDOW: usize = 150; | ||
const INITIAL_DIFFICULTY: Difficulty = Difficulty::min(); | ||
|
||
pub struct LinearWeightedMovingAverage { | ||
timestamps: VecDeque<u64>, | ||
accumulated_difficulties: VecDeque<Difficulty>, | ||
block_window: usize, | ||
} | ||
|
||
impl Default for LinearWeightedMovingAverage { | ||
fn default() -> Self { | ||
LinearWeightedMovingAverage::new(BLOCK_WINDOW) | ||
} | ||
} | ||
|
||
impl LinearWeightedMovingAverage { | ||
pub fn new(block_window: usize) -> LinearWeightedMovingAverage { | ||
LinearWeightedMovingAverage { | ||
timestamps: VecDeque::with_capacity(block_window + 1), | ||
accumulated_difficulties: VecDeque::with_capacity(block_window + 1), | ||
block_window, | ||
} | ||
} | ||
|
||
fn calculate(&self) -> Difficulty { | ||
let timestamps = &self.timestamps; | ||
if timestamps.len() <= 1 { | ||
return INITIAL_DIFFICULTY; | ||
} | ||
|
||
// Use the array length rather than block_window to include early cases where the no. of pts < block_window | ||
let n = (timestamps.len() - 1) as u64; | ||
|
||
let mut weighted_times: u64 = 0; | ||
|
||
let difficulty: u64 = self.accumulated_difficulties[n as usize] | ||
.checked_sub(self.accumulated_difficulties[0]) | ||
.expect("Accumulated difficulties cannot decrease in proof of work") | ||
.as_u64(); | ||
let ave_difficulty = difficulty as f64 / n as f64; | ||
|
||
// Loop through N most recent blocks. | ||
for i in 1..(n + 1) as usize { | ||
// 6*T limit prevents large drops in diff from long solve times which would cause oscillations. | ||
let solve_time = match timestamps[i].checked_sub(timestamps[i - 1]) { | ||
None | Some(0) => 1, | ||
Some(v) => cmp::min(v, 6 * TARGET), | ||
}; | ||
|
||
// Give linearly higher weight to more recent solve times. | ||
// Note: This will not overflow for practical values of block_window and solve time. | ||
weighted_times += solve_time * i as u64; | ||
} | ||
// k is the sum of weights (1+2+..+n) * target_time | ||
let k = n * (n + 1) * TARGET / 2; | ||
let target = ave_difficulty * k as f64 / weighted_times as f64; | ||
if target > std::u64::MAX as f64 { | ||
panic!("Difficulty target has overflowed"); | ||
} | ||
let target = target as u64; | ||
target.into() | ||
} | ||
} | ||
|
||
impl DifficultyAdjustment for LinearWeightedMovingAverage { | ||
fn add(&mut self, timestamp: u64, accumulated_difficulty: Difficulty) { | ||
self.timestamps.push_back(timestamp); | ||
self.accumulated_difficulties.push_back(accumulated_difficulty); | ||
while self.timestamps.len() > self.block_window + 1 { | ||
self.timestamps.pop_front(); | ||
self.accumulated_difficulties.pop_front(); | ||
} | ||
} | ||
|
||
fn get_difficulty(&self) -> Difficulty { | ||
self.calculate() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn lwma_zero_len() { | ||
let dif = LinearWeightedMovingAverage::default(); | ||
assert_eq!(dif.get_difficulty(), Difficulty::min()); | ||
} | ||
|
||
#[test] | ||
fn lwma_negative_solve_times() { | ||
let mut dif = LinearWeightedMovingAverage::default(); | ||
let mut timestamp = 60; | ||
let mut cum_diff = Difficulty::from(100); | ||
dif.add(timestamp, cum_diff); | ||
timestamp += 60; | ||
cum_diff += Difficulty::from(100); | ||
dif.add(timestamp, cum_diff); | ||
// Lets create a history and populate the vecs | ||
for _i in 0..150 { | ||
cum_diff += Difficulty::from(100); | ||
timestamp += 60; | ||
dif.add(timestamp, cum_diff); | ||
} | ||
// lets create chaos by having 60 blocks as negative solve times. This should never be allowed in practive by | ||
// having checks on the block times. | ||
for _i in 0..60 { | ||
cum_diff += Difficulty::from(100); | ||
timestamp -= 1; // Only choosing -1 here since we are testing negative solve times and we cannot have 0 time | ||
let diff_before = dif.get_difficulty(); | ||
dif.add(timestamp, cum_diff); | ||
let diff_after = dif.get_difficulty(); | ||
// Algo should handle this as 1sec solve time thus increase the difficulty constantly | ||
assert!(diff_after > diff_before); | ||
} | ||
} | ||
|
||
#[test] | ||
fn lwma_limit_difficulty_change() { | ||
let mut dif = LinearWeightedMovingAverage::new(5); | ||
dif.add(60, 100.into()); | ||
dif.add(10_000_000, 200.into()); | ||
assert_eq!(dif.get_difficulty(), 16.into()); | ||
dif.add(20_000_000, 216.into()); | ||
assert_eq!(dif.get_difficulty(), 9.into()); | ||
} | ||
|
||
#[test] | ||
// Data for 5-period moving average | ||
// Timestamp: 60, 120, 180, 240, 300, 350, 380, 445, 515, 615, 975, 976, 977, 978, 979 | ||
// Intervals: 60, 60, 60, 60, 60, 50, 30, 65, 70, 100, 360, 1, 1, 1, 1 | ||
// Diff: 100, 100, 100, 100, 100, 105, 128, 123, 116, 94, 39, 46, 55, 75, 148 | ||
// Acum dif: 100, 200, 300, 400, 500, 605, 733, 856, 972,1066,1105,1151,1206,1281,1429 | ||
// Target: 1, 100, 100, 100, 100, 106, 135, 129, 119, 93, 35, 38, 46, 66, 174 | ||
fn lwma_calculate() { | ||
let mut dif = LinearWeightedMovingAverage::new(5); | ||
dif.add(60, 100.into()); | ||
assert_eq!(dif.get_difficulty(), 1.into()); | ||
dif.add(120, 200.into()); | ||
assert_eq!(dif.get_difficulty(), 100.into()); | ||
dif.add(180, 300.into()); | ||
assert_eq!(dif.get_difficulty(), 100.into()); | ||
dif.add(240, 400.into()); | ||
assert_eq!(dif.get_difficulty(), 100.into()); | ||
dif.add(300, 500.into()); | ||
assert_eq!(dif.get_difficulty(), 100.into()); | ||
dif.add(350, 605.into()); | ||
assert_eq!(dif.get_difficulty(), 106.into()); | ||
dif.add(380, 733.into()); | ||
assert_eq!(dif.get_difficulty(), 135.into()); | ||
dif.add(445, 856.into()); | ||
assert_eq!(dif.get_difficulty(), 129.into()); | ||
dif.add(515, 972.into()); | ||
assert_eq!(dif.get_difficulty(), 119.into()); | ||
dif.add(615, 1066.into()); | ||
assert_eq!(dif.get_difficulty(), 93.into()); | ||
dif.add(975, 1105.into()); | ||
assert_eq!(dif.get_difficulty(), 35.into()); | ||
dif.add(976, 1151.into()); | ||
assert_eq!(dif.get_difficulty(), 38.into()); | ||
dif.add(977, 1206.into()); | ||
assert_eq!(dif.get_difficulty(), 46.into()); | ||
dif.add(978, 1281.into()); | ||
assert_eq!(dif.get_difficulty(), 66.into()); | ||
dif.add(979, 1429.into()); | ||
assert_eq!(dif.get_difficulty(), 174.into()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters