diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 81623c71f..44d6712d1 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -18,7 +18,7 @@ //! enough, consensus-relevant constants and short functions should be kept //! here. -use std::cmp::max; +use std::cmp::{max, min}; use std::fmt; use global; @@ -293,8 +293,10 @@ where HeaderInfo::from_diff_scaling(Difficulty::from_num(difficulty), sec_pow_scaling) } +pub const MAX_SECONDARY_SCALING: u64 = (::std::u32::MAX / 70) as u64; + /// Factor by which the secondary proof of work difficulty will be adjusted -fn secondary_pow_scaling(height: u64, diff_data: &Vec) -> u32 { +pub fn secondary_pow_scaling(height: u64, diff_data: &Vec) -> u32 { // median of past scaling factors, scaling is 1 if none found let mut scalings = diff_data .iter() @@ -305,18 +307,30 @@ fn secondary_pow_scaling(height: u64, diff_data: &Vec) -> u32 { } scalings.sort(); let scaling_median = scalings[scalings.len() / 2] as u64; - let secondary_count = diff_data.iter().filter(|n| n.is_secondary).count() as u64; + let secondary_count = max(diff_data.iter().filter(|n| n.is_secondary).count(), 1) as u64; // what's the ideal ratio at the current height let ratio = secondary_pow_ratio(height); + println!( + "-- {} {} {} {}", + scaling_median, + secondary_count, + diff_data.len(), + ratio + ); // adjust the past median based on ideal ratio vs actual ratio - let scaling = scaling_median * secondary_count * 100 / ratio / diff_data.len() as u64; - if scaling == 0 { - 1 + let scaling = scaling_median * diff_data.len() as u64 * ratio / 100 / secondary_count as u64; + + // various bounds + let bounded_scaling = if scaling < scaling_median / 4 || scaling == 0 { + max(scaling_median / 4, 1) + } else if scaling > MAX_SECONDARY_SCALING || scaling > scaling_median * 4 { + min(MAX_SECONDARY_SCALING, scaling_median * 4) } else { - scaling as u32 - } + scaling + }; + bounded_scaling as u32 } /// Median timestamp within the time window starting at `from` with the diff --git a/core/tests/consensus.rs b/core/tests/consensus.rs index b06cf82c8..aee015de9 100644 --- a/core/tests/consensus.rs +++ b/core/tests/consensus.rs @@ -17,10 +17,7 @@ extern crate grin_core as core; extern crate chrono; use chrono::prelude::Utc; -use core::consensus::{ - next_difficulty, valid_header_version, HeaderInfo, BLOCK_TIME_WINDOW, DAMP_FACTOR, - DIFFICULTY_ADJUST_WINDOW, MEDIAN_TIME_INDEX, MEDIAN_TIME_WINDOW, UPPER_TIME_BOUND, -}; +use core::consensus::*; use core::global; use core::pow::Difficulty; use std::fmt::{self, Display}; @@ -514,6 +511,71 @@ fn next_target_adjustment() { ); } +#[test] +fn secondary_pow_scale() { + let window = DIFFICULTY_ADJUST_WINDOW + MEDIAN_TIME_WINDOW; + let mut hi = HeaderInfo::from_diff_scaling(Difficulty::from_num(10), 100); + + // all primary, factor should be multiplied by 4 (max adjustment) so it + // becomes easier to find a high difficulty block + assert_eq!( + secondary_pow_scaling(1, &(0..window).map(|_| hi.clone()).collect()), + 400 + ); + // all secondary on 90%, factor should lose 10% + hi.is_secondary = true; + assert_eq!( + secondary_pow_scaling(1, &(0..window).map(|_| hi.clone()).collect()), + 90 + ); + // all secondary on 1%, should be divided by 4 (max adjustment) + assert_eq!( + secondary_pow_scaling(890_000, &(0..window).map(|_| hi.clone()).collect()), + 25 + ); + // same as above, testing lowest bound + let mut low_hi = HeaderInfo::from_diff_scaling(Difficulty::from_num(10), 3); + low_hi.is_secondary = true; + assert_eq!( + secondary_pow_scaling(890_000, &(0..window).map(|_| low_hi.clone()).collect()), + 1 + ); + // just about the right ratio, also playing with median + let primary_hi = HeaderInfo::from_diff_scaling(Difficulty::from_num(10), 50); + assert_eq!( + secondary_pow_scaling( + 1, + &(0..(window / 10)) + .map(|_| primary_hi.clone()) + .chain((0..(window * 9 / 10)).map(|_| hi.clone())) + .collect() + ), + 100 + ); + // 95% secondary, should come down + assert_eq!( + secondary_pow_scaling( + 1, + &(0..(window / 20)) + .map(|_| primary_hi.clone()) + .chain((0..(window * 95 / 100)).map(|_| hi.clone())) + .collect() + ), + 94 + ); + // 40% secondary, should come up + assert_eq!( + secondary_pow_scaling( + 1, + &(0..(window * 6 / 10)) + .map(|_| primary_hi.clone()) + .chain((0..(window * 4 / 10)).map(|_| hi.clone())) + .collect() + ), + 112 + ); +} + #[test] fn hard_forks() { assert!(valid_header_version(0, 1));