diff --git a/5ghost.py b/5ghost.py index 63a03ab..1ac11b6 100644 --- a/5ghost.py +++ b/5ghost.py @@ -1,18 +1,17 @@ # Time between successful PoW solutions -POW_SOLUTION_TIME = 5 +POW_SOLUTION_TIME = 10 # Time for a block to traverse the network TRANSIT_TIME = 10 # Max uncle depth -UNCLE_DEPTH = 9 +UNCLE_DEPTH = 4 # Uncle block reward (normal block reward = 1) UNCLE_REWARD_COEFF = 30/32. # Reward for including uncles -NEPHEW_REWARD_COEFF = 2/64. +NEPHEW_REWARD_COEFF = 1/64. # Rounds to test -ROUNDS = 120000 +ROUNDS = 200000 import random -import copy all_miners = {} @@ -24,10 +23,12 @@ class Miner(): # Set up a few genesis blocks (since the algo is grandpa-dependent, # we need two genesis blocks plus some genesis uncles) self.blocks = {} + self.children = {} for i in range(UNCLE_DEPTH + 2): self.blocks[i] = \ {"parent": i-1, "uncles": {}, "miner": -1, "height": i, - "score": i, "id": i, "children": {}} + "score": i, "id": i} + self.children[i-1] = {i: True} # ID of "latest block" self.head = UNCLE_DEPTH + 1 @@ -39,11 +40,12 @@ class Miner(): 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"] + self.blocks[block["id"]] = block + if block["parent"] not in self.children: + self.children[block["parent"]] = {} + if block["id"] not in self.children[block["parent"]]: + self.children[block["parent"]][block["id"]] = block["id"] if block["score"] > self.blocks[self.head]["score"]: self.head = block["id"] @@ -59,7 +61,7 @@ class Miner(): u = {} notu = {} for i in range(UNCLE_DEPTH): - for c in h["children"]: + for c in self.children.get(h["id"], {}): u[c] = True notu[H["id"]] = True for c in H["uncles"]: @@ -71,7 +73,7 @@ class Miner(): 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": {}} + "id": random.randrange(1000000000000)} self.recv(block) global all_miners all_miners[block["id"]] = block @@ -125,8 +127,8 @@ ZORO = {} print "### PRINTING BLOCKCHAIN ###" while h["id"] > UNCLE_DEPTH + 2: - print h["id"], h["miner"], h["height"], h["score"] - print "Uncles: ", list(h["uncles"]) + # 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 diff --git a/ghost.py b/ghost.py new file mode 100644 index 0000000..b183540 --- /dev/null +++ b/ghost.py @@ -0,0 +1,173 @@ +# Time between successful PoW solutions +POW_SOLUTION_TIME = 60 +# Time for a block to traverse the network +TRANSIT_TIME = 12 +# Max uncle depth +UNCLE_DEPTH = 0 +# Uncle block reward (normal block reward = 1) +UNCLE_REWARD_COEFF = 7/8. +# Reward for including uncles +NEPHEW_REWARD_COEFF = 1/16. +# Rounds to test +ROUNDS = 1000000 + +import random + +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 = {} + self.children = {} + for i in range(UNCLE_DEPTH + 2): + self.blocks[i] = \ + {"parent": i-1, "uncles": {}, "miner": -1, "height": i, + "score": i, "id": i} + self.children[i-1] = {i: True} + # 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 + if addme: + self.blocks[block["id"]] = block + if block["parent"] not in self.children: + self.children[block["parent"]] = {} + if block["id"] not in self.children[block["parent"]]: + self.children[block["parent"]][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"]] + # Select the uncles. The valid set of uncles for a block consists + # of the children of the 2nd to N+1th order grandparents minus + # the parent and said grandparents themselves and blocks that were + # uncles of those previous blocks + u = {} + notu = {} + for i in range(UNCLE_DEPTH): + for c in self.children.get(h["id"], {}): + 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)} + self.recv(block) + global all_miners + all_miners[block["id"]] = block + return block + + +# If b1 is the n-th degree grandchild and b2 is the m-th degree grandchild +# of nearest common ancestor C, returns min(m, n) +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 + +# Set hashpower percentages here +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