diff --git a/5ghost.py b/5ghost.py new file mode 100644 index 0000000..c527a60 --- /dev/null +++ b/5ghost.py @@ -0,0 +1,164 @@ +# Time between successful PoW solutions +POW_SOLUTION_TIME = 10 +# Time for a block to traverse the network +TRANSIT_TIME = 10 +# Max uncle depth +UNCLE_DEPTH = 4 +# Uncle block reward (normal block reward = 1) +UNCLE_REWARD_COEFF = 29/32. +# Reward for including uncles +NEPHEW_REWARD_COEFF = 3/64. +# Rounds to test +ROUNDS = 100000 + +import random +import copy + +all_miners = {} + + +class Miner(): + def __init__(self, p): + self.hashpower = p + self.id = random.randrange(10000000) + # Set up a few genesis blocks (since the algo is grandpa-dependent, + # we need two genesis blocks plus some genesis uncles) + self.blocks = {} + for i in range(UNCLE_DEPTH + 2): + self.blocks[i] = \ + {"parent": i-1, "uncles": {}, "miner": -1, "height": i, + "score": i, "id": i, "children": {}} + # ID of "latest block" + self.head = UNCLE_DEPTH + 1 + + # Hear about a block + def recv(self, block): + # Add the block to the set if it's valid + addme = True + if block["id"] in self.blocks: + addme = False + if block["parent"] not in self.blocks: + addme = False + p = self.blocks[block["parent"]] + if addme: + self.blocks[block["id"]] = copy.deepcopy(block) + if block["id"] not in p["children"]: + p["children"][block["id"]] = block["id"] + if block["score"] > self.blocks[self.head]["score"]: + self.head = block["id"] + + # Mine a block + def mine(self): + HEAD = self.blocks[self.head] + H = HEAD + h = self.blocks[self.blocks[self.head]["parent"]] + u = {} + notu = {} + for i in range(UNCLE_DEPTH): + for c in h["children"]: + u[c] = True + notu[H["id"]] = True + for c in H["uncles"]: + notu[c] = True + H = h + h = self.blocks[h["parent"]] + for i in notu: + if i in u: + del u[i] + block = {"parent": self.head, "uncles": u, "miner": self.id, + "height": HEAD["height"] + 1, "score": HEAD["score"]+1+len(u), + "id": random.randrange(1000000000000), "children": {}} + self.recv(block) + global all_miners + all_miners[block["id"]] = block + return block + + +def cousin_degree(miner, b1, b2): + while miner.blocks[b1]["height"] > miner.blocks[b2]["height"]: + b1 = miner.blocks[b1]["parent"] + while miner.blocks[b2]["height"] > miner.blocks[b1]["height"]: + b2 = miner.blocks[b2]["parent"] + t = 0 + while b1 != b2: + b1 = miner.blocks[b1]["parent"] + b2 = miner.blocks[b2]["parent"] + t += 1 + return t + +percentages = [1]*25 + [5, 5, 5, 5, 5, 10, 15, 25] +miners = [] +for p in percentages: + miners.append(Miner(p)) + +miner_dict = {} +for m in miners: + miner_dict[m.id] = m + +listen_queue = [] + +for t in range(ROUNDS): + if t % 5000 == 0: + print t + for m in miners: + R = random.randrange(POW_SOLUTION_TIME * sum(percentages)) + if R < m.hashpower and t < ROUNDS - TRANSIT_TIME * 3: + b = m.mine() + listen_queue.append([t + TRANSIT_TIME, b]) + while len(listen_queue) and listen_queue[0][0] <= t: + t, b = listen_queue.pop(0) + for m in miners: + m.recv(b) + +h = miners[0].blocks[miners[0].head] +profit = {} +total_blocks_in_chain = 0 +length_of_chain = 0 +ZORO = {} +print "### PRINTING BLOCKCHAIN ###" + +while h["id"] > UNCLE_DEPTH + 2: + print h["id"], h["miner"], h["height"], h["score"] + print "Uncles: ", list(h["uncles"]) + total_blocks_in_chain += 1 + len(h["uncles"]) + ZORO[h["id"]] = True + length_of_chain += 1 + profit[h["miner"]] = profit.get(h["miner"], 0) + \ + 1 + NEPHEW_REWARD_COEFF * len(h["uncles"]) + for u in h["uncles"]: + ZORO[u] = True + u2 = miners[0].blocks[u] + profit[u2["miner"]] = profit.get(u2["miner"], 0) + UNCLE_REWARD_COEFF + h = miners[0].blocks[h["parent"]] + +print "### PRINTING HEADS ###" + +for m in miners: + print m.head + + +print "### PRINTING PROFITS ###" + +for p in profit: + print miner_dict[p].hashpower, profit[p] + +print "### PRINTING RESULTS ###" + +groupings = {} +counts = {} +for p in profit: + h = miner_dict[p].hashpower + counts[h] = counts.get(h, 0) + 1 + groupings[h] = groupings.get(h, 0) + profit[p] + +for c in counts: + print c, groupings[c] / counts[c] / (groupings[1] / counts[1]) + +print " " +print "Total blocks produced: ", len(all_miners) - UNCLE_DEPTH +print "Total blocks in chain: ", total_blocks_in_chain +print "Efficiency: ", \ + total_blocks_in_chain * 1.0 / (len(all_miners) - UNCLE_DEPTH) +print "Average uncles: ", total_blocks_in_chain * 1.0 / length_of_chain - 1 +print "Length of chain: ", length_of_chain +print "Block time: ", ROUNDS * 1.0 / length_of_chain diff --git a/sim.py b/sim.py index d218226..22420c8 100644 --- a/sim.py +++ b/sim.py @@ -51,8 +51,10 @@ class Miner(): p["children"][block["id"]] = block["id"] # Check if the new block deserves to be the new head if len(p["children"]) >= 1 + UNCLES: - if block["score"] > self.blocks[self.head]["score"]: - self.head = block["id"] + for c in p["children"]: + newblock = self.blocks[c] + if newblock["score"] > self.blocks[self.head]["score"]: + self.head = newblock["id"] # Mine a block def mine(self):