/ 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