From 3276832e82af7ee8d275a328c92f6cb5d3984f63 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Tue, 8 Jul 2014 02:47:10 -0400 Subject: [PATCH 01/13] first commit --- README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 From c806fc409c162ddb16163df7badd212874f1ce99 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Tue, 8 Jul 2014 02:51:02 -0400 Subject: [PATCH 02/13] Added sim --- sim.py | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 sim.py diff --git a/sim.py b/sim.py new file mode 100644 index 0000000..a9f3981 --- /dev/null +++ b/sim.py @@ -0,0 +1,119 @@ +# Time between block number increases (NOT time between PoW solutions) +BLOCKTIME = 50 +# Time for a block to traverse the network +TRANSIT_TIME = 50 +# Number of required uncles +UNCLES = 4 +# Uncle block reward (normal block reward = 1) +UNCLE_REWARD_COEFF = 0.875 +# Block length to test +BLOCKS = 5000 + +import random +import copy + + +class Miner(): + def __init__(self, p): + self.hashpower = p + self.id = random.randrange(1000000000) + # Set up a few genesis blocks (since the algo is grandpa-dependent, + # we need two genesis blocks plus some genesis uncles) + self.blocks = { + 0: {"parent": -1, "uncles": [], "miner": -1, "height": 0, + "score": 0, "id": 0, "children": {1: 1}}, + 1: {"parent": 0, "uncles": [], "miner": -1, "height": 1, + "score": 0, "id": 1, "children": {}} + } + # for i in range(2, UNCLES + 2): + # self.blocks[i] = {"parent": 0, "uncles": [], "miner": -1, + # "height": 1, "id": i, "children": {}} + # ID of parent of "latest block" + # We care about the parent of the latest block because that way + # we can keep track of uncles. + self.head = 0 + + # 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 + for u in block["uncles"]: + if self.blocks[u]["id"] not in self.blocks: + addme = False + p = self.blocks[block["parent"]] + if addme: + self.blocks[block["id"]] = copy.deepcopy(block) + # Check if the new block's parent deserves to be the new head + if block["id"] not in p["children"]: + p["children"][block["id"]] = block["id"] + if len(p["children"]) >= 1 + UNCLES: + if block["score"] > self.blocks[self.head]["score"]: + self.head = block["parent"] + + # Mine a block + def mine(self): + h = self.blocks[self.head] + b = sorted(list(h["children"]), key=lambda x: self.blocks[x]["id"]) + p = self.blocks[b[0]] + block = {"parent": b[0], "uncles": b[1:], "miner": self.id, + "height": h["height"] + 2, "score": p["score"] + len(b), + "id": random.randrange(1000000000), "children": {}} + self.recv(block) + return block + +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(BLOCKS * BLOCKTIME): + if t % 5000 == 0: + print t + for m in miners: + R = random.randrange(BLOCKTIME * sum(percentages) / (UNCLES + 1)) + if R < m.hashpower: + 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 = {} +print "### PRINTING BLOCKCHAIN ###" + +while h["id"] > 1: + print h["miner"], h["height"], h["score"] + profit[h["miner"]] = profit.get(h["miner"], 0) + 1 + for u in h["uncles"]: + u2 = miners[0].blocks[u] + profit[u2["miner"]] = profit.get(u2["miner"], 0) + UNCLE_REWARD_COEFF + h = miners[0].blocks[h["parent"]] + +print "### PRINTING PROFITS ###" + +for p in profit: + print miner_dict[p].hashpower, profit[p] + +print "### PRINTING GROUPINGS ###" + +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]) From df679e50cbbac984b784ea89f998efa7045fc9c6 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Tue, 8 Jul 2014 04:05:52 -0400 Subject: [PATCH 03/13] Changes --- sim.py | 81 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/sim.py b/sim.py index a9f3981..d218226 100644 --- a/sim.py +++ b/sim.py @@ -1,13 +1,15 @@ -# Time between block number increases (NOT time between PoW solutions) -BLOCKTIME = 50 +# Time between successful PoW solutions +POW_SOLUTION_TIME = 10 # Time for a block to traverse the network TRANSIT_TIME = 50 # Number of required uncles UNCLES = 4 # Uncle block reward (normal block reward = 1) UNCLE_REWARD_COEFF = 0.875 -# Block length to test -BLOCKS = 5000 +# Reward for including uncles +NEPHEW_REWARD_COEFF = 0.01 +# Rounds to test +ROUNDS = 80000 import random import copy @@ -16,7 +18,7 @@ import copy class Miner(): def __init__(self, p): self.hashpower = p - self.id = random.randrange(1000000000) + 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 = { @@ -25,13 +27,8 @@ class Miner(): 1: {"parent": 0, "uncles": [], "miner": -1, "height": 1, "score": 0, "id": 1, "children": {}} } - # for i in range(2, UNCLES + 2): - # self.blocks[i] = {"parent": 0, "uncles": [], "miner": -1, - # "height": 1, "id": i, "children": {}} - # ID of parent of "latest block" - # We care about the parent of the latest block because that way - # we can keep track of uncles. - self.head = 0 + # ID of "latest block" + self.head = 1 # Hear about a block def recv(self, block): @@ -42,29 +39,45 @@ class Miner(): if block["parent"] not in self.blocks: addme = False for u in block["uncles"]: - if self.blocks[u]["id"] not in self.blocks: + if u not in self.blocks: addme = False p = self.blocks[block["parent"]] if addme: self.blocks[block["id"]] = copy.deepcopy(block) - # Check if the new block's parent deserves to be the new head + # Each parent keeps track of its children, to help + # facilitate the rule that a block must have N+ siblings + # to be valid if block["id"] not in p["children"]: 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["parent"] + self.head = block["id"] # Mine a block def mine(self): - h = self.blocks[self.head] - b = sorted(list(h["children"]), key=lambda x: self.blocks[x]["id"]) + h = self.blocks[self.blocks[self.head]["parent"]] + b = sorted(list(h["children"]), key=lambda x: -self.blocks[x]["score"]) p = self.blocks[b[0]] block = {"parent": b[0], "uncles": b[1:], "miner": self.id, "height": h["height"] + 2, "score": p["score"] + len(b), - "id": random.randrange(1000000000), "children": {}} + "id": random.randrange(1000000000000), "children": {}} self.recv(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: @@ -76,12 +89,12 @@ for m in miners: listen_queue = [] -for t in range(BLOCKS * BLOCKTIME): +for t in range(ROUNDS): if t % 5000 == 0: print t for m in miners: - R = random.randrange(BLOCKTIME * sum(percentages) / (UNCLES + 1)) - if R < m.hashpower: + 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: @@ -91,22 +104,36 @@ for t in range(BLOCKS * BLOCKTIME): h = miners[0].blocks[miners[0].head] profit = {} +total_blocks_in_chain = 0 +length_of_chain = 0 +ZORO = {} print "### PRINTING BLOCKCHAIN ###" while h["id"] > 1: print h["miner"], h["height"], h["score"] - profit[h["miner"]] = profit.get(h["miner"], 0) + 1 + 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 GROUPINGS ###" +print "### PRINTING RESULTS ###" groupings = {} counts = {} @@ -117,3 +144,11 @@ for p in profit: for c in counts: print c, groupings[c] / counts[c] / (groupings[1] / counts[1]) + +print " " +print "Total blocks produced: ", len(miners[0].blocks) - 2 +print "Total blocks in chain: ", total_blocks_in_chain +print "Efficiency: ", total_blocks_in_chain * 1.0 / (len(miners[0].blocks) - 2) +print "Average uncles: ", total_blocks_in_chain * 1.0 / length_of_chain +print "Length of chain: ", length_of_chain +print "Block time: ", ROUNDS * 1.0 / length_of_chain From af029a7ab62b0eab7a54ef28e2aa822a12340213 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Tue, 8 Jul 2014 13:50:11 -0400 Subject: [PATCH 04/13] Added new version --- 5ghost.py | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ sim.py | 6 +- 2 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 5ghost.py 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): From b5997436e9b32762da202d264171c6932fb2f4c0 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Wed, 9 Jul 2014 01:25:32 -0400 Subject: [PATCH 05/13] Changed some parameters and percentages --- 5ghost.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/5ghost.py b/5ghost.py index c527a60..63a03ab 100644 --- a/5ghost.py +++ b/5ghost.py @@ -1,15 +1,15 @@ # Time between successful PoW solutions -POW_SOLUTION_TIME = 10 +POW_SOLUTION_TIME = 5 # Time for a block to traverse the network TRANSIT_TIME = 10 # Max uncle depth -UNCLE_DEPTH = 4 +UNCLE_DEPTH = 9 # Uncle block reward (normal block reward = 1) -UNCLE_REWARD_COEFF = 29/32. +UNCLE_REWARD_COEFF = 30/32. # Reward for including uncles -NEPHEW_REWARD_COEFF = 3/64. +NEPHEW_REWARD_COEFF = 2/64. # Rounds to test -ROUNDS = 100000 +ROUNDS = 120000 import random import copy @@ -52,6 +52,10 @@ class Miner(): 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): @@ -74,6 +78,8 @@ class Miner(): 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"] @@ -86,6 +92,7 @@ def cousin_degree(miner, b1, b2): t += 1 return t +# Set hashpower percentages here percentages = [1]*25 + [5, 5, 5, 5, 5, 10, 15, 25] miners = [] for p in percentages: From b5a7db9132adc73b38cdbb96497677f93c0d36b6 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Fri, 11 Jul 2014 09:32:30 -0400 Subject: [PATCH 06/13] changes --- 5ghost.py | 30 +++++----- ghost.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 ghost.py 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 From 622e2749a926e3a3c617e66dfc069a7f602339cc Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Fri, 11 Jul 2014 09:33:19 -0400 Subject: [PATCH 07/13] Moved files --- 5ghost.py | 173 --------------------------------- ghost.py | 2 +- sim.py => multi_uncle_ghost.py | 0 3 files changed, 1 insertion(+), 174 deletions(-) delete mode 100644 5ghost.py rename sim.py => multi_uncle_ghost.py (100%) diff --git a/5ghost.py b/5ghost.py deleted file mode 100644 index 1ac11b6..0000000 --- a/5ghost.py +++ /dev/null @@ -1,173 +0,0 @@ -# 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 = 30/32. -# Reward for including uncles -NEPHEW_REWARD_COEFF = 1/64. -# Rounds to test -ROUNDS = 200000 - -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 diff --git a/ghost.py b/ghost.py index b183540..9300fed 100644 --- a/ghost.py +++ b/ghost.py @@ -3,7 +3,7 @@ POW_SOLUTION_TIME = 60 # Time for a block to traverse the network TRANSIT_TIME = 12 # Max uncle depth -UNCLE_DEPTH = 0 +UNCLE_DEPTH = 1 # Uncle block reward (normal block reward = 1) UNCLE_REWARD_COEFF = 7/8. # Reward for including uncles diff --git a/sim.py b/multi_uncle_ghost.py similarity index 100% rename from sim.py rename to multi_uncle_ghost.py From 18880ef4f1e27bb55285cea7eda3b8ed384505ec Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Sun, 13 Jul 2014 08:50:27 -0400 Subject: [PATCH 08/13] Added mining algo test --- ghost.py | 10 +++++----- mining.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 mining.py diff --git a/ghost.py b/ghost.py index 9300fed..a415c7f 100644 --- a/ghost.py +++ b/ghost.py @@ -1,15 +1,15 @@ # Time between successful PoW solutions -POW_SOLUTION_TIME = 60 +POW_SOLUTION_TIME = 6 # Time for a block to traverse the network TRANSIT_TIME = 12 # Max uncle depth -UNCLE_DEPTH = 1 +UNCLE_DEPTH = 4 # Uncle block reward (normal block reward = 1) -UNCLE_REWARD_COEFF = 7/8. +UNCLE_REWARD_COEFF = 15/16. # Reward for including uncles -NEPHEW_REWARD_COEFF = 1/16. +NEPHEW_REWARD_COEFF = 1/32. # Rounds to test -ROUNDS = 1000000 +ROUNDS = 500000 import random diff --git a/mining.py b/mining.py new file mode 100644 index 0000000..ff7a474 --- /dev/null +++ b/mining.py @@ -0,0 +1,59 @@ +import pyethereum +u = pyethereum.utils +import time + +ops = [ + lambda x, y: x+y % 2**256, + lambda x, y: x*y % 2**256, + lambda x, y: x % y if y > 0 else x+y, + lambda x, y: x & y, + lambda x, y: x | y, + lambda x, y: x ^ y +] + + +def gen(seed, w, d): + tape = [] + h = 0 + for i in range(d): + if h < 2**32: + h = u.big_endian_to_int(u.sha3(seed+str(i))) + v1 = h % w + h /= w + v2 = h % w + h /= w + op = ops[h % len(ops)] + h /= len(ops) + tape.append([v1, v2, op]) + return tape + + +def lshift(n): + return 2**255 * (n % 2) + (n / 2) + + +def run(seed, w, tape): + v = [] + h = 0 + for i in range(w): + if i % 1 == 0: + h = u.big_endian_to_int(u.sha3(seed+str(i))) + else: + h = lshift(h) + v.append(h) + for t in tape: + v[t[0]] = t[2](v[t[0]], v[t[1]]) + return u.sha3(str(v)) + + +def test(): + t1 = time.time() + for i in range(10): + tape = gen(str(i), 1000, 1000) + print time.time() - t1 + + t2 = time.time() + for i in range(10): + p = run(str(i), 1000, tape) + print time.time() - t2 + p = p From 46998c64f5431b93fff5240020f3d32d6f82d59b Mon Sep 17 00:00:00 2001 From: Vlad Zamfir Date: Sun, 13 Jul 2014 15:49:13 +0200 Subject: [PATCH 09/13] Added comments and more detailed reporting --- mining.py | 61 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/mining.py b/mining.py index ff7a474..caf9551 100644 --- a/mining.py +++ b/mining.py @@ -2,6 +2,7 @@ import pyethereum u = pyethereum.utils import time +#These are the operations that will end up in the tape ops = [ lambda x, y: x+y % 2**256, lambda x, y: x*y % 2**256, @@ -12,9 +13,15 @@ ops = [ ] -def gen(seed, w, d): +''' +the tape will be 'w' wide and 'd' operations deep +it is a list of triples [i, j, op], later used +in the tape's execution: xi = xi op xj +''' +def gen_tape(seed, w, d): tape = [] h = 0 + #Getting as much entropy out of a hash as possible for i in range(d): if h < 2**32: h = u.big_endian_to_int(u.sha3(seed+str(i))) @@ -31,8 +38,9 @@ def gen(seed, w, d): def lshift(n): return 2**255 * (n % 2) + (n / 2) - -def run(seed, w, tape): +#Generates the inputs to and evaluates the tape, the mining nonce can be taken to be in the seed +def gen_inputs(seed,w): + #generating the tape's inputs v = [] h = 0 for i in range(w): @@ -41,19 +49,44 @@ def run(seed, w, tape): else: h = lshift(h) v.append(h) + return v + +#Evaluate tape on inputs (incorrect dimension of v is an unhandled exception) +def run_tape(v, tape): for t in tape: v[t[0]] = t[2](v[t[0]], v[t[1]]) - return u.sha3(str(v)) + #Implemented in a blockchain, any additional hashes or timestamp would be added in the sha + return str(v) + +def sha_cap(s): + return u.sha3(s) -def test(): - t1 = time.time() - for i in range(10): - tape = gen(str(i), 1000, 1000) - print time.time() - t1 +def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): + time_generating_tape = 0. + time_generating_inputs = 0. + time_evaluating_tape = 0. + time_sha_capping = 0. + for i in range(num_iterations): + t = time.time() + tape = gen_tape(str(i), tape_w, tape_d) + time_generating_tape += time.time() - t + + for i in xrange(num_tape_evals): + t = time.time() + v = gen_inputs(str(i), tape_w) + time_generating_inputs += time.time() - t + + t = time.time() + x = run_tape(v,tape) + time_evaluating_tape += time.time() - t + + t = time.time() + h = sha_cap(x) + time_sha_capping += time.time() - t - t2 = time.time() - for i in range(10): - p = run(str(i), 1000, tape) - print time.time() - t2 - p = p + total_time = time_generating_tape + time_generating_inputs + time_evaluating_tape + time_sha_capping + print "% of time generating tape:", time_generating_tape/total_time + print "% of time generating inputs:", time_generating_inputs/total_time + print "% of time evaluating tape:", time_evaluating_tape/total_time + print "% of time sha-capping:", time_sha_capping/total_time From e3d2db579db228bc363c059551e1c21d61404817 Mon Sep 17 00:00:00 2001 From: Vlad Zamfir Date: Sun, 13 Jul 2014 15:54:19 +0200 Subject: [PATCH 10/13] Comment + index fix in test() --- mining.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mining.py b/mining.py index caf9551..2119d76 100644 --- a/mining.py +++ b/mining.py @@ -61,7 +61,7 @@ def run_tape(v, tape): def sha_cap(s): return u.sha3(s) - +# This times the various parts of the hashing function - make the tape longer to make tape evaluation dominate def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): time_generating_tape = 0. time_generating_inputs = 0. @@ -72,9 +72,9 @@ def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): tape = gen_tape(str(i), tape_w, tape_d) time_generating_tape += time.time() - t - for i in xrange(num_tape_evals): + for j in xrange(num_tape_evals): t = time.time() - v = gen_inputs(str(i), tape_w) + v = gen_inputs(str(j), tape_w) time_generating_inputs += time.time() - t t = time.time() From 4233cbbd2a9c84574a4600d873173f1fef51b576 Mon Sep 17 00:00:00 2001 From: Vlad Zamfir Date: Sun, 13 Jul 2014 15:56:40 +0200 Subject: [PATCH 11/13] rm sha_cap() --- mining.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mining.py b/mining.py index 2119d76..6ea315b 100644 --- a/mining.py +++ b/mining.py @@ -58,8 +58,6 @@ def run_tape(v, tape): #Implemented in a blockchain, any additional hashes or timestamp would be added in the sha return str(v) -def sha_cap(s): - return u.sha3(s) # This times the various parts of the hashing function - make the tape longer to make tape evaluation dominate def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): @@ -82,7 +80,7 @@ def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): time_evaluating_tape += time.time() - t t = time.time() - h = sha_cap(x) + h = u.sha3(x) time_sha_capping += time.time() - t total_time = time_generating_tape + time_generating_inputs + time_evaluating_tape + time_sha_capping From e545c622672b850b51dee935f6edd4d7166183ee Mon Sep 17 00:00:00 2001 From: Vlad Zamfir Date: Sun, 13 Jul 2014 19:34:14 +0200 Subject: [PATCH 12/13] made test() a bit more friendly --- mining.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mining.py b/mining.py index 6ea315b..f49d067 100644 --- a/mining.py +++ b/mining.py @@ -59,8 +59,13 @@ def run_tape(v, tape): return str(v) -# This times the various parts of the hashing function - make the tape longer to make tape evaluation dominate -def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): +# This times the various parts of the hashing function - you can make the tape longer to make tape evaluation dominate +#num_iterations is the number of tapes that are used +#num_tape_evals is the number of nonces allowed, per tape +#tape_w is the width of the tape +#tape_d is the depth of the tape + +def test(num_iterations = 10, num_tape_evals = 1000, tape_w = 100, tape_d = 1000): time_generating_tape = 0. time_generating_inputs = 0. time_evaluating_tape = 0. @@ -69,16 +74,16 @@ def test(num_iterations, num_tape_evals, tape_w = 100, tape_d = 1000): t = time.time() tape = gen_tape(str(i), tape_w, tape_d) time_generating_tape += time.time() - t - + for j in xrange(num_tape_evals): t = time.time() v = gen_inputs(str(j), tape_w) time_generating_inputs += time.time() - t - + t = time.time() x = run_tape(v,tape) time_evaluating_tape += time.time() - t - + t = time.time() h = u.sha3(x) time_sha_capping += time.time() - t From b248823cef4ec2308fc5cccf193b506f5ab04285 Mon Sep 17 00:00:00 2001 From: Vlad Zamfir Date: Wed, 16 Jul 2014 20:38:13 +0200 Subject: [PATCH 13/13] First commit I usually run it with with: go install && mining_test, or go test -bench . -benchtime 100ms --- mining_test.go | 346 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 mining_test.go diff --git a/mining_test.go b/mining_test.go new file mode 100644 index 0000000..a2502ea --- /dev/null +++ b/mining_test.go @@ -0,0 +1,346 @@ +package main + +import ( + "fmt" + "math" + "math/big" + "encoding/hex" + "github.com/obscuren/sha3" + "testing" +) + +//For use in benchmarking +const tree_depth = 5 +const tape_width = 32 //int(math.Pow(2,tree_depth)) < would be nice, but go won't recognize as const +const tape_depth = 500 + +const Report_Zeros = true + +//Number of operations that are drawn from to form the tape and the tree +const num_ops = 7 +//This is the number of times the PoW algorithm is run +const sample_size = 100 + +func Bytes2Hex(d []byte) string { + return hex.EncodeToString(d) +} + +func Hex2Bytes(str string) []byte { + h, _ := hex.DecodeString(str) + return h +} + +func plus(z *big.Int, x *big.Int, y *big.Int) *big.Int { + var lim big.Int + lim.Exp(big.NewInt(2),big.NewInt(256), big.NewInt(0)) + z.Add(x,y) + return z.Mod(z,&lim) + +} + +func times(z *big.Int, x *big.Int, y *big.Int) *big.Int { + var lim, x1, y1, z1 big.Int + lim.Exp(big.NewInt(2),big.NewInt(256), big.NewInt(0)) + x1.Set(x) + y1.Set(y) + z.Mul(x,y) + z.Mod(z,&lim) + z1 = *big.NewInt(0) + if Report_Zeros { + if (z.Cmp(&z1) == 0 && x1.Cmp(&z1) != 0 && y1.Cmp(&z1) != 0) { + fmt.Printf("-------getting zero---------\n") + fmt.Println(Bytes2Hex(x1.Bytes())) + fmt.Println(Bytes2Hex(y1.Bytes())) + fmt.Println(Bytes2Hex(z.Bytes())) + } + } + return z +} + +func mod(z *big.Int, x *big.Int, y *big.Int) *big.Int { + + if (x.Cmp(big.NewInt(0)) == 0 || y.Cmp(big.NewInt(0)) == 0) { + return big.NewInt(0) + } + if x.Cmp(y) == -1 { //if x < y + z.Mod(y,x) + } else if x.Cmp(y) == 1 { //if x > y + z.Mod(x,y) + } + return z +} + +func xor(z *big.Int, x *big.Int, y *big.Int) *big.Int {return z.Xor(x,y)} +func and(z *big.Int, x *big.Int, y *big.Int) *big.Int {return z.And(x,y)} +func or(z *big.Int, x *big.Int, y *big.Int) *big.Int {return z.Or(x,y)} + +func rshift(z *big.Int, x *big.Int, y *big.Int) *big.Int { + var lim big.Int + lim.Exp(big.NewInt(2),big.NewInt(256), big.NewInt(0)) + z.Rsh(x,7) + return z.Mod(z,&lim) +} + + +func GetOp(i int) func(z *big.Int, x *big.Int, y *big.Int) *big.Int{ + switch i { + case 0: + return plus + case 1: + return rshift + case 2: + return mod + case 3: + return xor + case 4: + return and + case 5: + return or + case 6: + return times + } + return plus +} + +type Tapelink struct { + I int64 + J int64 + op func(z *big.Int, x *big.Int, y *big.Int) *big.Int +} + +func Sha3Bin(data []byte) []byte { + d := sha3.NewKeccak256() + d.Write(data) + return d.Sum(nil) +} + +func BenchmarkSha3Bin(b *testing.B){ + for i:= 0; i < b.N; i++ { + Sha3Bin([]byte(string(i))) + } +} + +//generates a tape of operations that is w*d long +func gen_tape(seed int64, w int, d int) []Tapelink { + var X = big.NewInt(0) + var Y = big.NewInt(int64(math.Pow(2,32))) + var M = big.NewInt(0) + var T []Tapelink + for i := 0; i < w*d; i++{ + // add empty link to tape + T = append(T, *new(Tapelink)) + + // generate new entropy as needed + if (int64(X.Cmp(Y)) == -1) {X = X.SetBytes(Sha3Bin([]byte(string(seed + int64(i)))))} + + // Pick random index I + T[i].I = M.Mod(X,big.NewInt(int64(w))).Int64() + M = big.NewInt(int64(w)) + X = X.Div(X,M) + + // Pick random index J + mm := big.NewInt(M.Mod(X,big.NewInt(int64(w-1))).Int64() + int64(1) + T[i].I) + T[i].J = M.Mod(mm, big.NewInt(int64(w))).Int64() + M = big.NewInt(int64(w-1)) + X = X.Div(X,M) + + // Pick random operation + T[i].op = GetOp(int(M.Mod(X, big.NewInt(int64(num_ops))).Int64())) + M = big.NewInt(int64(num_ops)) + X = X.Div(X,M) + } + return T +} + +func BenchmarkGen_tape(b *testing.B){ + var X big.Int + var s int64 + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + X.SetBytes(Sha3Bin([]byte(string(i)))) + s = X.Int64() + b.StartTimer() + gen_tape(s, tape_width, tape_depth) + } + +} + +func gen_inputs(seed int64, w int) []big.Int { + var A []big.Int + X := big.NewInt(0) + for i := 0; i < w; i++ { + var B big.Int + A = append(A,B) + if (i % 256 == 0) { + A[i].Set(X.SetBytes(Sha3Bin([]byte(string(seed + int64(i)))))) + } else { + A[i].Set(X.Lsh(X, 1)) + } + } + return A +} + +func BenchmarkGen_inputs(b *testing.B){ + var X big.Int + var s int64 + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + X.SetBytes(Sha3Bin([]byte(string(i)))) + s = X.Int64() + b.StartTimer() + gen_inputs(s, tape_width) + } +} + +//this changes the inputs as it goes through a tape with d links +func run_tape(tape []Tapelink, inputs []big.Int, d int) { + var X *big.Int + X = big.NewInt(0) + for i := 0; i < d; i++ { + X = tape[i].op(X, &inputs[tape[i].I], &inputs[tape[i].J]) + inputs[tape[i].I].Set(X) + } +} + +func BenchmarkRun_tape(b *testing.B){ + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + T := gen_tape(int64(i), tape_width, tape_depth) + I := gen_inputs(int64(i), tape_width) + b.StartTimer() + run_tape(T,I,tape_width*tape_depth) + } + +} + + +//returns a 2^d - 1 length tape of operations (no I or J's in the Tapelinks) +func gen_tree(seed int64, d int) []Tapelink { + M := big.NewInt(0) // dummy variable + X := big.NewInt(0) // entropy variable + Y := big.NewInt(int64(math.Pow(2,32))) // entropy buffer size + var T []Tapelink // the tree will be stored here + + for i := 0; i < int(math.Pow(2, float64(d))) - 1; i++{ + + T = append(T, *new(Tapelink)) + + //giving it more entropy, if X < 2^32 + if (X.Cmp(Y) == -1) {X = X.SetBytes(Sha3Bin([]byte(string(seed + int64(i)))))} + + //filling the tape with random ops + T[i].op = GetOp(int(M.Mod(X, big.NewInt(num_ops)).Int64())) + M = big.NewInt(num_ops) + X = X.Div(X,M) + } + return T +} + +func BenchmarkGen_tree(b *testing.B) { + var X big.Int + var s int64 + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + X.SetBytes(Sha3Bin([]byte(string(i)))) + s = X.Int64() + b.StartTimer() + gen_tree(s, tree_depth) + } +} + +//there should be 2^d inputs and 2^d - 1 links in the tape. not complying is an unhandled exception +func run_tree(inputs []big.Int, tree []Tapelink, d int) *big.Int { + X := big.NewInt(0) + counter := 0 + for j := 0; j < d; j++ { + for i := 0; i < int(math.Pow(2,float64(d - j - 1))); i++ { + X = tree[counter].op(X, &inputs[2*i], &inputs[2*i + 1]) + inputs[i].Set(X) + counter += 1 + } + } + + return &inputs[0] +} + +func BenchmarkRun_tree(b *testing.B) { + var X big.Int + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + var inputs []big.Int + for j := 0; j < tape_width; j++ { + X.SetBytes(Sha3Bin([]byte(string(j + i*tape_width)))) + inputs = append(inputs, X) + } + tree := gen_tree(X.Int64(), tree_depth) + b.StartTimer() + run_tree(inputs, tree, tree_depth) + } +} + +func sha_cap(s []string, n int) []byte{ + var feed string + feed = "" + for i := 0; i < n; i++ { + feed += s[i] + } + return Sha3Bin([]byte(feed)) +} + + +//benchmarked with only one string in the slice, s +func BenchmarkSha_cap(b *testing.B){ + var X big.Int + var s []string + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + X.SetBytes(Sha3Bin([]byte(string(i)))) + s = append(s, X.String()) + b.StartTimer() + sha_cap(s,1) + } +} + + + +func main(){ + var seed int64 + + var sample []big.Int + for i := 0; i < sample_size; i++ { + + if i%5 == 0 { + fmt.Printf("i: %d\n",i) + } + + seed = int64(i) + T := gen_tape(seed, tape_width, tape_depth) + I := gen_inputs(seed, tape_width) + run_tape(T,I, tape_depth*tape_width) + T = gen_tree(seed, tree_depth) + var output big.Int = *run_tree(I,T,tree_depth) + var blockhashes []string + blockhashes = append(blockhashes, output.String()) + H := sha_cap(blockhashes,1) + + output.SetBytes(H) + sample = append(sample, output) + } + + var collisions int = 0 + for i := 0; i < sample_size; i++ { + for j:=0; j < i; j++ { + if(sample[i].Cmp(&sample[j]) == 0) { + collisions += 1 + } + } + } + fmt.Printf("Collisions: %d out of %d\n", (1+int(math.Pow(float64(1+8*collisions),0.5)))/2 - 1, sample_size) +} +