mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
Implemented new difficulty calculation algorithm.
See #62 for background. Still needs to be integrated with proof of work and validation.
This commit is contained in:
parent
55eb2f6887
commit
163b1133a7
2 changed files with 179 additions and 53 deletions
|
@ -32,7 +32,7 @@ pub const REWARD: u64 = 1_000_000_000;
|
|||
/// that we may reduce this value in the future as we get more data on mining
|
||||
/// with Cuckoo Cycle, networks improve and block propagation is optimized
|
||||
/// (adjusting the reward accordingly).
|
||||
pub const BLOCK_TIME_SEC: u8 = 60;
|
||||
pub const BLOCK_TIME_SEC: i64 = 60;
|
||||
|
||||
/// Cuckoo-cycle proof size (cycle length)
|
||||
pub const PROOFSIZE: usize = 42;
|
||||
|
@ -53,6 +53,94 @@ pub const MAX_SIZESHIFT: u8 = 29;
|
|||
/// a solution.
|
||||
pub const EASINESS: u32 = 50;
|
||||
|
||||
/// Default number of blocks in the past when cross-block cut-through will start
|
||||
/// happening. Needs to be long enough to not overlap with a long reorg.
|
||||
/// Rational
|
||||
/// behind the value is the longest bitcoin fork was about 30 blocks, so 5h. We
|
||||
/// add an order of magnitude to be safe and round to 48h of blocks to make it
|
||||
/// easier to reason about.
|
||||
pub const CUT_THROUGH_HORIZON: u32 = 48 * 3600 / (BLOCK_TIME_SEC as u32);
|
||||
|
||||
/// The maximum size we're willing to accept for any message. Enforced by the
|
||||
/// peer-to-peer networking layer only for DoS protection.
|
||||
pub const MAX_MSG_LEN: u64 = 20_000_000;
|
||||
|
||||
pub const MEDIAN_TIME_WINDOW: u32 = 11;
|
||||
|
||||
pub const DIFFICULTY_ADJUST_WINDOW: u32 = 23;
|
||||
|
||||
pub const BLOCK_TIME_WINDOW: i64 = (DIFFICULTY_ADJUST_WINDOW as i64) * BLOCK_TIME_SEC;
|
||||
|
||||
pub const UPPER_TIME_BOUND: i64 = BLOCK_TIME_WINDOW * 4 / 3;
|
||||
|
||||
pub const LOWER_TIME_BOUND: i64 = BLOCK_TIME_WINDOW * 5 / 6;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TargetError {
|
||||
err: String,
|
||||
}
|
||||
pub fn next_target2<T>(cursor: T) -> Result<Difficulty, TargetError>
|
||||
where T: IntoIterator<Item = Result<(i64, Difficulty), TargetError>>
|
||||
{
|
||||
|
||||
// Block times at the begining and end of the adjustment window, used to
|
||||
// calculate medians later.
|
||||
let mut window_begin = vec![];
|
||||
let mut window_end = vec![];
|
||||
|
||||
// Sum of difficulties in the window, used to calculate the average later.
|
||||
let mut diff_sum = Difficulty::zero();
|
||||
|
||||
// Enumerating backward over blocks
|
||||
for (n, head_info) in cursor.into_iter().enumerate() {
|
||||
let m = n as u32;
|
||||
let (ts, diff) = head_info?;
|
||||
|
||||
// Sum each element in the adjustment window. In addition, retain
|
||||
// timestamps within median windows (at ]start;start-11] and ]end;end-11]
|
||||
// to later calculate medians.
|
||||
if m < DIFFICULTY_ADJUST_WINDOW {
|
||||
diff_sum = diff_sum + diff;
|
||||
|
||||
if m < MEDIAN_TIME_WINDOW {
|
||||
window_begin.push(ts);
|
||||
}
|
||||
} else if m < DIFFICULTY_ADJUST_WINDOW + MEDIAN_TIME_WINDOW {
|
||||
window_end.push(ts);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check we have enough blocks
|
||||
if window_end.len() < (MEDIAN_TIME_WINDOW as usize) {
|
||||
return Ok(Difficulty::one());
|
||||
}
|
||||
|
||||
// Calculating time medians at the beginning and end of the window.
|
||||
window_begin.sort();
|
||||
window_end.sort();
|
||||
let begin_ts = window_begin[window_begin.len() / 2];
|
||||
let end_ts = window_end[window_end.len() / 2];
|
||||
|
||||
// Average difficulty and dampened average time
|
||||
let diff_avg = diff_sum / Difficulty::from_num(DIFFICULTY_ADJUST_WINDOW);
|
||||
let ts_damp = (3 * BLOCK_TIME_WINDOW + (begin_ts - end_ts)) / 4;
|
||||
|
||||
// Apply time bounds
|
||||
let adj_ts = if ts_damp < LOWER_TIME_BOUND {
|
||||
LOWER_TIME_BOUND
|
||||
} else if ts_damp > UPPER_TIME_BOUND {
|
||||
UPPER_TIME_BOUND
|
||||
} else {
|
||||
ts_damp
|
||||
};
|
||||
|
||||
// Final ratio calculation
|
||||
Ok(diff_avg * Difficulty::from_num(BLOCK_TIME_WINDOW as u32) /
|
||||
Difficulty::from_num(adj_ts as u32))
|
||||
}
|
||||
|
||||
/// Difficulty adjustment somewhat inspired by Ethereum's. Tuned to add or
|
||||
/// remove 1/1024th of the target for each 10 seconds of deviation from the 30
|
||||
/// seconds block time. Increases Cuckoo size shift by one when next_target
|
||||
|
@ -93,60 +181,79 @@ pub fn next_target(ts: i64,
|
|||
}
|
||||
}
|
||||
|
||||
/// Default number of blocks in the past when cross-block cut-through will start
|
||||
/// happening. Needs to be long enough to not overlap with a long reorg.
|
||||
/// Rational
|
||||
/// behind the value is the longest bitcoin fork was about 30 blocks, so 5h. We
|
||||
/// add an order of magnitude to be safe and round to 48h of blocks to make it
|
||||
/// easier to reason about.
|
||||
pub const CUT_THROUGH_HORIZON: u32 = 48 * 3600 / (BLOCK_TIME_SEC as u32);
|
||||
|
||||
/// The maximum size we're willing to accept for any message. Enforced by the
|
||||
/// peer-to-peer networking layer only for DoS protection.
|
||||
pub const MAX_MSG_LEN: u64 = 20_000_000;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use core::target::Difficulty;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
/// Checks different next_target adjustments and difficulty boundaries
|
||||
fn next_target_adjustment() {
|
||||
// can't do lower than min
|
||||
assert_eq!(next_target(60, 0, Difficulty::one(), 26),
|
||||
(Difficulty::one(), 26));
|
||||
assert_eq!(next_target(90, 30, Difficulty::one(), 26),
|
||||
(Difficulty::one(), 26));
|
||||
assert_eq!(next_target(60, 0, Difficulty::one(), 26),
|
||||
(Difficulty::one(), 26));
|
||||
|
||||
// lower next_target if gap too short
|
||||
assert_eq!(next_target(30, 0, Difficulty::one(), 26).0,
|
||||
Difficulty::from_num(4));
|
||||
assert_eq!(next_target(50, 0, Difficulty::one(), 26).0,
|
||||
Difficulty::from_num(2));
|
||||
assert_eq!(next_target(40, 0, Difficulty::from_num(1024 * 8), 26).0,
|
||||
Difficulty::from_num(1024 * 8 + 18));
|
||||
|
||||
// lower difficulty if gap too wide
|
||||
assert_eq!(next_target(70, 0, Difficulty::from_num(10), 26).0,
|
||||
Difficulty::from_num(9));
|
||||
assert_eq!(next_target(90, 0, Difficulty::from_num(1024 * 8), 26).0,
|
||||
Difficulty::from_num(1024 * 8 - 9 * 3));
|
||||
|
||||
// identical, no adjustment
|
||||
assert_eq!(next_target(60, 0, Difficulty::from_num(1024 * 8), 26).0,
|
||||
Difficulty::from_num(1024 * 8));
|
||||
|
||||
// increase cuckoo size if next_target goes above soft max, target is doubled,
|
||||
// up to 29
|
||||
assert_eq!(next_target(60, 0, Difficulty::from_num(1 << 16), 25),
|
||||
(Difficulty::from_num(1 << 16), 25));
|
||||
assert_eq!(next_target(60, 0, Difficulty::from_num((1 << 16) + 1), 25),
|
||||
(Difficulty::from_num(1 << 15), 26));
|
||||
assert_eq!(next_target(60, 0, Difficulty::from_num((1 << 24) + 1), 26),
|
||||
(Difficulty::from_num(1 << 23), 27));
|
||||
// Builds an iterator for next difficulty calculation with the provided
|
||||
// constant time interval, difficulty and total length.
|
||||
fn repeat(interval: i64, diff: u32, len: u32) -> Vec<Result<(i64, Difficulty), TargetError>> {
|
||||
let diffs = vec![Difficulty::from_num(diff); len as usize];
|
||||
let times = (0..(len as usize)).map(|n| (n as i64) * interval).rev();
|
||||
let pairs = times.zip(diffs.iter());
|
||||
pairs.map(|(t, d)| Ok((t, d.clone()))).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn repeat_offs(from: i64,
|
||||
interval: i64,
|
||||
diff: u32,
|
||||
len: u32)
|
||||
-> Vec<Result<(i64, Difficulty), TargetError>> {
|
||||
map_vec!(repeat(interval, diff, len), |e| {
|
||||
match e.clone() {
|
||||
Err(e) => Err(e),
|
||||
Ok((t, d)) => Ok((t + from, d)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks different next_target adjustments and difficulty boundaries
|
||||
#[test]
|
||||
fn next_target_adjustment() {
|
||||
// not enough data
|
||||
assert_eq!(next_target2(vec![]).unwrap(), Difficulty::one());
|
||||
assert_eq!(next_target2(vec![Ok((60, Difficulty::one()))]).unwrap(),
|
||||
Difficulty::one());
|
||||
assert_eq!(next_target2(repeat(60, 10, DIFFICULTY_ADJUST_WINDOW)).unwrap(),
|
||||
Difficulty::one());
|
||||
|
||||
// just enough data, right interval, should stay constant
|
||||
let just_enough = DIFFICULTY_ADJUST_WINDOW + MEDIAN_TIME_WINDOW;
|
||||
assert_eq!(next_target2(repeat(60, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(1000));
|
||||
|
||||
// checking averaging works, window length is odd so need to compensate a little
|
||||
let sec = DIFFICULTY_ADJUST_WINDOW / 2 + 1 + MEDIAN_TIME_WINDOW;
|
||||
let mut s1 = repeat(60, 500, sec);
|
||||
let mut s2 = repeat_offs((sec * 60) as i64, 60, 1545, DIFFICULTY_ADJUST_WINDOW / 2);
|
||||
s2.append(&mut s1);
|
||||
assert_eq!(next_target2(s2).unwrap(), Difficulty::from_num(999));
|
||||
|
||||
// too slow, diff goes down
|
||||
assert_eq!(next_target2(repeat(90, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(889));
|
||||
assert_eq!(next_target2(repeat(120, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(800));
|
||||
|
||||
// too fast, diff goes up
|
||||
assert_eq!(next_target2(repeat(55, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(1021));
|
||||
assert_eq!(next_target2(repeat(45, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(1067));
|
||||
|
||||
// hitting lower time bound, should always get the same result below
|
||||
assert_eq!(next_target2(repeat(20, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(1200));
|
||||
assert_eq!(next_target2(repeat(10, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(1200));
|
||||
|
||||
// hitting higher time bound, should always get the same result above
|
||||
assert_eq!(next_target2(repeat(160, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(750));
|
||||
assert_eq!(next_target2(repeat(200, 1000, just_enough)).unwrap(),
|
||||
Difficulty::from_num(750));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
//! the related difficulty, defined as the maximum target divided by the hash.
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use std::ops::{Add, Mul, Div};
|
||||
|
||||
use bigint::BigUint;
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer, de};
|
||||
|
@ -37,9 +37,14 @@ pub struct Difficulty {
|
|||
}
|
||||
|
||||
impl Difficulty {
|
||||
/// Difficulty of zero, which is practically invalid (not target can be
|
||||
/// calculated from it) but very useful as a start for additions.
|
||||
pub fn zero() -> Difficulty {
|
||||
Difficulty { num: BigUint::new(vec![0]) }
|
||||
}
|
||||
|
||||
/// Difficulty of one, which is the minumum difficulty (when the hash
|
||||
/// equals the
|
||||
/// max target)
|
||||
/// equals the max target)
|
||||
pub fn one() -> Difficulty {
|
||||
Difficulty { num: BigUint::new(vec![1]) }
|
||||
}
|
||||
|
@ -81,6 +86,20 @@ impl Add<Difficulty> for Difficulty {
|
|||
}
|
||||
}
|
||||
|
||||
impl Mul<Difficulty> for Difficulty {
|
||||
type Output = Difficulty;
|
||||
fn mul(self, other: Difficulty) -> Difficulty {
|
||||
Difficulty { num: self.num * other.num }
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Difficulty> for Difficulty {
|
||||
type Output = Difficulty;
|
||||
fn div(self, other: Difficulty) -> Difficulty {
|
||||
Difficulty { num: self.num / other.num }
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Difficulty {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
let data = self.num.to_bytes_be();
|
||||
|
|
Loading…
Reference in a new issue