|
@@ -10,7 +10,7 @@ import random
|
|
|
class DarkfiTable:
|
|
class DarkfiTable:
|
|
|
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, fee_kp=0, fee_ki=0, fee_kd=0):
|
|
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, fee_kp=0, fee_ki=0, fee_kd=0):
|
|
|
self.Sigma=airdrop
|
|
self.Sigma=airdrop
|
|
|
- self.darkies = []
|
|
|
|
|
|
|
+ self.darkies = {}
|
|
|
self.running_time=running_time
|
|
self.running_time=running_time
|
|
|
self.start_time=None
|
|
self.start_time=None
|
|
|
self.end_time=None
|
|
self.end_time=None
|
|
@@ -26,9 +26,11 @@ class DarkfiTable:
|
|
|
self.base_fee = []
|
|
self.base_fee = []
|
|
|
self.tips_avg = []
|
|
self.tips_avg = []
|
|
|
self.cc_diff = []
|
|
self.cc_diff = []
|
|
|
|
|
+ self.basefee = [FEE_MAX]
|
|
|
|
|
+ self.slashed_idxs = []
|
|
|
|
|
|
|
|
def add_darkie(self, darkie):
|
|
def add_darkie(self, darkie):
|
|
|
- self.darkies+=[darkie]
|
|
|
|
|
|
|
+ self.darkies[darkie.idx] = darkie
|
|
|
|
|
|
|
|
"""
|
|
"""
|
|
|
for every slot under given running time, set f based off prior on-chain public \
|
|
for every slot under given running time, set f based off prior on-chain public \
|
|
@@ -60,18 +62,19 @@ class DarkfiTable:
|
|
|
total_stake = 0
|
|
total_stake = 0
|
|
|
Ys = []
|
|
Ys = []
|
|
|
Ts = []
|
|
Ts = []
|
|
|
- for i in range(len(self.darkies)):
|
|
|
|
|
- self.darkies[i].set_sigma_feedback(self.Sigma, self.winners[-1], f, slot, hp)
|
|
|
|
|
- diff = self.darkies[i].update_vesting()
|
|
|
|
|
|
|
+ for key in self.darkies.keys():
|
|
|
|
|
+ self.darkies[key].set_sigma_feedback(self.Sigma, self.winners[-1], f, slot, hp)
|
|
|
|
|
+ diff = self.darkies[key].update_vesting()
|
|
|
self.Sigma += diff
|
|
self.Sigma += diff
|
|
|
- y, T = self.darkies[i].run(hp)
|
|
|
|
|
|
|
+ y, T = self.darkies[key].run(hp)
|
|
|
Ys+=[y]
|
|
Ys+=[y]
|
|
|
Ts+=[T]
|
|
Ts+=[T]
|
|
|
- total_stake += self.darkies[i].stake
|
|
|
|
|
|
|
+ total_stake += self.darkies[key].stake
|
|
|
# slot secondary controller feedback
|
|
# slot secondary controller feedback
|
|
|
- self.winners += [sum([self.darkies[i].won_hist[-1] for i in range(len(self.darkies))])]
|
|
|
|
|
|
|
+ self.winners += [sum([self.darkies[key].won_hist[-1] for key in self.darkies.keys()])]
|
|
|
if self.winners[-1]==1:
|
|
if self.winners[-1]==1:
|
|
|
- is_slashed = self.reward_slash_lead(debug)
|
|
|
|
|
|
|
+ is_slashed, idx = self.reward_slash_lead(slot, debug)
|
|
|
|
|
+ self.slashed_idxs += [idx]
|
|
|
if is_slashed==False:
|
|
if is_slashed==False:
|
|
|
self.resolve_fork(slot, debug)
|
|
self.resolve_fork(slot, debug)
|
|
|
avg_y = sum(Ys)/len(Ys)
|
|
avg_y = sum(Ys)/len(Ys)
|
|
@@ -95,20 +98,21 @@ class DarkfiTable:
|
|
|
|
|
|
|
|
@returns: True if slashed False otherwise
|
|
@returns: True if slashed False otherwise
|
|
|
"""
|
|
"""
|
|
|
- def reward_slash_lead(self, debug=False):
|
|
|
|
|
|
|
+ def reward_slash_lead(self, slot, debug=False):
|
|
|
# reward the single lead
|
|
# reward the single lead
|
|
|
- for i in range(len(self.darkies)):
|
|
|
|
|
- if self.darkies[i].won_hist[-1]:
|
|
|
|
|
|
|
+ for key in self.darkies.keys():
|
|
|
|
|
+ if self.darkies[key].won_hist[-1]:
|
|
|
if random.random() < len(self.darkies)**-1:
|
|
if random.random() < len(self.darkies)**-1:
|
|
|
- self.darkies.remove(self.darkies[i])
|
|
|
|
|
- print('stakeholder {} slashed'.format(i))
|
|
|
|
|
- return True
|
|
|
|
|
|
|
+ self.darkies.pop(key, None)
|
|
|
|
|
+ print('stakeholder {} slashed'.format(key))
|
|
|
|
|
+ return True, key
|
|
|
else:
|
|
else:
|
|
|
- self.darkies[i].update_stake(self.rewards[-1])
|
|
|
|
|
|
|
+ self.darkies[key].update_stake(self.rewards[-1])
|
|
|
self.Sigma += self.rewards[-1]
|
|
self.Sigma += self.rewards[-1]
|
|
|
- self.tx_fees(i, debug)
|
|
|
|
|
|
|
+ if slot > HEADSTART_AIRDROP:
|
|
|
|
|
+ self.tx_fees(key, debug)
|
|
|
break
|
|
break
|
|
|
- return False
|
|
|
|
|
|
|
+ return False, -1
|
|
|
|
|
|
|
|
"""
|
|
"""
|
|
|
resolve fork, for slots with multiple leads, shuffle nodes, and reward first winner.
|
|
resolve fork, for slots with multiple leads, shuffle nodes, and reward first winner.
|
|
@@ -122,10 +126,11 @@ class DarkfiTable:
|
|
|
# resyncing depends on the random branch chosen,
|
|
# resyncing depends on the random branch chosen,
|
|
|
# it's simulated by choosing first wining node
|
|
# it's simulated by choosing first wining node
|
|
|
darkie_winning_idx = -1
|
|
darkie_winning_idx = -1
|
|
|
- random.shuffle(self.darkies)
|
|
|
|
|
- for darkie_idx in range(len(self.darkies)):
|
|
|
|
|
- if self.darkies[darkie_idx].won_hist[resync_slot_id]:
|
|
|
|
|
- self.darkies[darkie_idx].resync_stake(resync_reward)
|
|
|
|
|
|
|
+ keys_list = list(self.darkies.keys())
|
|
|
|
|
+ random.shuffle(keys_list)
|
|
|
|
|
+ for key in keys_list:
|
|
|
|
|
+ if self.darkies[key].won_hist[resync_slot_id]:
|
|
|
|
|
+ self.darkies[key].resync_stake(resync_reward)
|
|
|
self.Sigma += resync_reward
|
|
self.Sigma += resync_reward
|
|
|
|
|
|
|
|
def merge_length(self):
|
|
def merge_length(self):
|
|
@@ -143,22 +148,29 @@ class DarkfiTable:
|
|
|
"""
|
|
"""
|
|
|
def tx_fees(self, darkie_lead_idx, debug=False):
|
|
def tx_fees(self, darkie_lead_idx, debug=False):
|
|
|
txs = []
|
|
txs = []
|
|
|
- for darkie in self.darkies:
|
|
|
|
|
|
|
+ for key in self.darkies.keys():
|
|
|
# make sure tip is covered by darkie stake
|
|
# make sure tip is covered by darkie stake
|
|
|
- txs += [darkie.tx(self.rewards[-1])]
|
|
|
|
|
- ret, actual_cc = DarkfiTable.auction(txs)
|
|
|
|
|
|
|
+ tx = self.darkies[key].tx(self.rewards[-1])
|
|
|
|
|
+ if self.darkies[key].stake > 0 and self.darkies[key].stake >= (self.rewards[-1] + FEE_MAX):
|
|
|
|
|
+ assert tx.idx == self.darkies[key].idx
|
|
|
|
|
+ assert key == tx.idx, 'key: {}, idx: {}'.format(key, tx.idx)
|
|
|
|
|
+ txs += [tx]
|
|
|
|
|
+ ret, actual_cc = self.auction(txs)
|
|
|
self.computational_cost += [actual_cc]
|
|
self.computational_cost += [actual_cc]
|
|
|
|
|
+ basefee = self.basefee_pid.pid_clipped(self.computational_cost[-1], debug)
|
|
|
|
|
+ self.basefee += [basefee]
|
|
|
self.cc_diff += [MAX_BLOCK_CC - actual_cc]
|
|
self.cc_diff += [MAX_BLOCK_CC - actual_cc]
|
|
|
tips = ret[0]
|
|
tips = ret[0]
|
|
|
idxs = ret[1]
|
|
idxs = ret[1]
|
|
|
self.tips_avg += [tips/len(idxs) if len(idxs)>0 else 0]
|
|
self.tips_avg += [tips/len(idxs) if len(idxs)>0 else 0]
|
|
|
- basefee = self.basefee_pid.pid_clipped(self.computational_cost[-1], debug)
|
|
|
|
|
self.base_fee+=[basefee]
|
|
self.base_fee+=[basefee]
|
|
|
- assert tips == sum(txs[idx].tip for idx in idxs), 'tips: {}, sum(tips): {}'.format(tips, sum(tx.tip for tx in txs))
|
|
|
|
|
- for idx in idxs:
|
|
|
|
|
- fee = txs[idx].tip+basefee
|
|
|
|
|
|
|
+ assert tips == sum(txs[idx[0]].tip for idx in idxs), 'tips: {}, sum(tips): {}'.format(tips, sum(tx.tip for tx in txs))
|
|
|
|
|
+ for i, idx in idxs:
|
|
|
|
|
+ fee = txs[i].tip+basefee
|
|
|
|
|
+ assert idx == txs[i].idx
|
|
|
|
|
+ assert self.darkies[idx].stake > 0
|
|
|
|
|
+ assert self.darkies[idx].stake-fee >= -1, 'stake: {}, fee: {}'.format(self.darkies[txs[i].idx].stake, fee)
|
|
|
self.darkies[idx].pay_fee(fee)
|
|
self.darkies[idx].pay_fee(fee)
|
|
|
- #print("charging darkie[{}]: {} DRK per tx of length: {}, burning: {}".format(idx, fee, len(txs[idx]), basefee))
|
|
|
|
|
self.darkies[darkie_lead_idx].pay_fee(-1*tips)
|
|
self.darkies[darkie_lead_idx].pay_fee(-1*tips)
|
|
|
|
|
|
|
|
# subtract base fee from total stake
|
|
# subtract base fee from total stake
|
|
@@ -166,32 +178,32 @@ class DarkfiTable:
|
|
|
|
|
|
|
|
"""
|
|
"""
|
|
|
average APY (with compound interest added every epoch) ,
|
|
average APY (with compound interest added every epoch) ,
|
|
|
- scaled to running time for all nodes
|
|
|
|
|
|
|
+ scapled to running time for all nodes
|
|
|
@returns: average APY for all nodes
|
|
@returns: average APY for all nodes
|
|
|
"""
|
|
"""
|
|
|
def avg_apy(self):
|
|
def avg_apy(self):
|
|
|
- return Num(sum([darkie.apy_scaled_to_runningtime(self.rewards) for darkie in self.darkies])/len(self.darkies))
|
|
|
|
|
|
|
+ return Num(sum([self.darkies[key].apy_scaled_to_runningtime(self.rewards) for key in self.darkies.keys()])/len(self.darkies))
|
|
|
|
|
|
|
|
"""
|
|
"""
|
|
|
average APR scaled to running time for all nodes
|
|
average APR scaled to running time for all nodes
|
|
|
@returns: average APR for all nodes
|
|
@returns: average APR for all nodes
|
|
|
"""
|
|
"""
|
|
|
def avg_apr(self):
|
|
def avg_apr(self):
|
|
|
- return Num(sum([darkie.apr_scaled_to_runningtime() for darkie in self.darkies])/len(self.darkies))
|
|
|
|
|
|
|
+ return Num(sum([self.darkies[key].apr_scaled_to_runningtime() for key in self.darkies.keys()])/len(self.darkies))
|
|
|
|
|
|
|
|
"""
|
|
"""
|
|
|
returns: average stake ratio for all nodes
|
|
returns: average stake ratio for all nodes
|
|
|
"""
|
|
"""
|
|
|
def avg_stake_ratio(self):
|
|
def avg_stake_ratio(self):
|
|
|
- return sum([darkie.staked_tokens_ratio() for darkie in self.darkies]) / len(self.darkies)
|
|
|
|
|
|
|
+ return sum([self.darkies[key].staked_tokens_ratio() for key in self.darkies.keys()]) / len(self.darkies)
|
|
|
|
|
|
|
|
"""
|
|
"""
|
|
|
write lottery reward log
|
|
write lottery reward log
|
|
|
"""
|
|
"""
|
|
|
def write(self):
|
|
def write(self):
|
|
|
elapsed=self.end_time-self.start_time
|
|
elapsed=self.end_time-self.start_time
|
|
|
- for id, darkie in enumerate(self.darkies):
|
|
|
|
|
- darkie.write(id)
|
|
|
|
|
|
|
+ for key in self.darkies.keys():
|
|
|
|
|
+ self.darkies[key].write(key)
|
|
|
if self.debug:
|
|
if self.debug:
|
|
|
print("total time: {}, slot time: {}".format(str(timedelta(seconds=elapsed)), str(timedelta(seconds=elapsed/self.running_time))))
|
|
print("total time: {}, slot time: {}".format(str(timedelta(seconds=elapsed)), str(timedelta(seconds=elapsed/self.running_time))))
|
|
|
self.secondary_pid.write()
|
|
self.secondary_pid.write()
|
|
@@ -204,7 +216,7 @@ class DarkfiTable:
|
|
|
|
|
|
|
|
@return total tip for miner, and list of indices of darkies included.
|
|
@return total tip for miner, and list of indices of darkies included.
|
|
|
"""
|
|
"""
|
|
|
- def auction(txs):
|
|
|
|
|
|
|
+ def auction(self, txs):
|
|
|
W = MAX_BLOCK_CC
|
|
W = MAX_BLOCK_CC
|
|
|
n = len(txs)
|
|
n = len(txs)
|
|
|
K = [[[0,[]] for x in range(W + 1)] for x in range(n + 1)]
|
|
K = [[[0,[]] for x in range(W + 1)] for x in range(n + 1)]
|
|
@@ -214,7 +226,10 @@ class DarkfiTable:
|
|
|
K[i][w] = [0,[]]
|
|
K[i][w] = [0,[]]
|
|
|
elif txs[i-1].cc() <= w:
|
|
elif txs[i-1].cc() <= w:
|
|
|
if txs[i-1].tip + K[i-1][w-txs[i-1].cc()][0] > K[i-1][w][0]:
|
|
if txs[i-1].tip + K[i-1][w-txs[i-1].cc()][0] > K[i-1][w][0]:
|
|
|
- K[i][w] = [txs[i-1].tip + K[i-1][w-txs[i-1].cc()][0], K[i-1][w-txs[i-1].cc()][1] + [i-1]]
|
|
|
|
|
|
|
+ # make sure stakeholder have any stake to cover basefee+tip
|
|
|
|
|
+ assert self.darkies[txs[i-1].idx].stake > 0, 'tx: {}, darkie idx: {}'.format(i-1, txs[i-1].idx)
|
|
|
|
|
+ # note indices are keypair (txs index, darkie index)
|
|
|
|
|
+ K[i][w] = [txs[i-1].tip + K[i-1][w-txs[i-1].cc()][0], K[i-1][w-txs[i-1].cc()][1] + [[i-1, txs[i-1].idx]]]
|
|
|
else:
|
|
else:
|
|
|
K[i][w] = K[i-1][w]
|
|
K[i][w] = K[i-1][w]
|
|
|
else:
|
|
else:
|