mirror of
https://github.com/monero-project/research-lab.git
synced 2024-12-22 19:49:35 +00:00
Testing new versions
This commit is contained in:
parent
807d29ac0a
commit
41c8b73f2b
2 changed files with 313 additions and 60 deletions
|
@ -1,77 +1,85 @@
|
||||||
import unittest
|
import unittest
|
||||||
import math
|
import math
|
||||||
import numpy as np
|
|
||||||
import copy
|
import copy
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import time
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
class Block(object):
|
class Block(object):
|
||||||
""" Fundamental object. Contains dict of blockIDs:(parent blocks) """
|
"""
|
||||||
|
Fundamental object. Attributes:
|
||||||
|
data = payload dict with keys "timestamp" and "txns" and others
|
||||||
|
ident = string
|
||||||
|
parents = dict {blockID : parentBlock}
|
||||||
|
Functions:
|
||||||
|
addParents : takes dict {blockID : parentBlock} as input
|
||||||
|
and updates parents to include.
|
||||||
|
_recomputeIdent : recomputes identity
|
||||||
|
Usage:
|
||||||
|
b0 = Block()
|
||||||
|
b0.data = ...
|
||||||
|
b1 = Block()
|
||||||
|
b1.data = ...
|
||||||
|
b1.addParents({b0.ident:b0})
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = "" # string
|
# Initialize with empty payload, no identity, and empty parents.
|
||||||
self.timestamp = None # format tbd
|
self.data = None
|
||||||
self.data = None # payload
|
self.ident = hash(str(0))
|
||||||
self.parents = {} # block ID : pointer to block
|
self.parents = None
|
||||||
self.children = {} # block ID : pointer to block
|
self.addParents({})
|
||||||
def addChild(self, childIn):
|
|
||||||
if childIn not in self.children:
|
def addParents(self, parentsIn): # dict of parents
|
||||||
self.children.update({childIn.id:childIn})
|
if self.parents is None:
|
||||||
def addChildren(self, childrenIn):
|
self.parents = parentsIn
|
||||||
for child in childrenIn:
|
else:
|
||||||
self.addChild(childrenIn[child])
|
self.parents.update(parentsIn)
|
||||||
def addParent(self, parentIn):
|
self._recomputeIdent()
|
||||||
if parentIn not in self.parents:
|
|
||||||
self.parents.update({parentIn.id:parentIn})
|
def _recomputeIdent(self):
|
||||||
def addParents(self, parentsIn):
|
m = str(0) + str(self.data) + str(self.parents)
|
||||||
for parent in parentsIn:
|
self.ident = hash(m)
|
||||||
self.addParent(parentsIn[parent])
|
|
||||||
|
|
||||||
|
|
||||||
class Test_Block(unittest.TestCase):
|
class Test_Block(unittest.TestCase):
|
||||||
def test_Block(self):
|
def test_Block(self):
|
||||||
|
# b0 -> b1 -> {both b2, b3} -> b4... oh, and say b3 -> b5 also
|
||||||
b0 = Block()
|
b0 = Block()
|
||||||
b0.id = "0"
|
b0.data = {"timestamp" : time.time()}
|
||||||
self.assertTrue(b0.data is None)
|
time.sleep(1)
|
||||||
self.assertTrue(len(b0.parents)==0)
|
|
||||||
|
|
||||||
b1 = Block()
|
b1 = Block()
|
||||||
b1.parents.update({"0":b0})
|
b1.data = {"timestamp" : time.time(), "txns" : [1,2,3]}
|
||||||
b1.id = "1"
|
b1.addParents({b0.ident:b0}) # updateIdent called with addParent.
|
||||||
for parentID in b1.parents:
|
time.sleep(1)
|
||||||
b1.parents[parentID].children.update({b1.id:b1})
|
|
||||||
self.assertTrue(b1.data is None)
|
|
||||||
self.assertTrue(len(b1.parents)==1)
|
|
||||||
self.assertTrue("0" in b1.parents)
|
|
||||||
|
|
||||||
b2 = Block()
|
b2 = Block()
|
||||||
b2.parents.update({"0":b0})
|
b2.data = {"timestamp" : time.time(), "txns" : None}
|
||||||
b2.id = "2"
|
b2.addParents({b1.ident:b1})
|
||||||
for parentID in b2.parents:
|
time.sleep(1)
|
||||||
b2.parents[parentID].children.update({b2.id:b2})
|
|
||||||
self.assertTrue(b2.data is None)
|
|
||||||
self.assertTrue(len(b2.parents)==1)
|
|
||||||
self.assertTrue("0" in b2.parents)
|
|
||||||
|
|
||||||
b3 = Block()
|
b3 = Block()
|
||||||
b3.parents.update({"1":b1, "2":b2})
|
b3.data = {"timestamp" : time.time(), "txns" : None}
|
||||||
b3.id = "3"
|
b3.addParents({b1.ident:b1})
|
||||||
for parentID in b3.parents:
|
time.sleep(1)
|
||||||
b3.parents[parentID].children.update({b3.id:b3})
|
|
||||||
self.assertTrue(b3.data is None)
|
|
||||||
self.assertTrue(len(b3.parents)==2)
|
|
||||||
self.assertTrue("1" in b3.parents)
|
|
||||||
self.assertTrue("2" in b3.parents)
|
|
||||||
self.assertFalse("0" in b3.parents)
|
|
||||||
|
|
||||||
b4 = Block()
|
b4 = Block()
|
||||||
b4.parents.update({"2":b2})
|
b4.data = {"timestamp" : time.time()} # see how sloppy we can be wheeee
|
||||||
b4.id = "4"
|
b4.addParents({b2.ident:b2, b3.ident:b3})
|
||||||
for parentID in b4.parents:
|
time.sleep(1)
|
||||||
b4.parents[parentID].children.update({b4.id:b4})
|
|
||||||
self.assertTrue(b4.data is None)
|
|
||||||
self.assertTrue(len(b4.parents)==1)
|
|
||||||
self.assertTrue("2" in b4.parents)
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
|
b5 = Block()
|
||||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
b5.data = {"timestamp" : time.time(), "txns" : "stuff" }
|
||||||
|
b5.addParents({b3.ident:b3})
|
||||||
|
|
||||||
|
self.assertTrue(len(b1.parents)==1 and b0.ident in b1.parents)
|
||||||
|
self.assertTrue(len(b2.parents)==1 and b1.ident in b2.parents)
|
||||||
|
self.assertTrue(len(b3.parents)==1 and b1.ident in b3.parents)
|
||||||
|
self.assertTrue(len(b4.parents)==2)
|
||||||
|
self.assertTrue(b2.ident in b4.parents and b3.ident in b4.parents)
|
||||||
|
self.assertTrue(len(b5.parents)==1 and b3.ident in b5.parents)
|
||||||
|
|
||||||
|
|
||||||
|
#suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
|
||||||
|
#unittest.TextTestRunner(verbosity=1).run(suite)
|
||||||
|
|
245
source-code/Spectre/RoBlocks.py
Normal file
245
source-code/Spectre/RoBlocks.py
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
'''
|
||||||
|
A handler for Block.py that takes a collection of blocks (which
|
||||||
|
only reference parents) as input data. It uses a doubly-linked
|
||||||
|
tree to determine precedent relationships efficiently, and it can
|
||||||
|
use that precedence relationship to produce a reduced/robust pre-
|
||||||
|
cedence relationship as output (the spectre precedence relationship
|
||||||
|
between blocks.
|
||||||
|
|
||||||
|
Another handler will extract a coherent/robust list of non-conflict-
|
||||||
|
ing transactions from a reduced/robust RoBlocks object.
|
||||||
|
'''
|
||||||
|
from Block import *
|
||||||
|
|
||||||
|
class RoBlocks(object):
|
||||||
|
def __init__(self):
|
||||||
|
print("Initializing")
|
||||||
|
# Initialize a RoBlocks object.
|
||||||
|
self.data = None
|
||||||
|
self.blocks = {} # Set of blocks (which track parents)
|
||||||
|
self.family = {} # Doubly linked list tracks parent-and-child links
|
||||||
|
self.invDLL = {} # subset of blocks unlikely to be re-orged
|
||||||
|
self.roots = {} # dict {blockIdent : block} root blocks
|
||||||
|
self.leaves = {}
|
||||||
|
self.antichainCutoff = 600 # stop re-orging after this many layers
|
||||||
|
self.pendingVotes = {}
|
||||||
|
self.votes = {}
|
||||||
|
|
||||||
|
def _addBlocks(self, blocksIn):
|
||||||
|
print("Adding Blocks")
|
||||||
|
# Take dict of {blockIdent : block} and call _addBlock on each.
|
||||||
|
for b in blocksIn:
|
||||||
|
self._addBlock(blocksIn[b])
|
||||||
|
|
||||||
|
def _addBlock(self, b):
|
||||||
|
print("Adding block")
|
||||||
|
# Take a single block b and add to self.blocks, record family
|
||||||
|
# relations, update leaf monitor, update root monitor if nec-
|
||||||
|
# essary
|
||||||
|
diffDict = {b.ident:b}
|
||||||
|
self.blocks.update(diffDict)
|
||||||
|
self.family.update({b.ident:{}})
|
||||||
|
self.family[b.ident].update({"parents":b.parents, "children":{}})
|
||||||
|
for parentIdent in b.parents:
|
||||||
|
if parentIdent not in self.family:
|
||||||
|
self.family.update({parentIdent:{}})
|
||||||
|
if "parents" not in self.family[parentIdent]:
|
||||||
|
self.family[parentIdent].update({"parents":{}})
|
||||||
|
if "children" not in self.family[parentIdent]:
|
||||||
|
self.family[parentIdent].update({"children":{}})
|
||||||
|
self.family[parentIdent]["parents"].update(b.parents)
|
||||||
|
self.family[parentIdent]["children"].update(diffDict)
|
||||||
|
if parentIdent in self.leaves:
|
||||||
|
del self.leaves[parentIdent]
|
||||||
|
if len(b.parents)==0 and b.ident not in self.roots:
|
||||||
|
self.roots.update(diffDict)
|
||||||
|
self.leaves.update(diffDict)
|
||||||
|
|
||||||
|
def inPast(self, x, y):
|
||||||
|
print("Testing if in past")
|
||||||
|
# Return true if y is an ancestor of x
|
||||||
|
q = deque()
|
||||||
|
for pid in self.blocks[x].parents:
|
||||||
|
if pid==y:
|
||||||
|
return True
|
||||||
|
break
|
||||||
|
q.append(pid)
|
||||||
|
while(len(q)>0):
|
||||||
|
nxtIdent = q.popleft()
|
||||||
|
if len(self.blocks[nxtIdent].parents) > 0:
|
||||||
|
for pid in self.blocks[nxtIdent].parents:
|
||||||
|
if pid==y:
|
||||||
|
return True
|
||||||
|
break
|
||||||
|
q.append(pid)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def vote(self):
|
||||||
|
print("Voting")
|
||||||
|
# Compute partial spectre vote for top several layers of
|
||||||
|
# the dag.
|
||||||
|
(U, vids) = self.leafBackAntichain()
|
||||||
|
self.votes = {}
|
||||||
|
|
||||||
|
q = deque()
|
||||||
|
self.pendingVotes = {}
|
||||||
|
for i in range(len(U)):
|
||||||
|
for leafId in U[i]:
|
||||||
|
if i > 0:
|
||||||
|
self.sumPendingVotes(leafId, vids)
|
||||||
|
|
||||||
|
for x in U[i]:
|
||||||
|
if x != leafId:
|
||||||
|
q.append(x)
|
||||||
|
while(len(q)>0):
|
||||||
|
x = q.popleft()
|
||||||
|
if (leafId, leafId, x) not in self.votes:
|
||||||
|
self.votes.update({(leafId, leafId, x):1})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(leafId, leafId, x)]==1
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (leafId, leafId, x) as a key in self.votes while running vote(), and it should be +1, but it isn't:\n\n", (leafId, leafId, x), self.votes[(leafId, leafId, x)], "\n\n")
|
||||||
|
if (leafId, x, leafId) not in self.votes:
|
||||||
|
self.votes.update({(leafId, x, leafId):-1})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(leafId,x,leafId)]==-1
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (leafId, x, leafId) as a key in self.votes while running vote(), and it should be +1, but it isn't:\n\n", (leafId, x, leafId), self.votes[(leafId, x, leafId)], "\n\n")
|
||||||
|
self.transmitVote(leafId, leafId, x)
|
||||||
|
for pid in self.blocks[x].parents:
|
||||||
|
if not self.inPast(leafId, pid) and pid in vids and pid != leafId:
|
||||||
|
q.append(pid)
|
||||||
|
print(self.votes)
|
||||||
|
|
||||||
|
def sumPendingVotes(self, blockId, vulnIds):
|
||||||
|
print("Summing pending votes")
|
||||||
|
# For a blockId, take all pending votes for vulnerable IDs (x,y)
|
||||||
|
# if the net is positive vote 1, if the net is negative vote -1
|
||||||
|
# otherwise vote 0.
|
||||||
|
for x in vulnIds:
|
||||||
|
for y in vulnIds:
|
||||||
|
if (blockId, x, y) in self.pendingVotes:
|
||||||
|
if self.pendingVotes[(blockId, x, y)] > 0:
|
||||||
|
if (blockId, x, y) not in self.votes:
|
||||||
|
self.votes.update({(blockId, x, y):1})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(blockId,x,y)]==1
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (blockId, x, y) as a key in self.votes, and it should be +1, but it isn't:\n\n", (blockId, x, y), self.votes[(blockId, x,y)], "\n\n")
|
||||||
|
if (blockId, y, x) not in self.votes:
|
||||||
|
self.votes.update({(blockId, y, x):-1})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(blockId,y,x)]==-1
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (blockId, y, x) as a key in self.votes, and it should be -1, but it isn't:\n\n", (blockId, y, x), self.votes[(blockId, y,x)], "\n\n")
|
||||||
|
self.transmitVote(blockId, x, y)
|
||||||
|
elif self.pendingVotes[(blockId, x, y)] < 0:
|
||||||
|
if (blockId, x, y) not in self.votes:
|
||||||
|
self.votes.update({(blockId, x, y):-1})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(blockId,x,y)]==-1
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (blockId, x, y) as a key in self.votes, and it should be -1, but it isn't:\n\n", (blockId, x, y), self.votes[(blockId, x,y)], "\n\n")
|
||||||
|
|
||||||
|
if (blockId, y, x) not in self.votes:
|
||||||
|
self.votes.update({(blockId, y, x):1})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(blockId,y,x)]==1
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (blockId, y, x) as a key in self.votes, and it should be +1, but it isn't:\n\n", (blockId, x, y), self.votes[(blockId, x,y)], "\n\n")
|
||||||
|
self.transmitVote(blockId, y, x)
|
||||||
|
else:
|
||||||
|
if (blockId, x, y) not in self.votes:
|
||||||
|
self.votes.update({(blockId, x, y):0})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(blockId,x,y)]==0
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (blockId, x, y) as a key in self.votes, and it should be 0, but it isn't:\n\n", (blockId, x, y), self.votes[(blockId, x,y)], "\n\n")
|
||||||
|
if (blockId, y, x) not in self.votes:
|
||||||
|
self.votes.update({(blockId, y, x):0})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert self.votes[(blockId,y,x)]==0
|
||||||
|
except AssertionError:
|
||||||
|
print("Woops, we found (blockId, y, x) as a key in self.votes, and it should be 0, but it isn't:\n\n", (blockId, y, x), self.votes[(blockId, y,x)], "\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def transmitVote(self, v, x, y):
|
||||||
|
print("Transmitting votes")
|
||||||
|
q = deque()
|
||||||
|
for pid in self.blocks[v].parents:
|
||||||
|
q.append(pid)
|
||||||
|
while(len(q)>0):
|
||||||
|
print("Length of queue = ", len(q))
|
||||||
|
nxtPid = q.popleft()
|
||||||
|
if (nxtPid, x, y) not in self.pendingVotes:
|
||||||
|
self.pendingVotes.update({(nxtPid,x,y):1})
|
||||||
|
self.pendingVotes.update({(nxtPid,y,x):-1})
|
||||||
|
else:
|
||||||
|
self.pendingVotes[(nxtPid,x,y)] += 1
|
||||||
|
self.pendingVotes[(nxtPid,y,x)] -= 1
|
||||||
|
if len(self.blocks[nxtPid].parents) > 0:
|
||||||
|
for pid in self.blocks[nxtPid].parents:
|
||||||
|
if pid != nxtPid:
|
||||||
|
q.append(pid)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def leafBackAntichain(self):
|
||||||
|
print("Computing antichain")
|
||||||
|
temp = copy.deepcopy(self)
|
||||||
|
decomposition = []
|
||||||
|
vulnIdents = None
|
||||||
|
decomposition.append(temp.leaves)
|
||||||
|
vulnIdents = decomposition[-1]
|
||||||
|
temp = temp.pruneLeaves()
|
||||||
|
while(len(temp.blocks)>0 and len(decomposition) < self.antichainCutoff):
|
||||||
|
decomposition.append(temp.leaves)
|
||||||
|
for xid in decomposition[-1]:
|
||||||
|
if xid not in vulnIdents:
|
||||||
|
vulnIdents.update({xid:decomposition[-1][xid]})
|
||||||
|
temp = temp.pruneLeaves()
|
||||||
|
return decomposition, vulnIdents
|
||||||
|
|
||||||
|
def pruneLeaves(self):
|
||||||
|
print("Pruning leaves")
|
||||||
|
out = RoBlocks()
|
||||||
|
q = deque()
|
||||||
|
for rootIdent in self.roots:
|
||||||
|
q.append(rootIdent)
|
||||||
|
while(len(q)>0):
|
||||||
|
thisIdent = q.popleft()
|
||||||
|
if thisIdent not in self.leaves:
|
||||||
|
out._addBlock(self.blocks[thisIdent])
|
||||||
|
for chIdent in self.family[thisIdent]["children"]:
|
||||||
|
q.append(chIdent)
|
||||||
|
return out
|
||||||
|
|
||||||
|
class Test_RoBlock(unittest.TestCase):
|
||||||
|
def test_RoBlocks(self):
|
||||||
|
R = RoBlocks()
|
||||||
|
b = Block()
|
||||||
|
b.data = "zirconium encrusted tweezers"
|
||||||
|
b._recomputeIdent()
|
||||||
|
R._addBlock(b)
|
||||||
|
|
||||||
|
b = Block()
|
||||||
|
b.data = "brontosaurus slippers cannot exist"
|
||||||
|
b.addParents(R.leaves)
|
||||||
|
R._addBlock(b)
|
||||||
|
|
||||||
|
R.vote()
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(Test_RoBlock)
|
||||||
|
unittest.TextTestRunner(verbosity=1).run(suite)
|
Loading…
Reference in a new issue