This commit is contained in:
Vlad Zamfir 2014-07-21 17:33:11 +02:00
commit 59bf21c44c
5 changed files with 770 additions and 0 deletions

0
README.md Normal file
View File

173
ghost.py Normal file
View File

@ -0,0 +1,173 @@
# Time between successful PoW solutions
POW_SOLUTION_TIME = 6
# Time for a block to traverse the network
TRANSIT_TIME = 12
# Max uncle depth
UNCLE_DEPTH = 4
# Uncle block reward (normal block reward = 1)
UNCLE_REWARD_COEFF = 15/16.
# Reward for including uncles
NEPHEW_REWARD_COEFF = 1/32.
# Rounds to test
ROUNDS = 500000
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

95
mining.py Normal file
View File

@ -0,0 +1,95 @@
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,
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
]
'''
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)))
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)
#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):
if i % 1 == 0:
h = u.big_endian_to_int(u.sha3(seed+str(i)))
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]])
#Implemented in a blockchain, any additional hashes or timestamp would be added in the sha
return str(v)
# 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.
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 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
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

346
mining_test.go Normal file
View File

@ -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)
}

156
multi_uncle_ghost.py Normal file
View File

@ -0,0 +1,156 @@
# 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
# Reward for including uncles
NEPHEW_REWARD_COEFF = 0.01
# Rounds to test
ROUNDS = 80000
import random
import copy
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 = {
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": {}}
}
# ID of "latest block"
self.head = 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
for u in block["uncles"]:
if u not in self.blocks:
addme = False
p = self.blocks[block["parent"]]
if addme:
self.blocks[block["id"]] = copy.deepcopy(block)
# 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:
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):
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(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:
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"] > 1:
print h["miner"], h["height"], h["score"]
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(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