lottery.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import matplotlib.pyplot as plt
  2. from tqdm import tqdm
  3. import time
  4. from datetime import timedelta
  5. from core.darkie import *
  6. from pid.cascade import *
  7. from tqdm import tqdm
  8. import random
  9. class DarkfiTable:
  10. def __init__(self, airdrop, running_time, controller_type=CONTROLLER_TYPE_DISCRETE, kp=0, ki=0, kd=0, dt=1, kc=0, ti=0, td=0, ts=0, debug=False, r_kp=0, r_ki=0, r_kd=0):
  11. self.Sigma=airdrop
  12. self.darkies = []
  13. self.running_time=running_time
  14. self.start_time=None
  15. self.end_time=None
  16. self.secondary_pid = SecondaryDiscretePID(kp=kp, ki=ki, kd=kd) if controller_type==CONTROLLER_TYPE_DISCRETE else SecondaryTakahashiPID(kc=kc, ti=ti, td=td, ts=ts)
  17. print('secondary min/max : {}/{}'.format(self.secondary_pid.clip_min, self.secondary_pid.clip_max))
  18. self.primary_pid = PrimaryDiscretePID(kp=r_kp, ki=r_ki, kd=r_kd) if controller_type==CONTROLLER_TYPE_DISCRETE else PrimaryTakahashiPID(kc=kc, ti=ti, td=td, ts=ts)
  19. print('primary min/max : {}/{}'.format(self.primary_pid.clip_min, self.primary_pid.clip_max))
  20. self.debug=debug
  21. self.rewards = []
  22. self.winners = []
  23. def add_darkie(self, darkie):
  24. self.darkies+=[darkie]
  25. """
  26. for every slot under given running time, set f based off prior on-chain public \
  27. values, set sigmas, f, update vesting, stake for every stakeholder, resolve \
  28. forks.
  29. @param rand_running_time: randomization running time state
  30. @param debug: debug option
  31. @param hp: high precision option
  32. @returns: acc, avg_apy, avg_reward, stake_ratio, avg_apr
  33. """
  34. def background(self, rand_running_time=True, debug=False, hp=True):
  35. self.debug=debug
  36. self.start_time=time.time()
  37. feedback=0 # number leads in previous slot
  38. # random running time
  39. rand_running_time = random.randint(1,self.running_time) if rand_running_time else self.running_time
  40. self.running_time = rand_running_time
  41. rt_range = tqdm(np.arange(0,self.running_time, 1))
  42. merge_length = 0
  43. # loop through slots
  44. for count in rt_range:
  45. merge_length = 0
  46. # calculate probability of winning owning 100% of stake
  47. f = self.secondary_pid.pid_clipped(float(feedback), debug)
  48. # calculate reward value every epoch
  49. if count%EPOCH_LENGTH == 0:
  50. acc = self.secondary_pid.acc()
  51. reward = self.primary_pid.pid_clipped(acc, debug)
  52. self.rewards += [reward]
  53. #note! thread overhead is 10X slower than sequential node execution!
  54. total_stake = 0
  55. for i in range(len(self.darkies)):
  56. self.darkies[i].set_sigma_feedback(self.Sigma, feedback, f, count, hp)
  57. self.darkies[i].update_vesting()
  58. self.darkies[i].run(hp)
  59. total_stake += self.darkies[i].stake
  60. # count number of leads per slot
  61. winners=0
  62. # count secondary controller feedback
  63. for i in range(len(self.darkies)):
  64. winners += self.darkies[i].won_hist[-1]
  65. self.winners +=[winners]
  66. feedback = winners
  67. ################
  68. # resolve fork #
  69. ################
  70. if self.winners[-1]==1:
  71. for i in range(len(self.darkies)):
  72. if self.darkies[i].won_hist[-1]:
  73. if random.random() < len(self.darkies)**-1:
  74. self.darkies.remove(self.darkies[i])
  75. print('stakeholder {} slashed'.format(i))
  76. else:
  77. self.darkies[i].update_stake(self.rewards[-1])
  78. break
  79. # resolve finalization
  80. self.Sigma += self.rewards[-1]
  81. # resync nodes
  82. for i in reversed(self.winners[:-1]):
  83. if i !=1:
  84. merge_length+=1
  85. else:
  86. break
  87. for i in range(merge_length):
  88. resync_slot_id = count-(i+1)
  89. resync_reward_id = int((resync_slot_id)/EPOCH_LENGTH)
  90. resync_reward = self.rewards[resync_reward_id]
  91. # resyncing depends on the random branch chosen,
  92. # it's simulated by choosing first wining node
  93. darkie_winning_idx = 0
  94. random.shuffle(self.darkies)
  95. for darkie_idx in range(len(self.darkies)):
  96. if self.darkies[darkie_idx].won_hist[resync_slot_id]:
  97. darkie_winning_idx = darkie_idx
  98. break
  99. self.darkies[darkie_winning_idx].resync_stake(resync_reward)
  100. self.Sigma += resync_reward
  101. #################
  102. # fork resolved #
  103. #################
  104. rt_range.set_description('epoch: {}, fork: {} issuance {} DRK, acc: {}%, stake = {}%, sr: {}%, reward:{}, apr: {}%'.format(int(count/EPOCH_LENGTH), merge_length, round(self.Sigma,2), round(acc*100, 2), round(total_stake/self.Sigma*100 if self.Sigma>0 else 0,2), round(self.avg_stake_ratio()*100,2) , round(self.rewards[-1],2), round(self.avg_apr()*100,2)))
  105. #assert round(total_stake,1) <= round(self.Sigma,1), 'stake: {}, sigma: {}'.format(total_stake, self.Sigma)
  106. count+=1
  107. self.end_time=time.time()
  108. avg_reward = sum(self.rewards)/len(self.rewards)
  109. stake_ratio = self.avg_stake_ratio()
  110. avg_apy = self.avg_apy()
  111. avg_apr = self.avg_apr()
  112. return self.secondary_pid.acc_percentage(), avg_apy, avg_reward, stake_ratio, avg_apr
  113. """
  114. average APY (with compound interest added every epoch) ,
  115. scaled to running time for all nodes
  116. @returns: average APY for all nodes
  117. """
  118. def avg_apy(self):
  119. return Num(sum([darkie.apy_scaled_to_runningtime(self.rewards) for darkie in self.darkies])/len(self.darkies))
  120. """
  121. average APR scaled to running time for all nodes
  122. @returns: average APR for all nodes
  123. """
  124. def avg_apr(self):
  125. return Num(sum([darkie.apr_scaled_to_runningtime() for darkie in self.darkies])/len(self.darkies))
  126. """
  127. returns: average stake ratio for all nodes
  128. """
  129. def avg_stake_ratio(self):
  130. return sum([darkie.staked_tokens_ratio() for darkie in self.darkies]) / len(self.darkies)
  131. """
  132. write lottery reward log
  133. """
  134. def write(self):
  135. elapsed=self.end_time-self.start_time
  136. for id, darkie in enumerate(self.darkies):
  137. darkie.write(id)
  138. if self.debug:
  139. print("total time: {}, slot time: {}".format(str(timedelta(seconds=elapsed)), str(timedelta(seconds=elapsed/self.running_time))))
  140. self.secondary_pid.write()
  141. with open('log/rewards.log', 'w+') as f:
  142. buff = ','.join([str(i) for i in self.rewards])
  143. f.write(buff)