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.
# now queue is empty
result = BlockDAG(genBlock=self.dag.genesisBlock) # Create dummy new BlockDAG
for child in self.children[self.dag.genesisBlock.id]:
if child in pastIDs:
q.append(child) # Enqueue children of the genesis block that are in the past of blockID
while len(q)>0:
child = q.popleft() # Take a block out of queue.
for ch in self.children[child]:
if ch in pastIDs: # Enqueue children of the block that are in the past of blockID
q.append(ch)
if child not in result.blocks:
result.addBlock(blockIn=self.dag.blocks[child]) # Include current block into dummy BlockDAG
self.seenPasts.update({blockID:copy.deepcopy(result)}) # Include dummy BlockDAG into seenPasts
self.keyToRep.update({blockID:blockID}) # Include blockID in the equivalence class of blockID
return result # Return dummy BlockDAG
def stripLeaves(self, subdag):
""" Takes as input a subdag and outputs a sub-subdag where all leaves from subdag have been deleted. """
result = BlockDAG(subdag.genesisBlock)
q = deque()
q.append(subdag.genesisBlock.id)
while len(q)>0:
block = q.popleft()
if block not in subdag.leaves:
result.addBlock(subdag.blocks[block])
return result
def getMajorityOfFuture(self, votingBlock, futureIDs, blockX, blockY, partial):
s = 0
result = None
for fids in futureIDs:
if (fids,blockX,blockY) in partial:
s = s+1
elif (fids, blockY, blockX) in partial:
s = s-1
if s > 0:
result = (block,blockX,blockY)
elif s < 0:
result = (block,blockY,blockX)
return result
def getSum(self, result, subdag, partial):
for blockX in subdag.blocks:
for blockY in subdag.blocks:
s = 0
for votingBlock in subdag.blocks:
if (votingBlock, blockX, blockY) in partial:
s = s+1
elif (votingBlock, blockY, blockX) in partial:
s = s-1
if s > 0:
result.update({(blockX,blockY):True})
elif s < 0:
result.update({(blockY, blockX):True})
return result
def getVote(self, subdag=None):
""" This algorithm takes a subdag as input and computes how that subdag votes on its own interior order.
The output is a dictionary where all values are True and keys are of the form (blockX, blockY)
signifying that the network has decided blockX < blockY.
The total vote of the subdag on any pair of blocks (blockX, blockY) is defined as the majority of votes
of the blocks in the subdag, i.e. the majority of votes of the form (votingBlock, blockX, blockY). Each
votingBlock votes using the following rules:
(i) blocks from the past of votingBlock should precede votingBlock and blocks not from the past of votingBlock
(ii) votingBlock should precede blocks not from the past of votingBlock
(iii) votingBlock votes on pairs not from the past of votingBlock by majority of the future of votingBlock
(iv) votingBlock votes on pairs from the past of votingBlock by calling getVote on the past of votingBlock
"""
result = {} # Dictionary initially empty.
if subdag is None: if subdag is None:
subdag = copy.deepcopy(self.dag) result = self.vote(self.dag)
for blockID in subdag.blocks: elif len(subdag.blocks)>1:
result.update({(blockID,blockID):True}) # All blocks vote reflexively: x <= x for each x
if len(subdag.blocks) > 1:
partial = {} # This dictionary will have all True values and keys of the form (votingBlock, blockX, blockY)
for blockID in subdag.blocks:
partial.update({(blockID,blockID,blockID):True}) # All votingBlocks vote reflexively for themselves.
# 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 = [] canopy = []
nextdag = copy.deepcopy(subdag) nextdag = copy.deepcopy(subdag)
while len(nextdag.blocks) > 1: while len(nextdag.blocks)>1:
canopy.append(nextdag.leaves) canopy.append(nextdag.leaves)
nextdag = self.stripLeaves(nextdag) nextdag = self.pruneLeaves(nextdag)
partialVotes = {}
for votingBlock in subdag.blocks:
partialVotes.update({votingBlock:{}})
for layer in canopy: for layer in canopy:
for block in layer: # Compute votes from leaf-to-root as described above for votingBlock in layer:
thisPast = self.getPast(votingBlock)
# STEP 1: Get recursive vote, store in thisRecVote. recursiveVote = self.vote(thisPast)
if block in self.keyToRep: partialVotes[votingBlock] = copy.deepcopy(recursiveVote)
# In this case, the past of block and its vote have been computed already futureIDs = self.getFutureIDs(votingBlock)
# and stored with key self.keyToRep[block] in self.seenPasts for blockX in subdag.blocks:
# and self.seenVotes, respectively for blockY in subdag.blocks:
thisPast = self.seenPasts[self.keyToRep[block]] if blockX in thisPast.blocks and blockY in thisPast.blocks:
thisRecVote = self.seenVotes[self.keyToRep[block]] pass
else: elif blockX in thisPast.blocks and blockY not in thisPast.blocks:
# In this case, we can't tell, maybe or maybe not: perhaps the past of partialVotes[votingBlock].update({(blockX,votingBlock):True, (blockX, blockY):True, (votingBlock, blockY):True})
# block has been computed, but the key self.keyToRep[block] has not elif blockX not in thisPast.blocks and blockY in thisPast.blocks:
# been determined yet. partialVotes[votingBlock].update({(blockY,votingBlock):True, (blockY,blockX):True, (votingBlock,blockX):True})
thisPast = self.getPast(block) # We first compute the past, see if it has been else:
for key in self.seenPasts: # computed before at any other key. If so, we partialVotes[votingBlock].update({(votingBlock,blockX):True, (votingBlock,blockY):True})
if self.seenPasts[key]==thisPast: # can pull up the recursive vote and s=0
self.keyToRep.update({block:key}) # update the keyToRep to note the for fid in futureIDs:
thisRecVote = self.seenVotes[key] # alternative key. if fid in subdag.blocks:
break #print("partialvotes[fid]=",partialVotes[fid])
else: # We enter this case if we did not find any past that matches thisPast. if (blockX, blockY) in partialVotes[fid]:
self.keyToRep.update({block:block}) # In this case, keyToRep is identity s = s+1
thisRecVote = self.getVote(thisPast) # And we recursively compute the vote elif (blockY, blockX) in partialVotes[fid]:
self.seenVotes.update({self.keyToRep[block]:thisRecVote}) # then we store s = s-1
self.seenPasts.update({self.keyToRep[block]:thisPast}) # the vote and the past. if s > 0:
partialVotes[votingBlock].update({(blockX,blockY):True})
# STEP 2: Get block IDs that have a vote on pairs from the past of block elif s < 0:
futureIDs = self.getFutureIDs(block) partialVotes[votingBlock].update({(blockY,blockX):True})
for blockX in subdag.blocks:
# STEP 3: For every pair of blocks, either both in the pair are in the past (see thisRecVote) for blockY in subdag.blocks:
# or one of the pair is in the past (inducing a natural order) s = 0
# or both in the pair are not in the past (majority of votes from futureIDs) for votingBlock in subdag.blocks:
for key in thisRecVote: # Extend thisRecVote into partial by taking each key of (blockX, blockY) if (blockX, blockY) in partialVotes[votingBlock]:
newVote = (block, copy.deepcopy(key[0]), copy.deepcopy(key[1])) # and inserting (votingBlock, blockX, blockY). s=s+1
if newVote not in partial: elif (blockY, blockX) in partialVotes[votingBlock]:
partial.update({newVote:True}) s=s-1
for blockX in subdag.blocks: if s > 0:
for blockY in subdag.blocks: result.update({(blockX,blockY):True})
# Since we took thisRecVote into account, we can disregard the case: "both in past" elif s < 0:
if blockX in thisPast.blocks and blockY in thisPast.blocks: result.update({(blockY,blockX):True})
pass
# If blockX is in the past of block and blockY is not, then block votes blockX < block < blockY
elif blockX in thisPast.blocks and blockY not in thisPast.blocks:
partial.update({(block,blockX,block):True,(block,blockX,blockY):True,(block,block,blockY):True})
# If blockY is in the past of block and blockX is not, then block votes blockY < block < blockX
elif blockX not in thisPast.blocks and blockY in thisPast.blocks:
partial.update({(block,blockY,block):True,(block,blockY,blockX):True,(block,block,blockX):True})
# If blockX and blockY not in the past, then....
elif blockX not in thisPast.blocks and blockY not in thisPast.blocks:
# Could be the that blockX=blockY=block.
if blockX == blockY and blockX == block:
# partial.update({(block,block,block):True}) # Unnecessary, we already did this (line 150)
pass
# Could be that blockY=block but blockX != block and blockX not in the past of block
# In this case, block votes that block < blockX.
elif blockX != block and blockY == block:
partial.update({(block,block,blockX):True})
# Could be that blockX=block but blockY != block and blockY not in the past of block.
# 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 return result
def pruneLeaves(self, subdag):
newsubdag = BlockDAG()
newsubdag.startDAG(subdag.genBlock.id, subdag.genBlock)
q = deque()
for child in self.childRelation[subdag.genBlock.id]:
if child in subdag.blocks:
q.append(child)
while len(q) > 0:
nextBlock = q.popleft()
if nextBlock not in subdag.leaves:
newsubdag.addLeaf(blockIn=subdag.blocks[nextBlock])
if nextBlock in self.childRelation:
if len(self.childRelation[nextBlock])>1:
for child in self.childRelation[nextBlock]:
if child in subdag.blocks:
q.append(child)
return newsubdag
def addBlock(self, blockIn=None):
if blockIn == None:
parents = copy.deepcopy(self.dag.leaves)
blockID = str(len(self.dag)+1)
blockIn = Block(blockID, parents)
else:
blockID = blockIn.id
self.dag.addLeaf(blockIn)
for parent in blockIn.parents:
if parent not in self.childRelation:
self.childRelation.update({parent:[]})
self.childRelation[parent].append(blockID)
def getPastIDs(self, block):
thisPast = {}
q = deque()
self.updateChildren()
for parent in self.dag.blocks[block].parents:
q.append(parent)
while len(q) > 0:
nextPastBlockID = q.popleft()
if nextPastBlockID not in thisPast:
thisPast.update({nextPastBlockID:self.dag.blocks[nextPastBlockID]})
for parent in self.dag.blocks[nextPastBlockID].parents:
q.append(parent)
return thisPast
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
return thisFuture
def getPast(self, block):
pastIDs = self.getPastIDs(block)
subdag = BlockDAG()
subdag.startDAG(idIn = self.dag.genBlock.id, genBlockIn = self.dag.genBlock)
q = deque()
for child in self.childRelation[self.dag.genBlock.id]:
if child in pastIDs:
q.append(child)
while len(q) > 0:
nextBlock = q.popleft()
if nextBlock in pastIDs:
subdag.addLeaf(self.dag.blocks[nextBlock])
for child in self.childRelation[nextBlock]:
if child in pastIDs:
q.append(child)
return subdag
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)
shepard.addBlock(copy.deepcopy(newBlock))
vote = shepard.getVote()
oldVote = copy.deepcopy(vote)
self.assertTrue(("0","0") in vote)
self.assertTrue(("0","1") in vote)
self.assertTrue(("1","1") in vote)
genBlock = Block(idIn="0") b1 = Block()
brock = BlockDAG(genBlock) b1.setParents(parentList={"0":b0})
shepard = Spectre(brock) b1.setID("1")
block1 = Block(idIn="1", parentList={genBlock.id:genBlock}) shepard.addBlock(b1)
block2 = Block(idIn="2", parentList={genBlock.id:genBlock})
block3 = Block(idIn="3", parentList={"1":block1, "2":block2})
block4 = Block(idIn="4", parentList={"2":block2})
shepard.addBlock(copy.deepcopy(block1))
shepard.addBlock(copy.deepcopy(block2))
shepard.addBlock(copy.deepcopy(block3))
shepard.addBlock(copy.deepcopy(block4))
vote = shepard.getVote()
print(vote)
self.assertTrue(("0","2") in vote)
self.assertTrue(("2", "1") in vote)
self.assertTrue(("1", "3") in vote)
self.assertTrue(("3", "4") in vote)
self.assertFalse(("4", "2") in vote)
b2 = Block()
b2.setParents(parentList={"0":b0})
b2.setID("2")
shepard.addBlock(b2)
b3 = Block()
b3.setParents(parentList={"1":b1, "2":b2})
b3.setID("3")
shepard.addBlock(b3)
b4 = Block()
b4.setParents(parentList={"2":b2})
b4.setID("4")
shepard.addBlock(b4)
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" in shepard.dag.leaves and "4" in shepard.dag.leaves)
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)