/ 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