diff --git a/source-code/Spectre/Block.py b/source-code/Spectre/Block.py index 48f01d5..399fad7 100644 --- a/source-code/Spectre/Block.py +++ b/source-code/Spectre/Block.py @@ -1,77 +1,85 @@ import unittest import math -import numpy as np import copy from collections import deque import time +import hashlib 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): - self.id = "" # string - self.timestamp = None # format tbd - self.data = None # payload - self.parents = {} # block ID : pointer to block - self.children = {} # block ID : pointer to block - def addChild(self, childIn): - if childIn not in self.children: - self.children.update({childIn.id:childIn}) - def addChildren(self, childrenIn): - for child in childrenIn: - self.addChild(childrenIn[child]) - def addParent(self, parentIn): - if parentIn not in self.parents: - self.parents.update({parentIn.id:parentIn}) - def addParents(self, parentsIn): - for parent in parentsIn: - self.addParent(parentsIn[parent]) - - + # Initialize with empty payload, no identity, and empty parents. + self.data = None + self.ident = hash(str(0)) + self.parents = None + self.addParents({}) + + def addParents(self, parentsIn): # dict of parents + if self.parents is None: + self.parents = parentsIn + else: + self.parents.update(parentsIn) + self._recomputeIdent() + + def _recomputeIdent(self): + m = str(0) + str(self.data) + str(self.parents) + self.ident = hash(m) + + class Test_Block(unittest.TestCase): def test_Block(self): + # b0 -> b1 -> {both b2, b3} -> b4... oh, and say b3 -> b5 also b0 = Block() - b0.id = "0" - self.assertTrue(b0.data is None) - self.assertTrue(len(b0.parents)==0) + b0.data = {"timestamp" : time.time()} + time.sleep(1) b1 = Block() - b1.parents.update({"0":b0}) - b1.id = "1" - for parentID in b1.parents: - 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) + b1.data = {"timestamp" : time.time(), "txns" : [1,2,3]} + b1.addParents({b0.ident:b0}) # updateIdent called with addParent. + time.sleep(1) b2 = Block() - b2.parents.update({"0":b0}) - b2.id = "2" - for parentID in b2.parents: - 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) - + b2.data = {"timestamp" : time.time(), "txns" : None} + b2.addParents({b1.ident:b1}) + time.sleep(1) + b3 = Block() - b3.parents.update({"1":b1, "2":b2}) - b3.id = "3" - for parentID in b3.parents: - 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) - + b3.data = {"timestamp" : time.time(), "txns" : None} + b3.addParents({b1.ident:b1}) + time.sleep(1) + b4 = Block() - b4.parents.update({"2":b2}) - b4.id = "4" - for parentID in b4.parents: - 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) -unittest.TextTestRunner(verbosity=1).run(suite) - + b4.data = {"timestamp" : time.time()} # see how sloppy we can be wheeee + b4.addParents({b2.ident:b2, b3.ident:b3}) + time.sleep(1) + + b5 = Block() + 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) diff --git a/source-code/Spectre/RoBlocks.py b/source-code/Spectre/RoBlocks.py new file mode 100644 index 0000000..dc42aa7 --- /dev/null +++ b/source-code/Spectre/RoBlocks.py @@ -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)