Started full PoS / BLS implementation
This commit is contained in:
parent
76c25ad2ce
commit
6250c90ed2
|
@ -0,0 +1,107 @@
|
||||||
|
from hashlib import blake2s
|
||||||
|
blake = lambda x: blake2s(x).digest()
|
||||||
|
from py_ecc.optimized_bn128 import G1, G2, add, multiply, FQ, FQ2, pairing, \
|
||||||
|
normalize, field_modulus, b, b2, is_on_curve, curve_order
|
||||||
|
|
||||||
|
def compress_G1(pt):
|
||||||
|
x, y = normalize(pt)
|
||||||
|
return x.n + 2**255 * (y.n % 2)
|
||||||
|
|
||||||
|
def decompress_G1(p):
|
||||||
|
if p == 0:
|
||||||
|
return (FQ(1), FQ(1), FQ(0))
|
||||||
|
x = p % 2**255
|
||||||
|
y_mod_2 = p // 2**255
|
||||||
|
y = pow((x**3 + b.n) % field_modulus, (field_modulus+1)//4, field_modulus)
|
||||||
|
assert pow(y, 2, field_modulus) == (x**3 + b.n) % field_modulus
|
||||||
|
if y%2 != y_mod_2:
|
||||||
|
y = field_modulus - y
|
||||||
|
return (FQ(x), FQ(y), FQ(1))
|
||||||
|
|
||||||
|
# 16th root of unity
|
||||||
|
hex_root = FQ2([21573744529824266246521972077326577680729363968861965890554801909984373949499,
|
||||||
|
16854739155576650954933913186877292401521110422362946064090026408937773542853])
|
||||||
|
|
||||||
|
assert hex_root ** 8 != FQ2([1,0])
|
||||||
|
assert hex_root ** 16 == FQ2([1,0])
|
||||||
|
|
||||||
|
def sqrt_fq2(x):
|
||||||
|
y = x ** ((field_modulus ** 2 + 15) // 32)
|
||||||
|
while y**2 != x:
|
||||||
|
y *= hex_root
|
||||||
|
return y
|
||||||
|
|
||||||
|
def hash_to_G2(m):
|
||||||
|
k2 = m
|
||||||
|
while 1:
|
||||||
|
k1 = blake(k2)
|
||||||
|
k2 = blake(k1)
|
||||||
|
x1 = int.from_bytes(k1, 'big') % field_modulus
|
||||||
|
x2 = int.from_bytes(k2, 'big') % field_modulus
|
||||||
|
x = FQ2([x1, x2])
|
||||||
|
xcb = x**3 + b2
|
||||||
|
if xcb ** ((field_modulus ** 2 - 1) // 2) == FQ2([1,0]):
|
||||||
|
break
|
||||||
|
y = sqrt_fq2(xcb)
|
||||||
|
return multiply((x, y, FQ2([1,0])), 2*field_modulus-curve_order)
|
||||||
|
|
||||||
|
def compress_G2(pt):
|
||||||
|
assert is_on_curve(pt, b2)
|
||||||
|
x, y = normalize(pt)
|
||||||
|
return (x.coeffs[0] + 2**255 * (y.coeffs[0] % 2), x.coeffs[1])
|
||||||
|
|
||||||
|
def decompress_G2(p):
|
||||||
|
x1 = p[0] % 2**255
|
||||||
|
y1_mod_2 = p[0] // 2**255
|
||||||
|
x2 = p[1]
|
||||||
|
x = FQ2([x1, x2])
|
||||||
|
if x == FQ2([0, 0]):
|
||||||
|
return FQ2([1,0]), FQ2([1,0]), FQ2([0,0])
|
||||||
|
y = sqrt_fq2(x**3 + b2)
|
||||||
|
if y.coeffs[0] % 2 != y1_mod_2:
|
||||||
|
y = y * -1
|
||||||
|
assert is_on_curve((x, y, FQ2([1,0])), b2)
|
||||||
|
return x, y, FQ2([1,0])
|
||||||
|
|
||||||
|
def sign(m, k):
|
||||||
|
return compress_G2(multiply(hash_to_G2(m), k))
|
||||||
|
|
||||||
|
def privtopub(k):
|
||||||
|
return compress_G1(multiply(G1, k))
|
||||||
|
|
||||||
|
def verify(m, pub, sig):
|
||||||
|
return pairing(decompress_G2(sig), G1) == pairing(hash_to_G2(m), decompress_G1(pub))
|
||||||
|
|
||||||
|
def aggregate_sigs(sigs):
|
||||||
|
o = FQ2([1,0]), FQ2([1,0]), FQ2([0,0])
|
||||||
|
for s in sigs:
|
||||||
|
o = add(o, decompress_G2(s))
|
||||||
|
return compress_G2(o)
|
||||||
|
|
||||||
|
def aggregate_pubs(pubs):
|
||||||
|
o = FQ(1), FQ(1), FQ(0)
|
||||||
|
for p in pubs:
|
||||||
|
o = add(o, decompress_G1(p))
|
||||||
|
return compress_G1(o)
|
||||||
|
|
||||||
|
for x in (1, 5, 124, 735, 127409812145, 90768492698215092512159, 0):
|
||||||
|
print('Testing with privkey %d' % x)
|
||||||
|
p1 = multiply(G1, x)
|
||||||
|
p2 = multiply(G2, x)
|
||||||
|
msg = str(x).encode('utf-8')
|
||||||
|
msghash = hash_to_G2(msg)
|
||||||
|
assert normalize(decompress_G1(compress_G1(p1))) == normalize(p1)
|
||||||
|
assert normalize(decompress_G2(compress_G2(p2))) == normalize(p2)
|
||||||
|
assert normalize(decompress_G2(compress_G2(msghash))) == normalize(msghash)
|
||||||
|
sig = sign(msg, x)
|
||||||
|
pub = privtopub(x)
|
||||||
|
assert verify(msg, pub, sig)
|
||||||
|
|
||||||
|
print('Testing signature aggregation')
|
||||||
|
msg = b'cow'
|
||||||
|
keys = [1, 5, 124, 735, 127409812145, 90768492698215092512159, 0]
|
||||||
|
sigs = [sign(msg, k) for k in keys]
|
||||||
|
pubs = [privtopub(k) for k in keys]
|
||||||
|
aggsig = aggregate_sigs(sigs)
|
||||||
|
aggpub = aggregate_pubs(pubs)
|
||||||
|
assert verify(msg, aggpub, aggsig)
|
|
@ -0,0 +1,68 @@
|
||||||
|
from hashlib import blake2s
|
||||||
|
blake = lambda x: blake2s(x).digest()
|
||||||
|
from ethereum.utils import normalize_address, hash32, trie_root, \
|
||||||
|
big_endian_int, address, int256, encode_hex, decode_hex, encode_int, \
|
||||||
|
big_endian_to_int
|
||||||
|
from rlp.sedes import big_endian_int, Binary, binary, CountableList
|
||||||
|
import rlp
|
||||||
|
import bls
|
||||||
|
import random
|
||||||
|
|
||||||
|
class BeaconBlock(rlp.Serializable):
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
('parent_hash', hash32),
|
||||||
|
('skip_count', int256),
|
||||||
|
('randao_reveal', hash32),
|
||||||
|
('attestation_bitmask', binary),
|
||||||
|
('attestation_aggregate_sig', int256),
|
||||||
|
('ffg_signer_list', binary),
|
||||||
|
('ffg_aggregate_sig', int256),
|
||||||
|
('main_chain_ref', hash32),
|
||||||
|
('state_hash', hash32),
|
||||||
|
('height', int256),
|
||||||
|
('sig', int256)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
parent_hash=b'\x00'*32, skip_count=0, randao_reveal=b'\x00'*32,
|
||||||
|
attestation_bitmask=b'', attestation_aggregate_sig=0,
|
||||||
|
ffg_signer_list=b'', ffg_aggregate_sig=0, main_chain_ref=b'\x00'*32,
|
||||||
|
state_hash=b'\x00'*32, height=0, sig=0):
|
||||||
|
# at the beginning of a method, locals() is a dict of all arguments
|
||||||
|
fields = {k: v for k, v in locals().items() if k != 'self'}
|
||||||
|
super(BlockHeader, self).__init__(**fields)
|
||||||
|
|
||||||
|
def quick_sample(seed, validator_count, sample_count):
|
||||||
|
k = 0
|
||||||
|
while 256**k < n:
|
||||||
|
k += 1
|
||||||
|
o = []; source = seed; pos = 0
|
||||||
|
while len(o) < sample_count:
|
||||||
|
if pos + k > 32:
|
||||||
|
source = blake(source)
|
||||||
|
pos = 0
|
||||||
|
m = big_endian_to_int(source[pos:pos+k])
|
||||||
|
if n * (m // n + 1) <= 256**k:
|
||||||
|
o.append(m % n)
|
||||||
|
pos += k
|
||||||
|
return o
|
||||||
|
|
||||||
|
privkeys = [int.from_bytes(blake2s(str(i).encode('utf-8'))) for i in range(3000)]
|
||||||
|
|
||||||
|
def mock_make_child(parent_state, skips, ):
|
||||||
|
attest
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
('parent_hash', hash32),
|
||||||
|
('skip_count', int256),
|
||||||
|
('randao_reveal', hash32),
|
||||||
|
('attestation_bitmask', binary),
|
||||||
|
('attestation_aggregate_sig', int256),
|
||||||
|
('ffg_signer_list', binary),
|
||||||
|
('ffg_aggregate_sig', int256),
|
||||||
|
('main_chain_ref', hash32),
|
||||||
|
('state_hash', hash32),
|
||||||
|
('height', int256),
|
||||||
|
('sig', int256)
|
||||||
|
]
|
|
@ -1,151 +0,0 @@
|
||||||
import os
|
|
||||||
from binascii import hexlify
|
|
||||||
from Crypto.Hash import keccak
|
|
||||||
import random
|
|
||||||
|
|
||||||
def to_hex(s):
|
|
||||||
return hexlify(s).decode('utf-8')
|
|
||||||
|
|
||||||
memo = {}
|
|
||||||
|
|
||||||
def sha3(x):
|
|
||||||
if x not in memo:
|
|
||||||
memo[x] = keccak.new(digest_bits=256, data=x).digest()
|
|
||||||
return memo[x]
|
|
||||||
|
|
||||||
def hash_to_int(h):
|
|
||||||
o = 0
|
|
||||||
for c in h:
|
|
||||||
o = (o << 8) + c
|
|
||||||
return o
|
|
||||||
|
|
||||||
NOTARIES = 40
|
|
||||||
BASE_TS_DIFF = 1
|
|
||||||
SKIP_TS_DIFF = 6
|
|
||||||
SAMPLE = 9
|
|
||||||
MIN_SAMPLE = 5
|
|
||||||
POWDIFF = 50 * NOTARIES
|
|
||||||
SHARDS = 12
|
|
||||||
|
|
||||||
def checkpow(work, nonce):
|
|
||||||
# Discrete log PoW, lolz
|
|
||||||
# Quadratic nonresidues only
|
|
||||||
return pow(work, nonce, 65537) * POWDIFF < 65537 * 2 and pow(nonce, 32768, 65537) == 65536
|
|
||||||
|
|
||||||
class MainChainBlock():
|
|
||||||
def __init__(self, parent, pownonce, ts):
|
|
||||||
self.parent_hash = parent.hash if parent else (b'\x00' * 32)
|
|
||||||
assert isinstance(self.parent_hash, bytes)
|
|
||||||
self.hash = sha3(self.parent_hash + str(pownonce).encode('utf-8'))
|
|
||||||
self.ts = ts
|
|
||||||
if parent:
|
|
||||||
assert checkpow(parent.pownonce, pownonce)
|
|
||||||
assert self.ts >= parent.ts
|
|
||||||
self.pownonce = pownonce
|
|
||||||
self.number = 0 if parent is None else parent.number + 1
|
|
||||||
|
|
||||||
|
|
||||||
main_genesis = MainChainBlock(None, 59049, 0)
|
|
||||||
|
|
||||||
class Node():
|
|
||||||
|
|
||||||
def __init__(self, _id, network, sleepy=False, careless=False, ts=0):
|
|
||||||
self.blocks = {
|
|
||||||
main_genesis.hash: main_genesis
|
|
||||||
}
|
|
||||||
self.main_chain = [main_genesis.hash]
|
|
||||||
self.timequeue = []
|
|
||||||
self.parentqueue = {}
|
|
||||||
self.children = {}
|
|
||||||
self.ts = ts
|
|
||||||
self.id = _id
|
|
||||||
self.network = network
|
|
||||||
self.used_parents = {}
|
|
||||||
self.processed = {}
|
|
||||||
self.sleepy = sleepy
|
|
||||||
self.careless = careless
|
|
||||||
|
|
||||||
def broadcast(self, x):
|
|
||||||
if self.sleepy and self.ts:
|
|
||||||
return
|
|
||||||
self.network.broadcast(self, x)
|
|
||||||
self.on_receive(x)
|
|
||||||
|
|
||||||
def log(self, words, lvl=3, all=False):
|
|
||||||
#if "Tick:" != words[:5] or self.id == 0:
|
|
||||||
if (self.id == 0 or all) and lvl >= 2:
|
|
||||||
print(self.id, words)
|
|
||||||
|
|
||||||
def on_receive(self, obj, reprocess=False):
|
|
||||||
if obj.hash in self.processed and not reprocess:
|
|
||||||
return
|
|
||||||
self.processed[obj.hash] = True
|
|
||||||
if isinstance(obj, MainChainBlock):
|
|
||||||
return self.on_receive_main_block(obj)
|
|
||||||
|
|
||||||
def add_to_timequeue(self, obj):
|
|
||||||
i = 0
|
|
||||||
while i < len(self.timequeue) and self.timequeue[i].ts < obj.ts:
|
|
||||||
i += 1
|
|
||||||
self.timequeue.insert(i, obj)
|
|
||||||
|
|
||||||
def add_to_multiset(self, _set, k, v):
|
|
||||||
if k not in _set:
|
|
||||||
_set[k] = []
|
|
||||||
_set[k].append(v)
|
|
||||||
|
|
||||||
def change_head(self, chain, new_head):
|
|
||||||
chain.extend([None] * (new_head.number + 1 - len(chain)))
|
|
||||||
i, c = new_head.number, new_head.hash
|
|
||||||
while c != chain[i]:
|
|
||||||
chain[i] = c
|
|
||||||
c = self.blocks[c].parent_hash
|
|
||||||
i -= 1
|
|
||||||
for i in range(len(chain)):
|
|
||||||
assert self.blocks[chain[i]].number == i
|
|
||||||
assert self.blocks[chain[i]].ts <= self.ts
|
|
||||||
|
|
||||||
def process_children(self, h):
|
|
||||||
if h in self.parentqueue:
|
|
||||||
for b in self.parentqueue[h]:
|
|
||||||
self.on_receive(b, reprocess=True)
|
|
||||||
del self.parentqueue[h]
|
|
||||||
|
|
||||||
def on_receive_main_block(self, block):
|
|
||||||
# Parent not yet received
|
|
||||||
if block.parent_hash not in self.blocks:
|
|
||||||
self.add_to_multiset(self.parentqueue, block.parent_hash, block)
|
|
||||||
return None
|
|
||||||
if block.ts > self.ts:
|
|
||||||
self.add_to_timequeue(block)
|
|
||||||
return None
|
|
||||||
self.log("Processing main chain block %s" % to_hex(block.hash[:4]))
|
|
||||||
self.blocks[block.hash] = block
|
|
||||||
# Reorg the main chain if new head
|
|
||||||
if block.number > self.blocks[self.main_chain[-1]].number:
|
|
||||||
reorging = (block.parent_hash != self.main_chain[-1])
|
|
||||||
self.change_head(self.main_chain, block)
|
|
||||||
# Add child record
|
|
||||||
self.add_to_multiset(self.children, block.parent_hash, block.hash)
|
|
||||||
# Final steps
|
|
||||||
self.process_children(block.hash)
|
|
||||||
self.network.broadcast(self, block)
|
|
||||||
|
|
||||||
def is_descendant(self, a, b):
|
|
||||||
a, b = self.blocks[a], self.blocks[b]
|
|
||||||
while b.number > a.number:
|
|
||||||
b = self.blocks[b.parent_hash]
|
|
||||||
return a.hash == b.hash
|
|
||||||
|
|
||||||
def tick(self):
|
|
||||||
self.ts += 0.1
|
|
||||||
self.log("Tick: %.1f" % self.ts, lvl=1)
|
|
||||||
# Process time queue
|
|
||||||
while len(self.timequeue) and self.timequeue[0].ts <= self.ts:
|
|
||||||
self.on_receive(self.timequeue.pop(0))
|
|
||||||
# Attempt to mine a main chain block
|
|
||||||
pownonce = random.randrange(65537)
|
|
||||||
mchead = self.blocks[self.main_chain[-1]]
|
|
||||||
if checkpow(mchead.pownonce, pownonce):
|
|
||||||
assert self.ts >= mchead.ts
|
|
||||||
self.broadcast(MainChainBlock(mchead, pownonce, self.ts))
|
|
|
@ -1,16 +1,18 @@
|
||||||
from networksim import NetworkSimulator
|
from networksim import NetworkSimulator
|
||||||
from node import Node, NOTARIES, MainChainBlock, main_genesis
|
from pos_node import Node, NOTARIES, Block, genesis
|
||||||
from distributions import normal_distribution
|
from distributions import normal_distribution
|
||||||
|
|
||||||
net = NetworkSimulator(latency=12)
|
net = NetworkSimulator(latency=12)
|
||||||
notaries = [Node(i, net, ts=max(normal_distribution(50, 50)(), 0)) for i in range(NOTARIES)]
|
notaries = [Node(i, net, ts=max(normal_distribution(200, 200)(), 0) * 0.1, sleepy=i%4==0) for i in range(NOTARIES)]
|
||||||
net.agents = notaries
|
net.agents = notaries
|
||||||
net.generate_peers()
|
net.generate_peers()
|
||||||
for i in range(4000):
|
for i in range(100000):
|
||||||
net.tick()
|
net.tick()
|
||||||
for n in notaries:
|
for n in notaries:
|
||||||
|
print("Local timestamp: %.1f, timequeue len %d" % (n.ts, len(n.timequeue)))
|
||||||
print("Main chain head: %d" % n.blocks[n.main_chain[-1]].number)
|
print("Main chain head: %d" % n.blocks[n.main_chain[-1]].number)
|
||||||
print("Total main chain blocks received: %d" % (len([b for b in n.blocks.values() if isinstance(b, MainChainBlock)]) - 1))
|
print("Total main chain blocks received: %d" % (len([b for b in n.blocks.values() if isinstance(b, Block)]) - 1))
|
||||||
|
print("Notarized main chain blocks received: %d" % (len([b for b in n.blocks.values() if isinstance(b, Block) and n.is_notarized(b)]) - 1))
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
@ -18,15 +20,22 @@ import random
|
||||||
|
|
||||||
G=nx.Graph()
|
G=nx.Graph()
|
||||||
|
|
||||||
#positions = {main_genesis.hash: 0, beacon_genesis.hash: 0}
|
#positions = {genesis.hash: 0, beacon_genesis.hash: 0}
|
||||||
#queue = [
|
#queue = [
|
||||||
|
|
||||||
for b in n.blocks.values():
|
for b in n.blocks.values():
|
||||||
|
for en in notaries:
|
||||||
|
if isinstance(b, Block) and b.hash in en.processed and b.hash not in en.blocks:
|
||||||
|
assert (not en.have_ancestry(b.hash)) or b.ts > en.ts
|
||||||
if b.number > 0:
|
if b.number > 0:
|
||||||
if isinstance(b, MainChainBlock):
|
if isinstance(b, Block):
|
||||||
|
if n.is_notarized(b):
|
||||||
G.add_edge(b.hash, b.parent_hash, color='b')
|
G.add_edge(b.hash, b.parent_hash, color='b')
|
||||||
|
else:
|
||||||
|
G.add_edge(b.hash, b.parent_hash, color='#dddddd')
|
||||||
|
|
||||||
cache = {main_genesis.hash: 0}
|
|
||||||
|
cache = {genesis.hash: 0}
|
||||||
|
|
||||||
def mkoffset(b):
|
def mkoffset(b):
|
||||||
if b.hash not in cache:
|
if b.hash not in cache:
|
||||||
|
|
|
@ -1,363 +0,0 @@
|
||||||
import os
|
|
||||||
from binascii import hexlify
|
|
||||||
from Crypto.Hash import keccak
|
|
||||||
import random
|
|
||||||
|
|
||||||
def to_hex(s):
|
|
||||||
return hexlify(s).decode('utf-8')
|
|
||||||
|
|
||||||
memo = {}
|
|
||||||
|
|
||||||
def sha3(x):
|
|
||||||
if x not in memo:
|
|
||||||
memo[x] = keccak.new(digest_bits=256, data=x).digest()
|
|
||||||
return memo[x]
|
|
||||||
|
|
||||||
def hash_to_int(h):
|
|
||||||
o = 0
|
|
||||||
for c in h:
|
|
||||||
o = (o << 8) + c
|
|
||||||
return o
|
|
||||||
|
|
||||||
NOTARIES = 40
|
|
||||||
BASE_TS_DIFF = 1
|
|
||||||
SKIP_TS_DIFF = 6
|
|
||||||
SAMPLE = 9
|
|
||||||
MIN_SAMPLE = 5
|
|
||||||
POWDIFF = 50 * NOTARIES
|
|
||||||
SHARDS = 12
|
|
||||||
|
|
||||||
def checkpow(work, nonce):
|
|
||||||
# Discrete log PoW, lolz
|
|
||||||
# Quadratic nonresidues only
|
|
||||||
return pow(work, nonce, 65537) * POWDIFF < 65537 * 2 and pow(nonce, 32768, 65537) == 65536
|
|
||||||
|
|
||||||
class MainChainBlock():
|
|
||||||
def __init__(self, parent, pownonce, ts):
|
|
||||||
self.parent_hash = parent.hash if parent else (b'\x00' * 32)
|
|
||||||
assert isinstance(self.parent_hash, bytes)
|
|
||||||
self.hash = sha3(self.parent_hash + str(pownonce).encode('utf-8'))
|
|
||||||
self.ts = ts
|
|
||||||
if parent:
|
|
||||||
assert checkpow(parent.pownonce, pownonce)
|
|
||||||
assert self.ts >= parent.ts
|
|
||||||
self.pownonce = pownonce
|
|
||||||
self.number = 0 if parent is None else parent.number + 1
|
|
||||||
|
|
||||||
|
|
||||||
# Not a full RANDAO; stub for now
|
|
||||||
class BeaconBlock():
|
|
||||||
def __init__(self, parent, proposer, ts, sigs, main_chain_ref):
|
|
||||||
self.contents = os.urandom(32)
|
|
||||||
self.parent_hash = parent.hash if parent else (b'\x11' * 32)
|
|
||||||
self.hash = sha3(self.parent_hash + self.contents)
|
|
||||||
self.ts = ts
|
|
||||||
self.sigs = sigs
|
|
||||||
self.number = parent.number + 1 if parent else 0
|
|
||||||
self.main_chain_ref = main_chain_ref.hash if main_chain_ref else parent.main_chain_ref
|
|
||||||
|
|
||||||
if parent:
|
|
||||||
i = parent.child_proposers.index(proposer)
|
|
||||||
assert self.ts >= parent.ts + BASE_TS_DIFF + i * SKIP_TS_DIFF
|
|
||||||
assert len(sigs) >= parent.notary_req
|
|
||||||
for sig in sigs:
|
|
||||||
assert sig.target_hash == self.parent_hash
|
|
||||||
|
|
||||||
# Calculate child proposers
|
|
||||||
v = hash_to_int(sha3(self.contents))
|
|
||||||
self.child_proposers = []
|
|
||||||
while v > 0:
|
|
||||||
self.child_proposers.append(v % NOTARIES)
|
|
||||||
v //= NOTARIES
|
|
||||||
|
|
||||||
# Calculate notaries
|
|
||||||
first = parent and proposer == parent.child_proposers[0]
|
|
||||||
self.notary_req = 0 if first else MIN_SAMPLE
|
|
||||||
v = hash_to_int(sha3(self.contents + b':n'))
|
|
||||||
self.notaries = []
|
|
||||||
for i in range(SAMPLE if first else SAMPLE):
|
|
||||||
self.notaries.append(v % NOTARIES)
|
|
||||||
v //= NOTARIES
|
|
||||||
|
|
||||||
# Calculate shard proposers
|
|
||||||
v = hash_to_int(sha3(self.contents + b':s'))
|
|
||||||
self.shard_proposers = []
|
|
||||||
for i in range(SHARDS):
|
|
||||||
self.shard_proposers.append(v % NOTARIES)
|
|
||||||
v //= NOTARIES
|
|
||||||
|
|
||||||
|
|
||||||
class Sig():
|
|
||||||
def __init__(self, proposer, target):
|
|
||||||
self.proposer = proposer
|
|
||||||
self.target_hash = target.hash
|
|
||||||
self.hash = os.urandom(32)
|
|
||||||
assert self.proposer in target.notaries
|
|
||||||
|
|
||||||
class ShardCollation():
|
|
||||||
def __init__(self, shard_id, parent, proposer, beacon_ref, ts):
|
|
||||||
self.proposer = proposer
|
|
||||||
self.parent_hash = parent.hash if parent else (bytes([40 + shard_id]) * 32)
|
|
||||||
self.hash = sha3(self.parent_hash + str(self.proposer).encode('utf-8') + beacon_ref.hash)
|
|
||||||
self.ts = ts
|
|
||||||
self.shard_id = shard_id
|
|
||||||
self.number = parent.number + 1 if parent else 0
|
|
||||||
self.beacon_ref = beacon_ref.hash
|
|
||||||
|
|
||||||
if parent:
|
|
||||||
assert self.shard_id == parent.shard_id
|
|
||||||
assert self.proposer == beacon_ref.shard_proposers[self.shard_id]
|
|
||||||
assert self.ts >= parent.ts
|
|
||||||
|
|
||||||
assert self.ts >= beacon_ref.ts
|
|
||||||
|
|
||||||
main_genesis = MainChainBlock(None, 59049, 0)
|
|
||||||
beacon_genesis = BeaconBlock(None, 1, 0, [], main_genesis)
|
|
||||||
shard_geneses = [ShardCollation(i, None, 0, beacon_genesis, 0) for i in range(SHARDS)]
|
|
||||||
|
|
||||||
class BlockMakingRequest():
|
|
||||||
def __init__(self, parent, ts):
|
|
||||||
self.parent = parent
|
|
||||||
self.ts = ts
|
|
||||||
self.hash = os.urandom(32)
|
|
||||||
|
|
||||||
class Node():
|
|
||||||
|
|
||||||
def __init__(self, _id, network, sleepy=False, careless=False):
|
|
||||||
self.blocks = {
|
|
||||||
beacon_genesis.hash: beacon_genesis,
|
|
||||||
main_genesis.hash: main_genesis
|
|
||||||
}
|
|
||||||
for s in shard_geneses:
|
|
||||||
self.blocks[s.hash] = s
|
|
||||||
self.sigs = {}
|
|
||||||
self.beacon_chain = [beacon_genesis.hash]
|
|
||||||
self.main_chain = [main_genesis.hash]
|
|
||||||
self.shard_chains = [[g.hash] for g in shard_geneses]
|
|
||||||
self.timequeue = []
|
|
||||||
self.parentqueue = {}
|
|
||||||
self.children = {}
|
|
||||||
self.ts = 0
|
|
||||||
self.id = _id
|
|
||||||
self.network = network
|
|
||||||
self.used_parents = {}
|
|
||||||
self.processed = {}
|
|
||||||
self.sleepy = sleepy
|
|
||||||
self.careless = careless
|
|
||||||
|
|
||||||
def broadcast(self, x):
|
|
||||||
if self.sleepy and self.ts:
|
|
||||||
return
|
|
||||||
#self.log("Broadcasting %s %s" % ("block" if isinstance(x, BeaconBlock) else "sig", to_hex(x.hash[:4])))
|
|
||||||
self.network.broadcast(self, x)
|
|
||||||
self.on_receive(x)
|
|
||||||
|
|
||||||
def log(self, words, lvl=3, all=False):
|
|
||||||
#if "Tick:" != words[:5] or self.id == 0:
|
|
||||||
if (self.id == 0 or all) and lvl >= 2:
|
|
||||||
print(self.id, words)
|
|
||||||
|
|
||||||
def on_receive(self, obj, reprocess=False):
|
|
||||||
if obj.hash in self.processed and not reprocess:
|
|
||||||
return
|
|
||||||
self.processed[obj.hash] = True
|
|
||||||
#self.log("Processing %s %s" % ("block" if isinstance(obj, BeaconBlock) else "sig", to_hex(obj.hash[:4])))
|
|
||||||
if isinstance(obj, BeaconBlock):
|
|
||||||
return self.on_receive_beacon_block(obj)
|
|
||||||
elif isinstance(obj, MainChainBlock):
|
|
||||||
return self.on_receive_main_block(obj)
|
|
||||||
elif isinstance(obj, ShardCollation):
|
|
||||||
return self.on_receive_shard_collation(obj)
|
|
||||||
elif isinstance(obj, Sig):
|
|
||||||
return self.on_receive_sig(obj)
|
|
||||||
elif isinstance(obj, BlockMakingRequest):
|
|
||||||
if self.beacon_chain[-1] == obj.parent:
|
|
||||||
mc_ref = self.blocks[obj.parent]
|
|
||||||
for i in range(2):
|
|
||||||
if mc_ref.number == 0:
|
|
||||||
break
|
|
||||||
#mc_ref = self.blocks[mc_ref].parent_hash
|
|
||||||
x = BeaconBlock(self.blocks[obj.parent], self.id, self.ts,
|
|
||||||
self.sigs[obj.parent] if obj.parent in self.sigs else [],
|
|
||||||
self.blocks[self.main_chain[-1]])
|
|
||||||
self.log("Broadcasting block %s" % to_hex(x.hash[:4]))
|
|
||||||
self.broadcast(x)
|
|
||||||
|
|
||||||
def add_to_timequeue(self, obj):
|
|
||||||
i = 0
|
|
||||||
while i < len(self.timequeue) and self.timequeue[i].ts < obj.ts:
|
|
||||||
i += 1
|
|
||||||
self.timequeue.insert(i, obj)
|
|
||||||
|
|
||||||
def add_to_multiset(self, _set, k, v):
|
|
||||||
if k not in _set:
|
|
||||||
_set[k] = []
|
|
||||||
_set[k].append(v)
|
|
||||||
|
|
||||||
def change_head(self, chain, new_head):
|
|
||||||
chain.extend([None] * (new_head.number + 1 - len(chain)))
|
|
||||||
i, c = new_head.number, new_head.hash
|
|
||||||
while c != chain[i]:
|
|
||||||
chain[i] = c
|
|
||||||
c = self.blocks[c].parent_hash
|
|
||||||
i -= 1
|
|
||||||
for i in range(len(chain)):
|
|
||||||
assert self.blocks[chain[i]].number == i
|
|
||||||
|
|
||||||
def recalculate_head(self, chain, condition):
|
|
||||||
while not condition(self.blocks[chain[-1]]):
|
|
||||||
chain.pop()
|
|
||||||
descendant_queue = [chain[-1]]
|
|
||||||
new_head = chain[-1]
|
|
||||||
while len(descendant_queue):
|
|
||||||
first = descendant_queue.pop(0)
|
|
||||||
if first in self.children:
|
|
||||||
for c in self.children[first]:
|
|
||||||
if condition(self.blocks[c]):
|
|
||||||
descendant_queue.append(c)
|
|
||||||
if self.blocks[first].number > self.blocks[new_head].number:
|
|
||||||
new_head = first
|
|
||||||
self.change_head(chain, self.blocks[new_head])
|
|
||||||
for i in range(len(chain)):
|
|
||||||
assert condition(self.blocks[chain[i]])
|
|
||||||
|
|
||||||
def process_children(self, h):
|
|
||||||
if h in self.parentqueue:
|
|
||||||
for b in self.parentqueue[h]:
|
|
||||||
self.on_receive(b, reprocess=True)
|
|
||||||
del self.parentqueue[h]
|
|
||||||
|
|
||||||
def on_receive_main_block(self, block):
|
|
||||||
# Parent not yet received
|
|
||||||
if block.parent_hash not in self.blocks:
|
|
||||||
self.add_to_multiset(self.parentqueue, block.parent_hash, block)
|
|
||||||
return None
|
|
||||||
self.log("Processing main chain block %s" % to_hex(block.hash[:4]))
|
|
||||||
self.blocks[block.hash] = block
|
|
||||||
# Reorg the main chain if new head
|
|
||||||
if block.number > self.blocks[self.main_chain[-1]].number:
|
|
||||||
reorging = (block.parent_hash != self.main_chain[-1])
|
|
||||||
self.change_head(self.main_chain, block)
|
|
||||||
if reorging:
|
|
||||||
self.recalculate_head(self.beacon_chain,
|
|
||||||
lambda b: isinstance(b, BeaconBlock) and b.main_chain_ref in self.main_chain)
|
|
||||||
for i in range(SHARDS):
|
|
||||||
self.recalculate_head(self.shard_chains[i],
|
|
||||||
lambda b: isinstance(b, ShardCollation) and b.shard_id == i and b.beacon_ref in self.beacon_chain)
|
|
||||||
# Add child record
|
|
||||||
self.add_to_multiset(self.children, block.parent_hash, block.hash)
|
|
||||||
# Final steps
|
|
||||||
self.process_children(block.hash)
|
|
||||||
self.network.broadcast(self, block)
|
|
||||||
|
|
||||||
def is_descendant(self, a, b):
|
|
||||||
a, b = self.blocks[a], self.blocks[b]
|
|
||||||
while b.number > a.number:
|
|
||||||
b = self.blocks[b.parent_hash]
|
|
||||||
return a.hash == b.hash
|
|
||||||
|
|
||||||
def change_beacon_head(self, new_head):
|
|
||||||
self.log("Changed beacon head: %s" % new_head.number)
|
|
||||||
reorging = (new_head.parent_hash != self.beacon_chain[-1])
|
|
||||||
self.change_head(self.beacon_chain, new_head)
|
|
||||||
if reorging:
|
|
||||||
for i in range(SHARDS):
|
|
||||||
self.recalculate_head(self.shard_chains[i],
|
|
||||||
lambda b: isinstance(b, ShardCollation) and b.shard_id == i and b.beacon_ref in self.beacon_chain)
|
|
||||||
# Produce shard collations?
|
|
||||||
for s in range(SHARDS):
|
|
||||||
if self.id == new_head.shard_proposers[s]:
|
|
||||||
sc = ShardCollation(s, self.blocks[self.shard_chains[s][-1]], self.id, new_head, self.ts)
|
|
||||||
assert sc.beacon_ref == new_head.hash
|
|
||||||
assert self.is_descendant(self.blocks[sc.parent_hash].beacon_ref, new_head.hash)
|
|
||||||
self.broadcast(sc)
|
|
||||||
for c in self.shard_chains[s]:
|
|
||||||
assert self.blocks[c].shard_id == s and self.blocks[c].beacon_ref in self.beacon_chain
|
|
||||||
|
|
||||||
def on_receive_beacon_block(self, block):
|
|
||||||
# Parent not yet received
|
|
||||||
if block.parent_hash not in self.blocks:
|
|
||||||
self.add_to_multiset(self.parentqueue, block.parent_hash, block)
|
|
||||||
return
|
|
||||||
# Main chain parent not yet received
|
|
||||||
if block.main_chain_ref not in self.blocks:
|
|
||||||
self.add_to_multiset(self.parentqueue, block.main_chain_ref, block)
|
|
||||||
return
|
|
||||||
# Too early
|
|
||||||
if block.ts > self.ts:
|
|
||||||
self.add_to_timequeue(block)
|
|
||||||
return
|
|
||||||
# Check consistency of cross-link reference
|
|
||||||
assert self.is_descendant(self.blocks[block.parent_hash].main_chain_ref, block.main_chain_ref)
|
|
||||||
# Add the block
|
|
||||||
self.log("Processing beacon block %s" % to_hex(block.hash[:4]))
|
|
||||||
self.blocks[block.hash] = block
|
|
||||||
# Am I a notary, and is the block building on the head? Then broadcast a signature.
|
|
||||||
if block.parent_hash == self.beacon_chain[-1] or self.careless:
|
|
||||||
if self.id in block.notaries:
|
|
||||||
self.broadcast(Sig(self.id, block))
|
|
||||||
# Check for sigs, add to head?, make a block?
|
|
||||||
if len(self.sigs.get(block.hash, [])) >= block.notary_req:
|
|
||||||
if block.number > self.blocks[self.beacon_chain[-1]].number and block.main_chain_ref in self.main_chain:
|
|
||||||
self.change_beacon_head(block)
|
|
||||||
if self.id in self.blocks[block.hash].child_proposers:
|
|
||||||
my_index = self.blocks[block.hash].child_proposers.index(self.id)
|
|
||||||
target_ts = block.ts + BASE_TS_DIFF + my_index * SKIP_TS_DIFF
|
|
||||||
self.add_to_timequeue(BlockMakingRequest(block.hash, target_ts))
|
|
||||||
# Add child record
|
|
||||||
self.add_to_multiset(self.children, block.parent_hash, block.hash)
|
|
||||||
# Final steps
|
|
||||||
self.process_children(block.hash)
|
|
||||||
self.network.broadcast(self, block)
|
|
||||||
|
|
||||||
def on_receive_sig(self, sig):
|
|
||||||
self.add_to_multiset(self.sigs, sig.target_hash, sig)
|
|
||||||
# Add to head? Make a block?
|
|
||||||
if sig.target_hash in self.blocks and len(self.sigs[sig.target_hash]) == self.blocks[sig.target_hash].notary_req:
|
|
||||||
block = self.blocks[sig.target_hash]
|
|
||||||
if block.number > self.blocks[self.beacon_chain[-1]].number and block.main_chain_ref in self.main_chain:
|
|
||||||
self.change_beacon_head(block)
|
|
||||||
if self.id in block.child_proposers:
|
|
||||||
my_index = block.child_proposers.index(self.id)
|
|
||||||
target_ts = block.ts + BASE_TS_DIFF + my_index * SKIP_TS_DIFF
|
|
||||||
self.log("Making block request for %.1f" % target_ts)
|
|
||||||
self.add_to_timequeue(BlockMakingRequest(block.hash, target_ts))
|
|
||||||
# Rebroadcast
|
|
||||||
self.network.broadcast(self, sig)
|
|
||||||
|
|
||||||
def on_receive_shard_collation(self, block):
|
|
||||||
# Parent not yet received
|
|
||||||
if block.parent_hash not in self.blocks:
|
|
||||||
self.add_to_multiset(self.parentqueue, block.parent_hash, block)
|
|
||||||
return None
|
|
||||||
# Beacon ref not yet received
|
|
||||||
if block.beacon_ref not in self.blocks:
|
|
||||||
self.add_to_multiset(self.parentqueue, block.beacon_ref, block)
|
|
||||||
return None
|
|
||||||
# Check consistency of cross-link reference
|
|
||||||
assert self.is_descendant(self.blocks[block.parent_hash].beacon_ref, block.beacon_ref)
|
|
||||||
self.log("Processing shard collation %s" % to_hex(block.hash[:4]))
|
|
||||||
self.blocks[block.hash] = block
|
|
||||||
# Set head if needed
|
|
||||||
if block.number > self.blocks[self.shard_chains[block.shard_id][-1]].number and block.beacon_ref in self.beacon_chain:
|
|
||||||
self.change_head(self.shard_chains[block.shard_id], block)
|
|
||||||
# Add child record
|
|
||||||
self.add_to_multiset(self.children, block.parent_hash, block.hash)
|
|
||||||
# Final steps
|
|
||||||
self.process_children(block.hash)
|
|
||||||
self.network.broadcast(self, block)
|
|
||||||
|
|
||||||
def tick(self):
|
|
||||||
if self.ts == 0:
|
|
||||||
if self.id in beacon_genesis.notaries:
|
|
||||||
self.broadcast(Sig(self.id, beacon_genesis))
|
|
||||||
self.ts += 0.1
|
|
||||||
self.log("Tick: %.1f" % self.ts, lvl=1)
|
|
||||||
# Process time queue
|
|
||||||
while len(self.timequeue) and self.timequeue[0].ts <= self.ts:
|
|
||||||
self.on_receive(self.timequeue.pop(0))
|
|
||||||
# Attempt to mine a main chain block
|
|
||||||
pownonce = random.randrange(65537)
|
|
||||||
mchead = self.blocks[self.main_chain[-1]]
|
|
||||||
if checkpow(mchead.pownonce, pownonce):
|
|
||||||
self.broadcast(MainChainBlock(mchead, pownonce, self.ts))
|
|
Loading…
Reference in New Issue