Pārlūkot izejas kodu

[research/lotterysim] fix drop in accuracy after 0mint distribution ends; starting with 0 premint at genesis/pre-genesis

ertosns 3 gadi atpakaļ
vecāks
revīzija
635c326c17

+ 8 - 7
script/research/lotterysim/core/constants.py

@@ -9,7 +9,7 @@ CONTROLLER_TYPE_DISCRETE = 0
 # takahashi controller enum
 CONTROLLER_TYPE_TAKAHASHI = 1
 # initial distribution of tokens (random value for sake of experimentation)
-ERC20DRK = 2.1*10**7
+ERC20DRK = 0
 # initial distribution
 PREMINT = ERC20DRK
 # group base/order
@@ -25,15 +25,15 @@ REWARD_MAX = 1000
 # slot length in seconds
 SLOT = 90
 # epoch length in slots
-EPOCH_LENGTH = Num(10)
+EPOCH_LENGTH = 10
 # one month in slots
-ONE_MONTH = Num(60*60*24*30/SLOT)
+ONE_MONTH = 60*60*24*30/SLOT
 # one year in slots
-ONE_YEAR = Num(365.25*24*60*60/SLOT)
+ONE_YEAR = 365.25*24*60*60/SLOT
 # vesting issuance period
 VESTING_PERIOD = ONE_MONTH
 # stakeholder assumes  APR target
-TARGET_APR = Num(0.12)
+TARGET_APR = 0.12
 # primary controller assumes accuracy target
 PRIMARY_REWARD_TARGET = 0.33 # staked ratio
 # secondary controller assumes certain frequency of leaders per slot
@@ -53,13 +53,13 @@ EPSILON = 1
 # window of accuracy calculation
 ACC_WINDOW = int(EPOCH_LENGTH)*10
 # headstart airdrop period
-HEADSTART_AIRDROP = 0
+HEADSTART_AIRDROP = 500
 # threshold of randomly slashing stakeholder
 SLASHING_RATIO = 0.001
 # number of nodes
 NODES = 1000
 # headstart value
-BASE_L = NODES**-1*L*0.01
+BASE_L = NODES**-1*L
 # decimal high precision.
 L_HP = Num(L)
 F_MIN_HP = Num(F_MIN)
@@ -69,3 +69,4 @@ REWARD_MIN_HP = Num(REWARD_MIN)
 REWARD_MAX_HP = Num(REWARD_MAX)
 BASE_L_HP = Num(BASE_L)
 CC_DIFF_EPSILON=0.0001
+MIL_SLOT = 500

+ 23 - 11
script/research/lotterysim/core/darkie.py

@@ -2,7 +2,7 @@ from core.utils import *
 from core.strategy import *
 
 class Darkie():
-    def __init__(self, airdrop, initial_stake=None, vesting=[], hp=False, commit=True, epoch_len=EPOCH_LENGTH, strategy=random_strategy(EPOCH_LENGTH)):
+    def __init__(self, airdrop, initial_stake=None, vesting=[], hp=False, commit=True, epoch_len=EPOCH_LENGTH, strategy=random_strategy(EPOCH_LENGTH), idx=0):
         self.vesting = vesting
         self.stake = (Num(airdrop) if hp else airdrop)
         self.initial_stake = [self.stake]
@@ -15,6 +15,8 @@ class Darkie():
         self.won_hist = [] # winning history boolean
         self.fees = []
         self.tips = [0]
+        self.idx=idx
+        self.aprs = [] #milinial aprs. every 1k slots ~ 1day
 
     def clone(self):
         return Darkie(self.stake)
@@ -39,13 +41,15 @@ class Darkie():
     def apr_scaled_to_runningtime(self):
         initial_stake = self.vesting_wrapped_initial_stake()
         #assert self.stake >= initial_stake, 'stake: {}, initial_stake: {}, slot: {}, current: {}, previous: {} vesting'.format(self.stake, initial_stake, self.slot, self.current_vesting(), self.prev_vesting())
-        apr_scaled = Num(self.stake - initial_stake) / Num(initial_stake) if initial_stake>0 else 0
-        if self.slot <= HEADSTART_AIRDROP:
+        if self.slot < HEADSTART_AIRDROP:
             # during this phase, it's only called at end of epoch
-            apr_period = EPOCH_LENGTH
+            apr_period = self.slot%EPOCH_LENGTH
+        elif self.slot > MIL_SLOT:
+            apr_period = self.slot - int(self.slot/MIL_SLOT)*MIL_SLOT
         else:
             apr_period = self.slot-HEADSTART_AIRDROP
-        apr = apr_scaled * Num(ONE_YEAR/apr_period) if initial_stake > 0 and self.slot>0 else 0
+        apr_scaled = ((self.stake - initial_stake) / initial_stake) / apr_period if initial_stake>0  and apr_period>0 else 0
+        apr = apr_scaled * ONE_YEAR if initial_stake > 0 and apr_period>0 and self.slot>0 else 0
         #if apr>0 and self.stake-initial_stake>0:
             #print("apr: {}, stake: {}, initial_stake: {}".format(apr, self.stake, initial_stake))
         return apr
@@ -58,10 +62,12 @@ class Darkie():
     def vesting_wrapped_initial_stake(self):
         #returns  vesting stake plus initial stake gained from zero coin headstart during aridrop period
         vesting = self.current_vesting()
-        if self.slot <= HEADSTART_AIRDROP:
+        if self.slot < HEADSTART_AIRDROP:
             initial_stake = self.initial_stake[-1]
+        elif self.slot > MIL_SLOT:
+            initial_stake = self.initial_stake[int(int(self.slot/MIL_SLOT) * MIL_SLOT/EPOCH_LENGTH)-1]
         else:
-            initial_stake = self.initial_stake[int(HEADSTART_AIRDROP/EPOCH_LENGTH)]
+            initial_stake = self.initial_stake[int(HEADSTART_AIRDROP/EPOCH_LENGTH)-1]
         return vesting + initial_stake
 
     """
@@ -134,8 +140,10 @@ class Darkie():
                 assert scaled_target>0
             return scaled_target
 
+        apr = self.apr_scaled_to_runningtime()
+        if (self.slot+1) % MIL_SLOT == 0:
+            self.aprs += [apr]
         if self.slot % EPOCH_LENGTH ==0 and self.slot > 0:
-            apr = self.apr_scaled_to_runningtime()
             # staked ratio is added in strategy
             self.strategy.set_ratio(self.slot, apr)
             # epoch stake is added
@@ -165,6 +173,9 @@ class Darkie():
             buf += '(apr,staked ratio,{}):'.format(self.strategy.type)+','.join(['('+str(apr)+','+str(sr)+')' for sr, apr in zip(self.strategy.staked_tokens_ratio, self.strategy.annual_return)])
             buf+='\r\n'
             buf += 'apr: {}'.format(self.apr_scaled_to_runningtime())
+            buf += '\r\n'
+            buf += 'mil-aprs: {}'.format(','.join([str(apr) for apr in self.aprs]))
+            buf += '\r\n'
             f.write(buf)
 
     """
@@ -177,8 +188,8 @@ class Darkie():
         tx_size = random.randint(0, MAX_BLOCK_SIZE)
         tip = self.tx_tip(tx_size, last_reward)
         if self.stake < tip:
-            return Tx(tx_size, 0)
-        return Tx(tx_size, tip)
+            return Tx(tx_size, 0, self.idx)
+        return Tx(tx_size, tip, self.idx)
 
     def tx_tip(self, tx_size, last_reward):
         tip = random_tip_strategy()
@@ -197,10 +208,11 @@ class Darkie():
         return self.fees[-1] if len(self.fees)>0 else 0
 
 class Tx(object):
-    def __init__(self, size, tip):
+    def __init__(self, size, tip, idx):
         self.tx = [random.random() for _ in range(size)]
         self.len = size
         self.tip = tip
+        self.idx=idx
 
     """
     anonymous contract assumed to be of random streams from uniform distribution,

+ 53 - 38
script/research/lotterysim/core/lottery.py

@@ -10,7 +10,7 @@ import random
 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):
         self.Sigma=airdrop
-        self.darkies = []
+        self.darkies = {}
         self.running_time=running_time
         self.start_time=None
         self.end_time=None
@@ -26,9 +26,11 @@ class DarkfiTable:
         self.base_fee = []
         self.tips_avg = []
         self.cc_diff = []
+        self.basefee = [FEE_MAX]
+        self.slashed_idxs = []
 
     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 \
@@ -60,18 +62,19 @@ class DarkfiTable:
             total_stake = 0
             Ys = []
             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
-                y, T = self.darkies[i].run(hp)
+                y, T = self.darkies[key].run(hp)
                 Ys+=[y]
                 Ts+=[T]
-                total_stake += self.darkies[i].stake
+                total_stake += self.darkies[key].stake
             # 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:
-                is_slashed = self.reward_slash_lead(debug)
+                is_slashed, idx = self.reward_slash_lead(slot, debug)
+                self.slashed_idxs += [idx]
                 if is_slashed==False:
                     self.resolve_fork(slot, debug)
             avg_y = sum(Ys)/len(Ys)
@@ -95,20 +98,21 @@ class DarkfiTable:
 
     @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
-        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:
-                    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:
-                    self.darkies[i].update_stake(self.rewards[-1])
+                    self.darkies[key].update_stake(self.rewards[-1])
                     self.Sigma += self.rewards[-1]
-                    self.tx_fees(i, debug)
+                    if slot > HEADSTART_AIRDROP:
+                        self.tx_fees(key, debug)
                 break
-        return False
+        return False, -1
 
     """
     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,
             # it's simulated by choosing first wining node
             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
 
     def merge_length(self):
@@ -143,22 +148,29 @@ class DarkfiTable:
     """
     def tx_fees(self, darkie_lead_idx, debug=False):
         txs = []
-        for darkie in self.darkies:
+        for key in self.darkies.keys():
             # 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]
+        basefee = self.basefee_pid.pid_clipped(self.computational_cost[-1], debug)
+        self.basefee += [basefee]
         self.cc_diff += [MAX_BLOCK_CC - actual_cc]
         tips = ret[0]
         idxs = ret[1]
         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]
-        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)
-            #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)
 
         # subtract base fee from total stake
@@ -166,32 +178,32 @@ class DarkfiTable:
 
     """
     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
     """
     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
     @returns: average APR for all nodes
     """
     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
     """
     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
     """
     def write(self):
         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:
             print("total time: {}, slot time: {}".format(str(timedelta(seconds=elapsed)), str(timedelta(seconds=elapsed/self.running_time))))
         self.secondary_pid.write()
@@ -204,7 +216,7 @@ class DarkfiTable:
 
     @return total tip for miner, and list of indices of darkies included.
     """
-    def auction(txs):
+    def auction(self, txs):
         W = MAX_BLOCK_CC
         n = len(txs)
         K = [[[0,[]] for x in range(W + 1)] for x in range(n + 1)]
@@ -214,7 +226,10 @@ class DarkfiTable:
                     K[i][w] = [0,[]]
                 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]:
-                        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:
                         K[i][w] = K[i-1][w]
                 else:

+ 5 - 5
script/research/lotterysim/core/strategy.py

@@ -20,7 +20,7 @@ class Strategy(object):
         return
 
     def staked_value(self, stake):
-        return Num(self.staked_tokens_ratio[-1])*Num(stake)
+        return (self.staked_tokens_ratio[-1])*(stake)
 
 class Hodler(Strategy):
     def __init__(self, epoch_len):
@@ -39,7 +39,7 @@ class LinearStrategy(Strategy):
 
     def set_ratio(self, slot, apr):
         if slot%self.epoch_len==0:
-                sr = Num(apr)/Num(self.target)
+                sr = (apr)/(self.target)
                 if sr>1:
                     sr = 1
                 elif sr<0:
@@ -56,7 +56,7 @@ class LogarithmicStrategy(Strategy):
         if slot%self.epoch_len==0:
                 apr_ratio = math.fabs(apr/self.target)
                 fn = lambda x: (math.log(x, 10)+1)/2 * 0.95 + 0.05
-                sr = Num(fn(apr_ratio) if apr_ratio != 0 else 0)
+                sr = (fn(apr_ratio) if apr_ratio != 0 else 0)
                 if sr>1:
                     sr = 1
                 elif sr<0:
@@ -73,7 +73,7 @@ class SigmoidStrategy(Strategy):
         if slot%self.epoch_len==0:
                 apr_ratio = apr/self.target
                 apr_ratio = max(apr_ratio, 0)
-                sr = Num(2/(1+math.pow(math.e, -4*apr_ratio))-1)
+                sr = (2/(1+math.pow(math.e, -4*apr_ratio))-1)
                 if sr>1:
                     sr = 1
                 elif sr<0:
@@ -194,4 +194,4 @@ class Generous(Tip):
 
 
 def random_tip_strategy():
-    return random.choice([ZeroTip(), TenthOfReward(), HundredthOfReward(), MilthOfReward(), RewardApr(), TenthRewardApr(), TenthCCApr(), HundredthCCApr(), MilthCCApr(), Conservative(), Generous()])
+    return random.choice([ZeroTip(),  HundredthOfReward(), MilthOfReward(), RewardApr(), TenthCCApr(), HundredthCCApr(), MilthCCApr(), Conservative(), Generous()])

+ 1 - 1
script/research/lotterysim/discrete_instance_pi_headstart.py

@@ -14,7 +14,7 @@ RUNNING_TIME = int(input("running time:"))
 
 if __name__ == "__main__":
     mu = PREMINT/NODES
-    darkies = [Darkie(random.gauss(mu, mu/10), strategy=random_strategy(EPOCH_LENGTH)) for _ in range(NODES)]
+    darkies = [Darkie(random.gauss(mu, mu/10), strategy=random_strategy(EPOCH_LENGTH), idx=idx) for idx in range(NODES)]
     #dt = DarkfiTable(0, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0.03840000000000491,  r_kp=-2.53, r_ki=29.5, r_kd=53.77)
     #dt = DarkfiTable(PREMINT, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878, kd=0.03840000000000491,  r_kp=-0.719, r_ki=1.6, r_kd=0.1, fee_kp=-0.068188, fee_ki=-0.000205)
     #dt = DarkfiTable(PREMINT, RUNNING_TIME, CONTROLLER_TYPE_DISCRETE, kp=-0.010399999999938556, ki=-0.0365999996461878,  r_kp=0.229, r_ki=2.419, fee_kp=-0.068188, fee_ki=-0.000205)

+ 11 - 3
script/research/lotterysim/plot_darkies.py

@@ -10,9 +10,10 @@ for darkie in glob.glob('log/darkie[0-9]*.log'):
         buf = f.read()
         lines = buf.split('\n')
         apr = float(lines[2].split(':')[1].strip())
+        aprs = [float(item) for item in lines[3].split(':')[1].split(',')]
         initial_stake = [float(item) for item in lines[0].split(':')[1].split(',')]
         idx +=1
-        darkies += [(initial_stake, apr, idx)]
+        darkies += [(initial_stake, apr, aprs, idx)]
 plt.figure()
 # plot initial stake
 for darkie in darkies:
@@ -20,8 +21,15 @@ for darkie in darkies:
     plt.title('initial stake')
 legends = []
 for darkie in darkies:
-    legend = ["darkie{}".format(darkie[2])]
+    legend = ["darkie{}".format(darkie[3])]
     legends +=[legend]
-plt.legend(legends, loc='upper right')
+plt.legend(legends, loc='upper left')
 plt.savefig("log/plot_darkies_is.png")
 plt.close()
+
+plt.figure()
+for darkie in darkies:
+    plt.plot(darkie[2])
+    plt.title('APR')
+plt.savefig('log/plot_darkies_mil_apr.png')
+plt.close()