/ multi_uncle_ghost.py
multi_uncle_ghost.py
1 # Time between successful PoW solutions 2 POW_SOLUTION_TIME = 10 3 # Time for a block to traverse the network 4 TRANSIT_TIME = 50 5 # Number of required uncles 6 UNCLES = 4 7 # Uncle block reward (normal block reward = 1) 8 UNCLE_REWARD_COEFF = 0.875 9 # Reward for including uncles 10 NEPHEW_REWARD_COEFF = 0.01 11 # Rounds to test 12 ROUNDS = 80000 13 14 import random 15 import copy 16 17 18 class Miner(): 19 def __init__(self, p): 20 self.hashpower = p 21 self.id = random.randrange(10000000) 22 # Set up a few genesis blocks (since the algo is grandpa-dependent, 23 # we need two genesis blocks plus some genesis uncles) 24 self.blocks = { 25 0: {"parent": -1, "uncles": [], "miner": -1, "height": 0, 26 "score": 0, "id": 0, "children": {1: 1}}, 27 1: {"parent": 0, "uncles": [], "miner": -1, "height": 1, 28 "score": 0, "id": 1, "children": {}} 29 } 30 # ID of "latest block" 31 self.head = 1 32 33 # Hear about a block 34 def recv(self, block): 35 # Add the block to the set if it's valid 36 addme = True 37 if block["id"] in self.blocks: 38 addme = False 39 if block["parent"] not in self.blocks: 40 addme = False 41 for u in block["uncles"]: 42 if u not in self.blocks: 43 addme = False 44 p = self.blocks[block["parent"]] 45 if addme: 46 self.blocks[block["id"]] = copy.deepcopy(block) 47 # Each parent keeps track of its children, to help 48 # facilitate the rule that a block must have N+ siblings 49 # to be valid 50 if block["id"] not in p["children"]: 51 p["children"][block["id"]] = block["id"] 52 # Check if the new block deserves to be the new head 53 if len(p["children"]) >= 1 + UNCLES: 54 for c in p["children"]: 55 newblock = self.blocks[c] 56 if newblock["score"] > self.blocks[self.head]["score"]: 57 self.head = newblock["id"] 58 59 # Mine a block 60 def mine(self): 61 h = self.blocks[self.blocks[self.head]["parent"]] 62 b = sorted(list(h["children"]), key=lambda x: -self.blocks[x]["score"]) 63 p = self.blocks[b[0]] 64 block = {"parent": b[0], "uncles": b[1:], "miner": self.id, 65 "height": h["height"] + 2, "score": p["score"] + len(b), 66 "id": random.randrange(1000000000000), "children": {}} 67 self.recv(block) 68 return block 69 70 71 def cousin_degree(miner, b1, b2): 72 while miner.blocks[b1]["height"] > miner.blocks[b2]["height"]: 73 b1 = miner.blocks[b1]["parent"] 74 while miner.blocks[b2]["height"] > miner.blocks[b1]["height"]: 75 b2 = miner.blocks[b2]["parent"] 76 t = 0 77 while b1 != b2: 78 b1 = miner.blocks[b1]["parent"] 79 b2 = miner.blocks[b2]["parent"] 80 t += 1 81 return t 82 83 percentages = [1]*25 + [5, 5, 5, 5, 5, 10, 15, 25] 84 miners = [] 85 for p in percentages: 86 miners.append(Miner(p)) 87 88 miner_dict = {} 89 for m in miners: 90 miner_dict[m.id] = m 91 92 listen_queue = [] 93 94 for t in range(ROUNDS): 95 if t % 5000 == 0: 96 print t 97 for m in miners: 98 R = random.randrange(POW_SOLUTION_TIME * sum(percentages)) 99 if R < m.hashpower and t < ROUNDS - TRANSIT_TIME * 3: 100 b = m.mine() 101 listen_queue.append([t + TRANSIT_TIME, b]) 102 while len(listen_queue) and listen_queue[0][0] <= t: 103 t, b = listen_queue.pop(0) 104 for m in miners: 105 m.recv(b) 106 107 h = miners[0].blocks[miners[0].head] 108 profit = {} 109 total_blocks_in_chain = 0 110 length_of_chain = 0 111 ZORO = {} 112 print "### PRINTING BLOCKCHAIN ###" 113 114 while h["id"] > 1: 115 print h["miner"], h["height"], h["score"] 116 total_blocks_in_chain += 1 + len(h["uncles"]) 117 ZORO[h["id"]] = True 118 length_of_chain += 1 119 profit[h["miner"]] = profit.get(h["miner"], 0) + \ 120 1 + NEPHEW_REWARD_COEFF * len(h["uncles"]) 121 for u in h["uncles"]: 122 ZORO[u] = True 123 u2 = miners[0].blocks[u] 124 profit[u2["miner"]] = profit.get(u2["miner"], 0) + UNCLE_REWARD_COEFF 125 h = miners[0].blocks[h["parent"]] 126 127 print "### PRINTING HEADS ###" 128 129 for m in miners: 130 print m.head 131 132 133 print "### PRINTING PROFITS ###" 134 135 for p in profit: 136 print miner_dict[p].hashpower, profit[p] 137 138 print "### PRINTING RESULTS ###" 139 140 groupings = {} 141 counts = {} 142 for p in profit: 143 h = miner_dict[p].hashpower 144 counts[h] = counts.get(h, 0) + 1 145 groupings[h] = groupings.get(h, 0) + profit[p] 146 147 for c in counts: 148 print c, groupings[c] / counts[c] / (groupings[1] / counts[1]) 149 150 print " " 151 print "Total blocks produced: ", len(miners[0].blocks) - 2 152 print "Total blocks in chain: ", total_blocks_in_chain 153 print "Efficiency: ", total_blocks_in_chain * 1.0 / (len(miners[0].blocks) - 2) 154 print "Average uncles: ", total_blocks_in_chain * 1.0 / length_of_chain 155 print "Length of chain: ", length_of_chain 156 print "Block time: ", ROUNDS * 1.0 / length_of_chain