/ diffadjust / blkdiff.py
blkdiff.py
  1  import math, random
  2  
  3  hashpower = [float(x) for x in open('hashpower.csv').readlines()]
  4  
  5  # Target block time
  6  TARGET = 12
  7  # Should be 86400, but can reduce for a quicker sim
  8  SECONDS_IN_DAY = 86400
  9  # Look at the 1/x day exponential moving average
 10  EMA_FACTOR = 0.01
 11  # Damping factor for simple difficulty adjustment
 12  SIMPLE_ADJUST_DAMPING_FACTOR = 20
 13  # Maximum per-block diff adjustment (as fraction of current diff)
 14  SIMPLE_ADJUST_MAX = 0.5
 15  # Damping factor for quadratic difficulty adjustment
 16  QUADRATIC_ADJUST_DAMPING_FACTOR = 3
 17  # Maximum per-block diff adjustment (as fraction of current diff)
 18  QUADRATIC_ADJUST_MAX = 0.5
 19  # Threshold for bounded adjustor
 20  BOUNDED_ADJUST_THRESHOLD = 1.3
 21  # Bounded adjustment factor
 22  BOUNDED_ADJUST_FACTOR = 0.01
 23  # How many blocks back to look
 24  BLKS_BACK = 10
 25  # Naive difficulty adjustment factor
 26  NAIVE_ADJUST_FACTOR = 1/1024.
 27  
 28  
 29  # Produces a value according to the exponential distribution; used
 30  # to determine the time until the next block given an average block
 31  # time of t
 32  def expdiff(t):
 33      return -math.log(random.random()) * t
 34  
 35  
 36  # abs_sqr(3) = 9, abs_sqr(-7) = -49, etc
 37  def abs_sqr(x):
 38      return -(x**2) if x < 0 else x**2
 39  
 40  
 41  # Given an array of the most recent timestamps, and the most recent
 42  # difficulties, compute the next difficulty
 43  def simple_adjust(timestamps, diffs):
 44      if len(timestamps) < BLKS_BACK + 2:
 45          return diffs[-1]
 46      # Total interval between previous block and block a bit further back
 47      delta = timestamps[-2] - timestamps[-2-BLKS_BACK] + 0.0
 48      # Expected interval
 49      expected = TARGET * BLKS_BACK
 50      # Compute adjustment factor
 51      fac = 1 - (delta / expected - 1) / SIMPLE_ADJUST_DAMPING_FACTOR
 52      fac = max(min(fac, 1 + SIMPLE_ADJUST_MAX), 1 - SIMPLE_ADJUST_MAX)
 53      return diffs[-1] * fac
 54  
 55  
 56  # Alternative adjustment algorithm
 57  def quadratic_adjust(timestamps, diffs):
 58      if len(timestamps) < BLKS_BACK + 2:
 59          return diffs[-1]
 60      # Total interval between previous block and block a bit further back
 61      delta = timestamps[-2] - timestamps[-2-BLKS_BACK] + 0.0
 62      # Expected interval
 63      expected = TARGET * BLKS_BACK
 64      # Compute adjustment factor
 65      fac = 1 - abs_sqr(delta / expected - 1) / QUADRATIC_ADJUST_DAMPING_FACTOR
 66      fac = max(min(fac, 1 + QUADRATIC_ADJUST_MAX), 1 - QUADRATIC_ADJUST_MAX)
 67      return diffs[-1] * fac
 68  
 69  
 70  # Alternative adjustment algorithm
 71  def bounded_adjust(timestamps, diffs):
 72      if len(timestamps) < BLKS_BACK + 2:
 73          return diffs[-1]
 74      # Total interval between previous block and block a bit further back
 75      delta = timestamps[-2] - timestamps[-2-BLKS_BACK] + 0.0
 76      # Expected interval
 77      expected = TARGET * BLKS_BACK
 78      if delta / expected > BOUNDED_ADJUST_THRESHOLD:
 79          fac = (1 - BOUNDED_ADJUST_FACTOR)
 80      elif delta / expected < 1 / BOUNDED_ADJUST_THRESHOLD:
 81          fac = (1 + BOUNDED_ADJUST_FACTOR) ** (delta / expected)
 82      else:
 83          fac = 1
 84      return diffs[-1] * fac
 85  
 86  
 87  # Old Ethereum algorithm
 88  def old_adjust(timestamps, diffs):
 89      if len(timestamps) < 2:
 90          return diffs[-1]
 91      delta = timestamps[-1] - timestamps[-2]
 92      expected = TARGET * 0.693
 93      if delta > expected:
 94          fac = 1 - NAIVE_ADJUST_FACTOR
 95      else:
 96          fac = 1 + NAIVE_ADJUST_FACTOR
 97      return diffs[-1] * fac
 98  
 99  
100  def test(source, adjust):
101      # Variables to keep track of for stats purposes
102      ema = maxema = minema = TARGET
103      lthalf, gtdouble, lttq, gtft = 0, 0, 0, 0
104      count = 0
105      # Block times
106      times = [0]
107      # Block difficulty values
108      diffs = [source[0]]
109      # Next time to print status update
110      nextprint = 10**6
111      # Main loop
112      while times[-1] < len(source) * SECONDS_IN_DAY:
113          # Print status update every 10**6 seconds
114          if times[-1] > nextprint:
115              print '%d out of %d processed, ema %f' % \
116                  (times[-1], len(source) * SECONDS_IN_DAY, ema)
117              nextprint += 10**6
118          # Grab hashpower from data source
119          hashpower = source[int(times[-1] // SECONDS_IN_DAY)]
120          # Calculate new difficulty
121          diffs.append(adjust(times, diffs))
122          # Calculate next block time
123          times.append(times[-1] + expdiff(diffs[-1] / hashpower))
124          # Calculate min and max ema
125          ema = ema * (1 - EMA_FACTOR) + (times[-1] - times[-2]) * EMA_FACTOR
126          minema = min(minema, ema)
127          maxema = max(maxema, ema)
128          count += 1
129          # Keep track of number of blocks we are below 75/50% or above
130          # 133/200% of target
131          if ema < TARGET * 0.75:
132              lttq += 1
133              if ema < TARGET * 0.5:
134                  lthalf += 1
135          elif ema > TARGET * 1.33333:
136              gtft += 1
137              if ema > TARGET * 2:
138                  gtdouble += 1
139          # Pop items to save memory
140          if len(times) > 2000:
141              times.pop(0)
142              diffs.pop(0)
143      print 'min', minema, 'max', maxema, 'avg', times[-1] / count, \
144          'ema < half', lthalf * 1.0 / count, \
145          'ema > double', gtdouble * 1.0 / count, \
146          'ema < 3/4', lttq * 1.0 / count, \
147          'ema > 4/3', gtft * 1.0 / count
148  
149  # Example usage
150  # blkdiff.test(blkdiff.hashpower, blkdiff.simple_adjust)