Spectre passing unit tests!

This commit is contained in:
Brandon Goodell 2017-10-23 17:52:11 -06:00
parent 6229c28243
commit 71dbe05174
3 changed files with 326 additions and 275 deletions

View file

@ -0,0 +1,61 @@
import unittest
import math
import numpy as np
import copy
from collections import deque
import time
class Block(object):
""" Fundamental object. Contains dict of blockIDs:(parent blocks) """
def __init__(self, idIn=None, parentList=None):
self.parents = {}
self.id = ""
self.data = None
def setParents(self, parentList=None):
if parentList is not None:
self.parents = copy.deepcopy(parentList)
def setID(self, idIn = None):
if idIn is not None:
self.id = copy.deepcopy(idIn)
class Test_Block(unittest.TestCase):
def test_Block(self):
b0 = Block()
b0.setParents()
b0.setID("0")
self.assertTrue(b0.data is None)
self.assertTrue(len(b0.parents)==0)
b1 = Block()
b1.setParents(parentList={"0":b0})
b1.setID("1")
self.assertTrue(b1.data is None)
self.assertTrue(len(b1.parents)==1)
self.assertTrue("0" in b1.parents)
b2 = Block()
b2.setParents(parentList={"0":b0})
b2.setID("2")
self.assertTrue(b2.data is None)
self.assertTrue(len(b2.parents)==1)
self.assertTrue("0" in b2.parents)
b3 = Block()
b3.setParents(parentList={"1":b1, "2":b2})
b3.setID("3")
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.setParents(parentList={"2":b2})
b4.setID("4")
self.assertTrue(b4.data is None)
self.assertTrue(len(b4.parents)==1)
self.assertTrue("2" in b4.parents)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -0,0 +1,72 @@
from Block import *
class BlockDAG(object):
""" Collection of >=1 block. Also tracks IDs of leaf blocks, adds new leaves. """
def __init__(self):
self.blocks = {}
self.leaves = {}
self.genBlock = None
def startDAG(self, idIn=None, genBlockIn=None):
if genBlockIn is not None:
genesisBlock = genBlockIn
else:
genesisBlock = Block()
if idIn is None:
genesisBlock.setID(idIn="0")
else:
genesisBlock.setID(idIn)
self.genBlock = genesisBlock
self.blocks.update({self.genBlock.id:self.genBlock})
self.leaves.update({self.genBlock.id:self.genBlock})
def addLeaf(self, blockIn):
self.blocks.update({blockIn.id:blockIn})
self.leaves.update({blockIn.id:blockIn})
for parent in blockIn.parents:
if parent in self.leaves:
del self.leaves[parent]
class Test_BlockDAG(unittest.TestCase):
def test_BlockDAG(self):
dag = BlockDAG()
dag.startDAG()
self.assertTrue("0" in dag.blocks)
self.assertTrue("0" in dag.leaves)
self.assertTrue(len(dag.blocks)==1)
self.assertTrue(len(dag.leaves)==1)
b0 = dag.genBlock
b1 = Block()
b1.setParents(parentList={"0":b0})
b1.setID("1")
dag.addLeaf(b1)
self.assertTrue("1" in dag.blocks)
self.assertTrue("1" in dag.leaves)
self.assertTrue("0" not in dag.leaves)
self.assertTrue(len(dag.blocks)==2)
self.assertTrue(len(dag.leaves)==1)
b2 = Block()
b2.setParents(parentList={"0":b0})
b2.setID("2")
dag.addLeaf(b2)
b3 = Block()
b3.setParents(parentList={"1":b1, "2":b2})
b3.setID("3")
dag.addLeaf(b3)
b4 = Block()
b4.setParents(parentList={"2":b2})
b4.setID("4")
dag.addLeaf(b4)
self.assertTrue("0" in dag.blocks and "1" in dag.blocks and "2" in dag.blocks and "3" in dag.blocks and "4" in dag.blocks)
self.assertTrue("3" in dag.leaves and "4" in dag.leaves)
self.assertTrue(len(dag.blocks)==5 and len(dag.leaves)==2)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_BlockDAG)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -1,298 +1,216 @@
import unittest from BlockDAG import *
import math
import numpy as np
import copy
from collections import deque
import time
class Block(object):
""" Fundamental object. Contains dict of pointers to parent blocks. """
def __init__(self, idIn=None, parentList=None):
if parentList is None:
self.parents = {}
else:
self.parents = parentList
self.id = copy.deepcopy(idIn) # Hash of payload || header || extra2 and anything else i want to add later.
self.data = None
class BlockDAG(object):
""" Set of blocks (self.blocks), a distinguished genesis block, and a subset of leaf blocks (self.leaves)"""
def __init__(self, genBlock=None):
""" Creates vanilla BlockDAG with blank genesis block"""
self.blocks = {}
self.leaves = {}
self.genesisBlock = None
if genBlock is None:
self.genesisBlock = Block()
else:
self.genesisBlock = copy.deepcopy(genBlock)
self.blocks.update({copy.deepcopy(self.genesisBlock.id):self.genesisBlock})
self.leaves.update({copy.deepcopy(self.genesisBlock.id):self.genesisBlock})
def addBlock(self, blockIn):
""" Add new leaf blockIn to BlockDAG, update leafset """
self.blocks.update({blockIn.id:blockIn})
self.leaves.update({blockIn.id:blockIn})
q = deque()
for parent in blockIn.parents:
if parent in self.leaves:
q.append(parent)
while len(q)>0:
parent = q.popleft()
del self.leaves[parent]
class Spectre(object): class Spectre(object):
""" Contains a BlockDAG, a dictionary of children, a dictionary """ """
of observed pasts, a dictionary of observed votes, and a def __init__(self):
dictionary keyToRep that takes a block ID as key and has as self.dag = BlockDAG()
its value a representative block ID of some block with an self.childRelation = {}
identical past.""" def setDAG(self, dagIn=None):
def __init__(self, dagIn=None):
if dagIn is None: if dagIn is None:
self.dag = BlockDAG() self.dag = BlockDAG()
else: else:
self.dag = dagIn self.dag = copy.deepcopy(dagIn)
self.children = {} self.updateChildren() # self.childRelation is a dict: self.childRelation[key]={block with key in block.parents}
self.seenPasts = {}
self.seenVotes = {}
self.keyToRep = {}
def updateChildren(self): def updateChildren(self):
""" Update children dictionary: if child is a block with some self.childRelation = {}
parent, ensures that child is in self.children[parent]."""
for child in self.dag.blocks:
for parent in self.dag.blocks[child].parents:
if parent not in self.children:
self.children.update({parent:[]})
self.children[parent].append(child)
def addBlock(self, blockIn):
""" Takes block as input and includes it into the DAG and updates children."""
self.dag.addBlock(blockIn)
self.updateChildren()
def getFutureIDs(self, blockID):
""" Returns a dict of all block IDs of blocks in the future of blockID """
result = {}
q = deque() q = deque()
self.updateChildren() for blockID in self.dag.leaves:
if blockID in self.children: q.append(blockID)
for ch in self.children[blockID]: while len(q) > 0:
q.append(ch) blockID = q.popleft()
while len(q)>0:
ch = q.popleft()
if ch not in result:
result.update({ch:ch})
return result
def getPast(self, blockID):
""" Returns a sub-BlockDAG() with all the blocks from the past of blockID.
This method is made marginally more efficient using keyToRep and only
comping votes once for each given history. """
result = None # This will be the result returned. If we get None something went wrong.
if blockID in self.keyToRep: # In this case, we have computed the past before.
result = self.seenPasts[self.keyToRep[blockID]]
else: # In this case, we must compute the past.
q = deque()
pastIDs = {} # This is just a dictionary of block IDs we will check against.
for parent in self.dag.blocks[blockID].parents: for parent in self.dag.blocks[blockID].parents:
q.append(parent) # Start a queue with all the parents of blockID. q.append(parent)
while len(q)>0: # Fill pastIDs with all past block IDs. if parent not in self.childRelation:
nextID = q.popleft() # Take a block out of queue. self.childRelation.update({parent:[]})
if nextID not in pastIDs: # Record this blockID into dictionary pastIDs self.childRelation[parent].append(blockID)
pastIDs.update({nextID:nextID}) def vote(self, subdag=None):
for parent in self.dag.blocks[nextID].parents: result = {}
q.append(parent) # For each parent of the current block, enqueue them. if subdag is None:
# now queue is empty result = self.vote(self.dag)
result = BlockDAG(genBlock=self.dag.genesisBlock) # Create dummy new BlockDAG elif len(subdag.blocks)>1:
for child in self.children[self.dag.genesisBlock.id]: canopy = []
if child in pastIDs: nextdag = copy.deepcopy(subdag)
q.append(child) # Enqueue children of the genesis block that are in the past of blockID while len(nextdag.blocks)>1:
while len(q)>0: canopy.append(nextdag.leaves)
child = q.popleft() # Take a block out of queue. nextdag = self.pruneLeaves(nextdag)
for ch in self.children[child]: partialVotes = {}
if ch in pastIDs: # Enqueue children of the block that are in the past of blockID for votingBlock in subdag.blocks:
q.append(ch) partialVotes.update({votingBlock:{}})
if child not in result.blocks: for layer in canopy:
result.addBlock(blockIn=self.dag.blocks[child]) # Include current block into dummy BlockDAG for votingBlock in layer:
self.seenPasts.update({blockID:copy.deepcopy(result)}) # Include dummy BlockDAG into seenPasts thisPast = self.getPast(votingBlock)
self.keyToRep.update({blockID:blockID}) # Include blockID in the equivalence class of blockID recursiveVote = self.vote(thisPast)
return result # Return dummy BlockDAG partialVotes[votingBlock] = copy.deepcopy(recursiveVote)
futureIDs = self.getFutureIDs(votingBlock)
def stripLeaves(self, subdag): for blockX in subdag.blocks:
""" Takes as input a subdag and outputs a sub-subdag where all leaves from subdag have been deleted. """ for blockY in subdag.blocks:
result = BlockDAG(subdag.genesisBlock) if blockX in thisPast.blocks and blockY in thisPast.blocks:
q = deque() pass
q.append(subdag.genesisBlock.id) elif blockX in thisPast.blocks and blockY not in thisPast.blocks:
while len(q)>0: partialVotes[votingBlock].update({(blockX,votingBlock):True, (blockX, blockY):True, (votingBlock, blockY):True})
block = q.popleft() elif blockX not in thisPast.blocks and blockY in thisPast.blocks:
if block not in subdag.leaves: partialVotes[votingBlock].update({(blockY,votingBlock):True, (blockY,blockX):True, (votingBlock,blockX):True})
result.addBlock(subdag.blocks[block]) else:
return result partialVotes[votingBlock].update({(votingBlock,blockX):True, (votingBlock,blockY):True})
s=0
def getMajorityOfFuture(self, votingBlock, futureIDs, blockX, blockY, partial): for fid in futureIDs:
s = 0 if fid in subdag.blocks:
result = None #print("partialvotes[fid]=",partialVotes[fid])
for fids in futureIDs: if (blockX, blockY) in partialVotes[fid]:
if (fids,blockX,blockY) in partial:
s = s+1 s = s+1
elif (fids, blockY, blockX) in partial: elif (blockY, blockX) in partialVotes[fid]:
s = s-1 s = s-1
if s > 0: if s > 0:
result = (block,blockX,blockY) partialVotes[votingBlock].update({(blockX,blockY):True})
elif s < 0: elif s < 0:
result = (block,blockY,blockX) partialVotes[votingBlock].update({(blockY,blockX):True})
return result
def getSum(self, result, subdag, partial):
for blockX in subdag.blocks: for blockX in subdag.blocks:
for blockY in subdag.blocks: for blockY in subdag.blocks:
s = 0 s = 0
for votingBlock in subdag.blocks: for votingBlock in subdag.blocks:
if (votingBlock, blockX, blockY) in partial: if (blockX, blockY) in partialVotes[votingBlock]:
s = s+1 s=s+1
elif (votingBlock, blockY, blockX) in partial: elif (blockY, blockX) in partialVotes[votingBlock]:
s = s-1 s=s-1
if s > 0: if s > 0:
result.update({(blockX,blockY):True}) result.update({(blockX,blockY):True})
elif s < 0: elif s < 0:
result.update({(blockY, blockX):True}) result.update({(blockY,blockX):True})
return result return result
def getVote(self, subdag=None): def pruneLeaves(self, subdag):
""" This algorithm takes a subdag as input and computes how that subdag votes on its own interior order. newsubdag = BlockDAG()
The output is a dictionary where all values are True and keys are of the form (blockX, blockY) newsubdag.startDAG(subdag.genBlock.id, subdag.genBlock)
signifying that the network has decided blockX < blockY. q = deque()
for child in self.childRelation[subdag.genBlock.id]:
The total vote of the subdag on any pair of blocks (blockX, blockY) is defined as the majority of votes if child in subdag.blocks:
of the blocks in the subdag, i.e. the majority of votes of the form (votingBlock, blockX, blockY). Each q.append(child)
votingBlock votes using the following rules: while len(q) > 0:
(i) blocks from the past of votingBlock should precede votingBlock and blocks not from the past of votingBlock nextBlock = q.popleft()
(ii) votingBlock should precede blocks not from the past of votingBlock if nextBlock not in subdag.leaves:
(iii) votingBlock votes on pairs not from the past of votingBlock by majority of the future of votingBlock newsubdag.addLeaf(blockIn=subdag.blocks[nextBlock])
(iv) votingBlock votes on pairs from the past of votingBlock by calling getVote on the past of votingBlock if nextBlock in self.childRelation:
""" if len(self.childRelation[nextBlock])>1:
result = {} # Dictionary initially empty. for child in self.childRelation[nextBlock]:
if subdag is None: if child in subdag.blocks:
subdag = copy.deepcopy(self.dag) q.append(child)
for blockID in subdag.blocks: return newsubdag
result.update({(blockID,blockID):True}) # All blocks vote reflexively: x <= x for each x def addBlock(self, blockIn=None):
if len(subdag.blocks) > 1: if blockIn == None:
partial = {} # This dictionary will have all True values and keys of the form (votingBlock, blockX, blockY) parents = copy.deepcopy(self.dag.leaves)
for blockID in subdag.blocks: blockID = str(len(self.dag)+1)
partial.update({(blockID,blockID,blockID):True}) # All votingBlocks vote reflexively for themselves. blockIn = Block(blockID, parents)
# We are first going to compute votes for leaves. Then, we will compute votes for the blocks
# that would be leaves if all leaves of the current BlockDAG were to be deleted. We repeat
# this as expected until we only have the genesis block remaining. We call the structure canopy.
canopy = []
nextdag = copy.deepcopy(subdag)
while len(nextdag.blocks) > 1:
canopy.append(nextdag.leaves)
nextdag = self.stripLeaves(nextdag)
for layer in canopy:
for block in layer: # Compute votes from leaf-to-root as described above
# STEP 1: Get recursive vote, store in thisRecVote.
if block in self.keyToRep:
# In this case, the past of block and its vote have been computed already
# and stored with key self.keyToRep[block] in self.seenPasts
# and self.seenVotes, respectively
thisPast = self.seenPasts[self.keyToRep[block]]
thisRecVote = self.seenVotes[self.keyToRep[block]]
else: else:
# In this case, we can't tell, maybe or maybe not: perhaps the past of blockID = blockIn.id
# block has been computed, but the key self.keyToRep[block] has not self.dag.addLeaf(blockIn)
# been determined yet. for parent in blockIn.parents:
thisPast = self.getPast(block) # We first compute the past, see if it has been if parent not in self.childRelation:
for key in self.seenPasts: # computed before at any other key. If so, we self.childRelation.update({parent:[]})
if self.seenPasts[key]==thisPast: # can pull up the recursive vote and self.childRelation[parent].append(blockID)
self.keyToRep.update({block:key}) # update the keyToRep to note the
thisRecVote = self.seenVotes[key] # alternative key.
break
else: # We enter this case if we did not find any past that matches thisPast.
self.keyToRep.update({block:block}) # In this case, keyToRep is identity
thisRecVote = self.getVote(thisPast) # And we recursively compute the vote
self.seenVotes.update({self.keyToRep[block]:thisRecVote}) # then we store
self.seenPasts.update({self.keyToRep[block]:thisPast}) # the vote and the past.
# STEP 2: Get block IDs that have a vote on pairs from the past of block def getPastIDs(self, block):
futureIDs = self.getFutureIDs(block) thisPast = {}
q = deque()
# STEP 3: For every pair of blocks, either both in the pair are in the past (see thisRecVote) self.updateChildren()
# or one of the pair is in the past (inducing a natural order) for parent in self.dag.blocks[block].parents:
# or both in the pair are not in the past (majority of votes from futureIDs) q.append(parent)
for key in thisRecVote: # Extend thisRecVote into partial by taking each key of (blockX, blockY) while len(q) > 0:
newVote = (block, copy.deepcopy(key[0]), copy.deepcopy(key[1])) # and inserting (votingBlock, blockX, blockY). nextPastBlockID = q.popleft()
if newVote not in partial: if nextPastBlockID not in thisPast:
partial.update({newVote:True}) thisPast.update({nextPastBlockID:self.dag.blocks[nextPastBlockID]})
for blockX in subdag.blocks: for parent in self.dag.blocks[nextPastBlockID].parents:
for blockY in subdag.blocks: q.append(parent)
# Since we took thisRecVote into account, we can disregard the case: "both in past" return thisPast
if blockX in thisPast.blocks and blockY in thisPast.blocks: def getFutureIDs(self, block):
thisFuture = {}
q = deque()
self.updateChildren()
if block in self.childRelation:
# If this is the case, then block has at least one child.
for child in self.childRelation[block]:
q.append(child)
while len(q) > 0:
nextFutureBlockID = q.popleft()
if nextFutureBlockID not in thisFuture:
thisFuture.update({nextFutureBlockID:self.dag.blocks[nextFutureBlockID]})
if nextFutureBlockID in self.childRelation:
if len(self.childRelation[nextFutureBlockID]) > 0:
for child in self.childRelation[nextFutureBlockID]:
q.append(child)
else: # In this case, block has no children, so futureIDs should be empty.
pass pass
# If blockX is in the past of block and blockY is not, then block votes blockX < block < blockY return thisFuture
elif blockX in thisPast.blocks and blockY not in thisPast.blocks: def getPast(self, block):
partial.update({(block,blockX,block):True,(block,blockX,blockY):True,(block,block,blockY):True}) pastIDs = self.getPastIDs(block)
# If blockY is in the past of block and blockX is not, then block votes blockY < block < blockX subdag = BlockDAG()
elif blockX not in thisPast.blocks and blockY in thisPast.blocks: subdag.startDAG(idIn = self.dag.genBlock.id, genBlockIn = self.dag.genBlock)
partial.update({(block,blockY,block):True,(block,blockY,blockX):True,(block,block,blockX):True}) q = deque()
# If blockX and blockY not in the past, then.... for child in self.childRelation[self.dag.genBlock.id]:
elif blockX not in thisPast.blocks and blockY not in thisPast.blocks: if child in pastIDs:
# Could be the that blockX=blockY=block. q.append(child)
if blockX == blockY and blockX == block: while len(q) > 0:
# partial.update({(block,block,block):True}) # Unnecessary, we already did this (line 150) nextBlock = q.popleft()
pass if nextBlock in pastIDs:
# Could be that blockY=block but blockX != block and blockX not in the past of block subdag.addLeaf(self.dag.blocks[nextBlock])
# In this case, block votes that block < blockX. for child in self.childRelation[nextBlock]:
elif blockX != block and blockY == block: if child in pastIDs:
partial.update({(block,block,blockX):True}) q.append(child)
# Could be that blockX=block but blockY != block and blockY not in the past of block. return subdag
# In this case, block votes that block < blockY
elif blockX == block and blockY != block:
partial.update({(block, block, blockY):True})
# Last case: blockX != block != blockY, use majority of future blocks.
elif blockX != block and blockY != block:
maj = self.getMajorityOfFuture(block, futureIDs, blockX, blockY, partial)
if maj is not None:
partial.update({maj:True})
else:
print("DOOM AND GLOOM HOW DID YOU FIND YOURSELF HERE YOUNG CHILD?")
for key in partial:
print("Key = ", key, ", \t, val = ", partial[key])
result = self.getSum(result, subdag, partial)
return result
class Test_Spectre(unittest.TestCase): class Test_Spectre(unittest.TestCase):
def test_Spectre(self): def test_Spectre(self):
# CREATE BLOCKCHAIN shepard=Spectre()
genBlock = Block(idIn="0") b0 = Block()
brock = BlockDAG(genBlock) b0.setID("0")
shepard = Spectre(brock) shepard.dag.startDAG(idIn="0", genBlockIn=b0)
self.assertTrue(len(shepard.dag.leaves)==1 and len(shepard.dag.blocks)==1)
newBlock = Block(idIn="1", parentList=shepard.dag.leaves) b1 = Block()
shepard.addBlock(copy.deepcopy(newBlock)) b1.setParents(parentList={"0":b0})
vote = shepard.getVote() b1.setID("1")
oldVote = copy.deepcopy(vote) shepard.addBlock(b1)
self.assertTrue(("0","0") in vote)
self.assertTrue(("0","1") in vote)
self.assertTrue(("1","1") in vote)
genBlock = Block(idIn="0") b2 = Block()
brock = BlockDAG(genBlock) b2.setParents(parentList={"0":b0})
shepard = Spectre(brock) b2.setID("2")
block1 = Block(idIn="1", parentList={genBlock.id:genBlock}) shepard.addBlock(b2)
block2 = Block(idIn="2", parentList={genBlock.id:genBlock})
block3 = Block(idIn="3", parentList={"1":block1, "2":block2}) b3 = Block()
block4 = Block(idIn="4", parentList={"2":block2}) b3.setParents(parentList={"1":b1, "2":b2})
shepard.addBlock(copy.deepcopy(block1)) b3.setID("3")
shepard.addBlock(copy.deepcopy(block2)) shepard.addBlock(b3)
shepard.addBlock(copy.deepcopy(block3))
shepard.addBlock(copy.deepcopy(block4)) b4 = Block()
vote = shepard.getVote() b4.setParents(parentList={"2":b2})
print(vote) b4.setID("4")
self.assertTrue(("0","2") in vote) shepard.addBlock(b4)
self.assertTrue(("2", "1") in vote)
self.assertTrue(("1", "3") in vote) self.assertTrue("0" in shepard.dag.blocks and "1" in shepard.dag.blocks and "2" in shepard.dag.blocks and "3" in shepard.dag.blocks and "4" in shepard.dag.blocks)
self.assertTrue(("3", "4") in vote) self.assertTrue("3" in shepard.dag.leaves and "4" in shepard.dag.leaves)
self.assertFalse(("4", "2") in vote) self.assertTrue(len(shepard.dag.blocks)==5 and len(shepard.dag.leaves)==2)
self.assertTrue("0" in shepard.childRelation and "1" in shepard.childRelation and "2" in shepard.childRelation)
self.assertFalse("3" in shepard.childRelation)
self.assertFalse("4" in shepard.childRelation)
self.assertTrue("1" in shepard.childRelation["0"] and "2" in shepard.childRelation["0"])
self.assertTrue("3" in shepard.childRelation["1"])
self.assertTrue("3" in shepard.childRelation["2"] and "4" in shepard.childRelation["2"])
self.assertFalse("4" in shepard.childRelation["1"])
self.assertFalse("0" in shepard.childRelation["1"])
vote = shepard.vote()
#print(vote)
self.assertTrue(("0", "1") in vote and \
("0", "2") in vote and \
("0", "3") in vote and \
("0", "4") in vote and \
("2", "1") in vote and \
("2", "3") in vote and \
("2", "4") in vote and \
("1", "3") in vote and \
("1", "4") in vote and \
("3", "4") in vote)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Spectre) suite = unittest.TestLoader().loadTestsFromTestCase(Test_Spectre)
unittest.TextTestRunner(verbosity=1).run(suite) unittest.TextTestRunner(verbosity=1).run(suite)