diff --git a/diffadjust/blkdiff.py b/diffadjust/blkdiff.py index b594874..41de46f 100644 --- a/diffadjust/blkdiff.py +++ b/diffadjust/blkdiff.py @@ -2,98 +2,124 @@ import math, random hashpower = [float(x) for x in open('hashpower.csv').readlines()] -target = 12 -seconds_in_day = 86400 -ema_factor = 0.01 -f = 20 -sqrf = 3 -threshold = 1.3 -adj_factor = 0.01 -maxadjust = 0.5 -blks_back = 10 +# Target block time +TARGET = 12 +# Should be 86400, but can reduce for a quicker sim +SECONDS_IN_DAY = 86400 +# Look at the 1/x day exponential moving average +EMA_FACTOR = 0.01 +# Damping factor for simple difficulty adjustment +SIMPLE_ADJUST_DAMPING_FACTOR = 20 +# Maximum per-block diff adjustment (as fraction of current diff) +SIMPLE_ADJUST_MAX = 0.5 +# Damping factor for quadratic difficulty adjustment +QUADRATIC_ADJUST_DAMPING_FACTOR = 3 +# Maximum per-block diff adjustment (as fraction of current diff) +QUADRATIC_ADJUST_MAX = 0.5 +# Threshold for bounded adjustor +BOUNDED_ADJUST_THRESHOLD = 1.3 +# Bounded adjustment factor +BOUNDED_ADJUST_FACTOR = 0.01 +# How many blocks back to look +BLKS_BACK = 10 +# Produces a value according to the exponential distribution; used +# to determine the time until the next block given an average block +# time of t def expdiff(t): return -math.log(random.random()) * t -def calc_threshold_time(p, t): - return t * -math.log(1 - p) - - +# abs_sqr(3) = 9, abs_sqr(-7) = -49, etc def abs_sqr(x): return -(x**2) if x < 0 else x**2 +# Given an array of the most recent timestamps, and the most recent +# difficulties, compute the next difficulty def simple_adjust(timestamps, diffs): - if len(timestamps) < blks_back + 2: + if len(timestamps) < BLKS_BACK + 2: return diffs[-1] # Total interval between previous block and block a bit further back - delta = timestamps[-2] - timestamps[-2-blks_back] + 0.0 + delta = timestamps[-2] - timestamps[-2-BLKS_BACK] + 0.0 # Expected interval - expected = target * blks_back - fac = max(min(1 - (delta / expected - 1) / f, 1+maxadjust), 1-maxadjust) + expected = TARGET * BLKS_BACK + # Compute adjustment factor + fac = 1 - (delta / expected - 1) / SIMPLE_ADJUST_DAMPING_FACTOR + fac = max(min(fac, 1 + SIMPLE_ADJUST_MAX), 1 - SIMPLE_ADJUST_MAX) return diffs[-1] * fac +# Alternative adjustment algorithm def quadratic_adjust(timestamps, diffs): - if len(timestamps) < blks_back + 2: + if len(timestamps) < BLKS_BACK + 2: return diffs[-1] # Total interval between previous block and block a bit further back - delta = timestamps[-2] - timestamps[-2-blks_back] + 0.0 + delta = timestamps[-2] - timestamps[-2-BLKS_BACK] + 0.0 # Expected interval - expected = target * blks_back - fac = max(min(1 - abs_sqr(delta / expected - 1) / sqrf, - 1+maxadjust), 1-maxadjust) + expected = TARGET * BLKS_BACK + # Compute adjustment factor + fac = 1 - abs_sqr(delta / expected - 1) / QUADRATIC_ADJUST_DAMPING_FACTOR + fac = max(min(fac, 1 + QUADRATIC_ADJUST_MAX), 1 - QUADRATIC_ADJUST_MAX) return diffs[-1] * fac +# Alternative adjustment algorithm def bounded_adjust(timestamps, diffs): - if len(timestamps) < blks_back + 2: + if len(timestamps) < BLKS_BACK + 2: return diffs[-1] # Total interval between previous block and block a bit further back - delta = timestamps[-2] - timestamps[-2-blks_back] + 0.0 + delta = timestamps[-2] - timestamps[-2-BLKS_BACK] + 0.0 # Expected interval - expected = target * blks_back - if delta / expected > threshold: - fac = (1 - adj_factor) - elif delta / expected < 1 / threshold: - fac = (1 + adj_factor) ** (delta / expected) + expected = TARGET * BLKS_BACK + if delta / expected > BOUNDED_ADJUST_THRESHOLD: + fac = (1 - BOUNDED_ADJUST_FACTOR) + elif delta / expected < 1 / BOUNDED_ADJUST_THRESHOLD: + fac = (1 + BOUNDED_ADJUST_FACTOR) ** (delta / expected) else: fac = 1 return diffs[-1] * fac def test(source, adjust): - ema = maxema = minema = target + # Variables to keep track of for stats purposes + ema = maxema = minema = TARGET lthalf, gtdouble, lttq, gtft = 0, 0, 0, 0 - times = [0] - diffs = [source[0]] - nextprint = 10**6 count = 0 - while times[-1] < len(source) * seconds_in_day: + # Block times + times = [0] + # Block difficulty values + diffs = [source[0]] + # Next time to print status update + nextprint = 10**6 + # Main loop + while times[-1] < len(source) * SECONDS_IN_DAY: + # Print status update every 10**6 seconds if times[-1] > nextprint: - print '%d out of %d processed' % \ - (times[-1], len(source) * seconds_in_day) + print '%d out of %d processed, ema %f' % \ + (times[-1], len(source) * SECONDS_IN_DAY, ema) nextprint += 10**6 # Grab hashpower from data source - hashpower = source[int(times[-1] // seconds_in_day)] + hashpower = source[int(times[-1] // SECONDS_IN_DAY)] # Calculate new difficulty diffs.append(adjust(times, diffs)) # Calculate next block time times.append(times[-1] + expdiff(diffs[-1] / hashpower)) # Calculate min and max ema - ema = ema * (1 - ema_factor) + (times[-1] - times[-2]) * ema_factor + ema = ema * (1 - EMA_FACTOR) + (times[-1] - times[-2]) * EMA_FACTOR minema = min(minema, ema) maxema = max(maxema, ema) count += 1 - if ema < target * 0.75: + # Keep track of number of blocks we are below 75/50% or above + # 133/200% of target + if ema < TARGET * 0.75: lttq += 1 - if ema < target * 0.5: + if ema < TARGET * 0.5: lthalf += 1 - elif ema > target * 1.33333: + elif ema > TARGET * 1.33333: gtft += 1 - if ema > target * 2: + if ema > TARGET * 2: gtdouble += 1 # Pop items to save memory if len(times) > 2000: @@ -104,3 +130,6 @@ def test(source, adjust): 'ema > double', gtdouble * 1.0 / count, \ 'ema < 3/4', lttq * 1.0 / count, \ 'ema > 4/3', gtft * 1.0 / count + +# Example usage +# blkdiff.test(blkdiff.hashpower, blkdiff.simple_adjust)