Actually, we can improve it even more if we use a very big value for kimoto. It is good according to your test because we update the difficulty at every block.
import datetime
import random
import numpy as np
import matplotlib.pyplot as plt
# sudo apt-get install python-tk
# pip2 install numpy matplotlib
class RetargetTest():
# experiment with the number of work packages
def __init__(self):
self.current_time = datetime.datetime.now()
self.works_to_create = 3
self.generate_blocks = 100
self.current_height = 0
self.blockchain = []
self.work_packages = []
self.base_target = 0x000000ffffffffffffffffffffffffff
self.stretch_number_pows = True
self.do_not_randomize_block_times_but_do_always_60_sec = True
self.new_miner_every_xth_second = 10
self.how_many_miners_come_or_go = 70242
self.initial_miners = 50
self.miners_kick_in_at_block=50
self.miners_drop_at = 0
self.miners_drop_for=20
self.jitter_size = 0
def seeder(self, hasher):
random.seed(hasher)
def randomize_params(self):
self.generate_blocks = random.randint(80,500)
self.new_miner_every_xth_second = random.randint(5,50)
self.how_many_miners_come_or_go = random.randint(0,1000000)
self.initial_miners = random.randint(0,1000000)
self.miners_kick_in_at_block = random.randint(0,40)
self.jitter_size = random.randint(1,7)
self.miners_drop_at = random.randint(self.generate_blocks/2,self.generate_blocks)
pass
def create_block(self,timestamp, num_pow):
return {'time_stamp' : timestamp, 'num_pow' : num_pow, 'first_work_factor':0}
def create_work(self,idx, factor, target):
return {'id': idx, 'base_executions_per_second' : factor, 'target' : target}
def addSecs(self,tm, secs):
fulldate = tm + datetime.timedelta(seconds=secs)
return fulldate
def randomDuration(self):
if self.do_not_randomize_block_times_but_do_always_60_sec:
return 60
else:
return int(random.uniform(25, 120))
def currently_active_miners(self,current_height):
if self.current_height return 0
if self.current_height>=self.miners_drop_at and self.current_height<=self.miners_drop_at+self.miners_drop_for:
return 0
# get the current active number of miners in relation of blockchain height,
# but the number of miners increases by 1 every 10 blocks
increases = int(self.current_height/self.new_miner_every_xth_second) * self.how_many_miners_come_or_go
return self.initial_miners+increases
def miner_pows_based_on_target(self,work, height, dur):
current_target = work["target"]
factor = (current_target / self.base_target) * 1.0*dur/60.0
actual_pow_mined = work["base_executions_per_second"]
# random jitter
actual_pow_mined = abs((actual_pow_mined - self.jitter_size) + random.uniform(self.jitter_size,2*self.jitter_size)) * self.currently_active_miners(height)
if actual_pow_mined < 0:
actual_pow_mined = 0
actual_pow_mined = actual_pow_mined *factor
# rate limit to 20 pows per block
if actual_pow_mined > 20:
actual_pow_mined = 20
if actual_pow_mined < 0:
actual_pow_mined = 0
return actual_pow_mined
def kimoto(self,x):
return 1 + (0.7084 * pow(((x)/(144)), -1.228));
def retarget_work(self,block, x):
targetI = x["target"]
pastMass = 0
counter = 0
current_block = block
current_block_timestamp = self.blockchain[current_block]["time_stamp"]
adjustment = 0
isFull = True
fullCnt = 0
isEmpty = True
max_block_reading = 144
emptyCnt = 0
while isFull or isEmpty:
if isFull and self.blockchain[current_block]["num_pow"][x["id"]] == 20:
fullCnt += 1
else:
isFull = False
if isEmpty and self.blockchain[current_block]["num_pow"][x["id"]] == 0:
emptyCnt += 1
else:
isEmpty = False
current_block -= 1
if current_block < 1:
break
current_block = block
while True:
counter += 1
pastMass += self.blockchain[current_block]["num_pow"][x["id"]]
if current_block_timestamp < self.blockchain[current_block-1]["time_stamp"]:
current_block_timestamp = self.blockchain[current_block-1]["time_stamp"]
seconds_passed = (current_block_timestamp - self.blockchain[current_block-1]["time_stamp"]).seconds
current_block -= 1
if seconds_passed < 1:
seconds_passed = 1
trs_per_second = float(pastMass) / float(seconds_passed)
target_per_second = 10.0 / 60.0
if trs_per_second > 0:
adjustment = target_per_second / trs_per_second
kim = self.kimoto(pastMass * 1000)
if adjustment > kim or adjustment < (1.0/kim):
break
else:
adjustment = 1
if current_block < 1 or counter == max_block_reading:
break
if fullCnt > 1:
adjustment = adjustment / (1 << fullCnt)
if emptyCnt > 1:
adjustment = adjustment * (2 << emptyCnt)
targetI = targetI * adjustment
if targetI>self.base_target:
targetI = self.base_target
if x["id"] == 0:
self.blockchain[block]["first_work_factor"] = adjustment
x["target"] = targetI
#print "Retarget using",counter,"blocks","fullcnt",fullCnt,"emptyCnt",emptyCnt
def retarget_works(self,block):
for x in self.work_packages:
self.retarget_work(block,x)
def reset_chain(self):
self.blockchain = []
self.work_packages = []
self.current_height = 0
self.current_time = datetime.datetime.now()
# Here we create up to three different work objects
if self.works_to_create>=1:
self.work_packages.append(self.create_work(0, 20, self.base_target))
if self.works_to_create>=2:
self.work_packages.append(self.create_work(1, 60, self.base_target))
if self.works_to_create>=3:
self.work_packages.append(self.create_work(2, 35, self.base_target))
def generate_chain(self):
while self.current_height < self.generate_blocks:
if self.current_height%1000==0:
print " -> generated block",self.current_height
dur = self.randomDuration()
self.current_time = self.addSecs(self.current_time,dur) # random block generation time
block_pows = {}
for x in self.work_packages:
num_pow = self.miner_pows_based_on_target(x, self.current_height, dur) # mine some POW depending on the current difficulty
block_pows[x["id"]] = num_pow
self.blockchain.append(self.create_block(self.current_time, block_pows))
self.retarget_works(self.current_height) # This retargeting method is the "critical part here"
self.current_height = self.current_height + 1
def get_total_error(self):
values = []
ideal = []
for idx in range(len(self.blockchain)):
if idx == 0:
continue
x = self.blockchain[idx]
x_minus_one = self.blockchain[idx-1]
time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
strech_normalizer = time_passed / 60.0
sum_x = 0
for y in x["num_pow"]:
sum_x += x["num_pow"][y]
if sum_x == 0:
ideal.append(0)
else:
if self.stretch_number_pows == False:
ideal.append(self.works_to_create*10*strech_normalizer)
else:
ideal.append(self.works_to_create*10)
if self.stretch_number_pows == False:
values.append(sum_x)
else:
values.append(sum_x/strech_normalizer)
#print values
#print ideal
total_error = 0
for x in range(len(values)):
soll = ideal[x]
ist = values[x]
total_error += abs(soll-ist)
return total_error/len(values)
def plot(self,run):
values = []
target_factors = []
ideal = []
for idx in range(len(self.blockchain)):
if idx == 0:
continue
x = self.blockchain[idx]
x_minus_one = self.blockchain[idx-1]
time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
strech_normalizer = time_passed / 60.0
sum_x = 0
for y in x["num_pow"]:
sum_x += x["num_pow"][y]
if sum_x == 0:
ideal.append(0)
else:
if self.stretch_number_pows == False:
ideal.append(self.works_to_create*10*strech_normalizer)
else:
ideal.append(self.works_to_create*10)
if self.stretch_number_pows == False:
values.append(sum_x)
else:
values.append(sum_x/strech_normalizer)
x = range(self.generate_blocks)[1:]
y = values
#print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)
#fig = plt.figure()
ax0 = plt.subplot(211)
if self.stretch_number_pows:
ax0.set_ylabel('POW rate per 60s', color='b')
else:
ax0.set_ylabel('POWs per Block', color='b')
ax0.set_xlabel('Block height')
ax0.plot(x,y,'-o',x,ideal,'r--')
values = []
ideal = []
target_factors = []
for idx in range(len(self.blockchain)):
if idx == 0:
continue
x = self.blockchain[idx]
x_minus_one = self.blockchain[idx-1]
time_passed = (x["time_stamp"] - x_minus_one["time_stamp"]).seconds
strech_normalizer = time_passed / 60.0
sum_x = 0
sum_x += x["num_pow"][0]
if sum_x == 0:
ideal.append(0)
else:
if self.stretch_number_pows == False:
ideal.append(10*strech_normalizer)
else:
ideal.append(10)
#print "sumx",sum_x
if self.stretch_number_pows == False:
values.append(sum_x)
else:
values.append(sum_x/strech_normalizer)
x = range(self.generate_blocks)[1:]
y = values
plt.title('All Works: Total POWs')
#print "LEN: x",len(x),"y",len(y),"ideal",len(ideal)
ax1 = plt.subplot(212)
ax1.plot(x,y,'-o',x,ideal,'r--')
ax1.set_xlabel('Block Height')
# Make the y-axis label and tick labels match the line color.
if self.stretch_number_pows:
ax1.set_ylabel('POW rate per 60s', color='b')
else:
ax1.set_ylabel('POWs per Block', color='b')
for tl in ax1.get_yticklabels():
tl.set_color('b')
ax2 = ax1.twinx()
ax2.set_ylim(0.4, 1.6)
ax2.bar(x,[x["first_work_factor"] for x in self.blockchain][1:],0.45,color='#deb0b0', alpha=0.2)
ax2.set_ylabel('Retargeting Factor', color='r')
for tl in ax2.get_yticklabels():
tl.set_color('r')
plt.title('First Work: POWs + Retargeting Factor')
plt.savefig('render-' + str(run) + '.png')
plt.close()
Edit: Now max value is 4.71