/ ghost.py
ghost.py
1 # Time between successful PoW solutions 2 POW_SOLUTION_TIME = 12 3 # Time for a block to traverse the network 4 TRANSIT_TIME = 8 5 # Max uncle depth 6 UNCLE_DEPTH = 4 7 # Uncle block reward (normal block reward = 1) 8 UNCLE_REWARD_COEFF = 32/32. 9 UNCLE_DEPTH_PENALTY = 4/32. 10 # Reward for including uncles 11 NEPHEW_REWARD_COEFF = 1/32. 12 # Rounds to test 13 ROUNDS = 1000000 14 15 import random 16 17 all_miners = {} 18 19 20 class Miner(): 21 def __init__(self, p, backward=0): 22 # Miner hashpower 23 self.hashpower = p 24 # Miner mines a few blocks behind the head? 25 self.backward = backward 26 self.id = random.randrange(10000000) 27 # Set up a few genesis blocks (since the algo is grandpa-dependent, 28 # we need two genesis blocks plus some genesis uncles) 29 self.blocks = {} 30 self.children = {} 31 for i in range(UNCLE_DEPTH + 2): 32 self.blocks[i] = \ 33 {"parent": i-1, "uncles": {}, "miner": -1, "height": i, 34 "score": i, "id": i} 35 self.children[i-1] = {i: True} 36 # ID of "latest block" 37 self.head = UNCLE_DEPTH + 1 38 39 # Hear about a block 40 def recv(self, block): 41 # Add the block to the set if it's valid 42 addme = True 43 if block["id"] in self.blocks: 44 addme = False 45 if block["parent"] not in self.blocks: 46 addme = False 47 if addme: 48 self.blocks[block["id"]] = block 49 if block["parent"] not in self.children: 50 self.children[block["parent"]] = {} 51 if block["id"] not in self.children[block["parent"]]: 52 self.children[block["parent"]][block["id"]] = block["id"] 53 if block["score"] > self.blocks[self.head]["score"]: 54 self.head = block["id"] 55 56 # Mine a block 57 def mine(self): 58 HEAD = self.blocks[self.head] 59 for i in range(self.backward): 60 HEAD = self.blocks[HEAD["parent"]] 61 H = HEAD 62 h = self.blocks[self.blocks[self.head]["parent"]] 63 # Select the uncles. The valid set of uncles for a block consists 64 # of the children of the 2nd to N+1th order grandparents minus 65 # the parent and said grandparents themselves and blocks that were 66 # uncles of those previous blocks 67 u = {} 68 notu = {} 69 for i in range(UNCLE_DEPTH - self.backward): 70 for c in self.children.get(h["id"], {}): 71 u[c] = True 72 notu[H["id"]] = True 73 for c in H["uncles"]: 74 notu[c] = True 75 H = h 76 h = self.blocks[h["parent"]] 77 for i in notu: 78 if i in u: 79 del u[i] 80 block = {"parent": self.head, "uncles": u, "miner": self.id, 81 "height": HEAD["height"] + 1, "score": HEAD["score"]+1+len(u), 82 "id": random.randrange(1000000000000)} 83 self.recv(block) 84 global all_miners 85 all_miners[block["id"]] = block 86 return block 87 88 89 # If b1 is the n-th degree grandchild and b2 is the m-th degree grandchild 90 # of nearest common ancestor C, returns min(m, n) 91 def cousin_degree(miner, b1, b2): 92 while miner.blocks[b1]["height"] > miner.blocks[b2]["height"]: 93 b1 = miner.blocks[b1]["parent"] 94 while miner.blocks[b2]["height"] > miner.blocks[b1]["height"]: 95 b2 = miner.blocks[b2]["parent"] 96 t = 0 97 while b1 != b2: 98 b1 = miner.blocks[b1]["parent"] 99 b2 = miner.blocks[b2]["parent"] 100 t += 1 101 return t 102 103 # Set hashpower percentages and strategies 104 # Strategy = how many blocks behind head you mine 105 profiles = [ 106 # (hashpower, strategy, count) 107 (1, 0, 20), 108 (1, 1, 4), # cheaters, mine 1/2/4 blocks back to reduce 109 (1, 2, 3), # chance of being in a two-block fork 110 (1, 4, 3), 111 (5, 0, 1), 112 (10, 0, 1), 113 (15, 0, 1), 114 (25, 0, 1), 115 ] 116 117 total_pct = 0 118 miners = [] 119 for p, b, c in profiles: 120 for i in range(c): 121 miners.append(Miner(p, b)) 122 total_pct += p 123 124 miner_dict = {} 125 for m in miners: 126 miner_dict[m.id] = m 127 128 listen_queue = [] 129 130 for t in range(ROUNDS): 131 if t % 5000 == 0: 132 print t 133 for m in miners: 134 R = random.randrange(POW_SOLUTION_TIME * total_pct) 135 if R < m.hashpower and t < ROUNDS - TRANSIT_TIME * 3: 136 b = m.mine() 137 listen_queue.append([t + TRANSIT_TIME, b]) 138 while len(listen_queue) and listen_queue[0][0] <= t: 139 t, b = listen_queue.pop(0) 140 for m in miners: 141 m.recv(b) 142 143 h = miners[0].blocks[miners[0].head] 144 profit = {} 145 total_blocks_in_chain = 0 146 length_of_chain = 0 147 ZORO = {} 148 print "### PRINTING BLOCKCHAIN ###" 149 150 while h["id"] > UNCLE_DEPTH + 2: 151 # print h["id"], h["miner"], h["height"], h["score"] 152 # print "Uncles: ", list(h["uncles"]) 153 total_blocks_in_chain += 1 + len(h["uncles"]) 154 ZORO[h["id"]] = True 155 length_of_chain += 1 156 profit[h["miner"]] = profit.get(h["miner"], 0) + \ 157 1 + NEPHEW_REWARD_COEFF * len(h["uncles"]) 158 for u in h["uncles"]: 159 ZORO[u] = True 160 u2 = miners[0].blocks[u] 161 profit[u2["miner"]] \ 162 = profit.get(u2["miner"], 0) + UNCLE_REWARD_COEFF - UNCLE_DEPTH_PENALTY * (h["height"] - u2["height"]) 163 h = miners[0].blocks[h["parent"]] 164 165 print "### PRINTING HEADS ###" 166 167 for m in miners: 168 print m.head 169 170 171 print "### PRINTING PROFITS ###" 172 173 for p in profit: 174 print miner_dict.get(p, Miner(0)).hashpower, profit.get(p, 0) 175 176 print "### PRINTING RESULTS ###" 177 178 groupings = {} 179 counts = {} 180 for p in profit: 181 m = miner_dict.get(p, None) 182 if m: 183 h = str(m.hashpower)+','+str(m.backward) 184 counts[h] = counts.get(h, 0) + 1 185 groupings[h] = groupings.get(h, 0) + profit[p] 186 187 for c in counts: 188 print c, groupings[c] / counts[c] / (groupings['1,0'] / counts['1,0']) 189 190 print " " 191 print "Total blocks produced: ", len(all_miners) - UNCLE_DEPTH 192 print "Total blocks in chain: ", total_blocks_in_chain 193 print "Efficiency: ", \ 194 total_blocks_in_chain * 1.0 / (len(all_miners) - UNCLE_DEPTH) 195 print "Average uncles: ", total_blocks_in_chain * 1.0 / length_of_chain - 1 196 print "Length of chain: ", length_of_chain 197 print "Block time: ", ROUNDS * 1.0 / length_of_chain