|
@@ -0,0 +1,114 @@
|
|
|
|
|
+# Section 2 from "Streamlet: Textbook Streamlined Blockchains"
|
|
|
|
|
+
|
|
|
|
|
+class Node:
|
|
|
|
|
+ ''' This class represents a simplyfied protocol node.
|
|
|
|
|
+ Each node is numbered and has a secret-public keys pair, to sign messages.
|
|
|
|
|
+ Modes receive inputs (transactions) and maintain an ordered log (blockchain),
|
|
|
|
|
+ containing a sequense of strings (blocks). '''
|
|
|
|
|
+ def __init__(self, id, secret_key, public_key):
|
|
|
|
|
+ self.id = id
|
|
|
|
|
+ self.secret_key = secret_key
|
|
|
|
|
+ self.public_key = public_key
|
|
|
|
|
+ self.blockchain = Blockchain()
|
|
|
|
|
+ self.inputs = []
|
|
|
|
|
+
|
|
|
|
|
+ def __repr__(self):
|
|
|
|
|
+ return "Node=[id={0}, secret_key={1}, public_key={2}]".format(self.id, self.secret_key, self.public_key)
|
|
|
|
|
+
|
|
|
|
|
+ def receive_input(self, input):
|
|
|
|
|
+ # Additional validity rules must be defined by the protocol for its blockchain data structure.
|
|
|
|
|
+ self.inputs.append(input)
|
|
|
|
|
+
|
|
|
|
|
+ def output(self):
|
|
|
|
|
+ return self.blockchain
|
|
|
|
|
+
|
|
|
|
|
+ def broadcast(self, nodes, input):
|
|
|
|
|
+ for node in nodes:
|
|
|
|
|
+ node.receive_input(input)
|
|
|
|
|
+
|
|
|
|
|
+ def finalize_block(self):
|
|
|
|
|
+ block = Block(self.inputs)
|
|
|
|
|
+ self.blockchain.add_block(block) # Block is appended to nodes blockchain
|
|
|
|
|
+ self.inputs = []
|
|
|
|
|
+
|
|
|
|
|
+class Block:
|
|
|
|
|
+ ''' This class represents a simplyfied block structure. '''
|
|
|
|
|
+ def __init__(self, transactions):
|
|
|
|
|
+ self.transactions = transactions
|
|
|
|
|
+
|
|
|
|
|
+ def __repr__(self):
|
|
|
|
|
+ return "Block=[transactions={0}]".format(self.transactions)
|
|
|
|
|
+
|
|
|
|
|
+ def __eq__(self, other):
|
|
|
|
|
+ return self.transactions == other.transactions
|
|
|
|
|
+
|
|
|
|
|
+class Blockchain:
|
|
|
|
|
+ ''' This class represents a simplyfied blockchain structure. '''
|
|
|
|
|
+ def __init__(self):
|
|
|
|
|
+ self.blocks = []
|
|
|
|
|
+
|
|
|
|
|
+ def __repr__(self):
|
|
|
|
|
+ return "Blockchain=[blocks={0}]".format(self.blocks)
|
|
|
|
|
+
|
|
|
|
|
+ def __eq__(self, other):
|
|
|
|
|
+ return self.blocks == other.blocks
|
|
|
|
|
+
|
|
|
|
|
+ def __len__(self):
|
|
|
|
|
+ return len(self.blocks)
|
|
|
|
|
+
|
|
|
|
|
+ def __getitem__(self, index):
|
|
|
|
|
+ return self.blocks[index]
|
|
|
|
|
+
|
|
|
|
|
+ def add_block(self, block):
|
|
|
|
|
+ self.blocks.append(block)
|
|
|
|
|
+
|
|
|
|
|
+# There are in total n nodes numbered.
|
|
|
|
|
+node0 = Node(0, "dummy_secret_key0", "dummy_public_key0")
|
|
|
|
|
+node1 = Node(1, "dummy_secret_key1", "dummy_public_key1")
|
|
|
|
|
+
|
|
|
|
|
+# Advesary chooses last node to corrupt(static corruption).
|
|
|
|
|
+corruptedNode = Node(2, "dummy_secret_key2", "dummy_public_key2")
|
|
|
|
|
+
|
|
|
|
|
+# We simulate some rounds to test consistency.
|
|
|
|
|
+
|
|
|
|
|
+# Round 0 synchronization period
|
|
|
|
|
+# node0 receives input and broadcasts it to rest nodes.
|
|
|
|
|
+node0.receive_input("tx0")
|
|
|
|
|
+node0.broadcast([node1, corruptedNode], "tx0")
|
|
|
|
|
+
|
|
|
|
|
+# node1 receives input and broadcasts it to rest nodes.
|
|
|
|
|
+node1.receive_input("tx1")
|
|
|
|
|
+node1.broadcast([node0, corruptedNode], "tx1")
|
|
|
|
|
+
|
|
|
|
|
+# corruptedNode receives input but doesn't broadcast to rest nodes.
|
|
|
|
|
+corruptedNode.receive_input("tx2")
|
|
|
|
|
+
|
|
|
|
|
+# We assume nodes finalize blocks(append to blockchain) at the end of each round
|
|
|
|
|
+node0.finalize_block()
|
|
|
|
|
+node1.finalize_block()
|
|
|
|
|
+corruptedNode.finalize_block()
|
|
|
|
|
+
|
|
|
|
|
+# In round 1, a new node joins
|
|
|
|
|
+node3 = Node(3, "dummy_secret_key3", "dummy_public_key3")
|
|
|
|
|
+
|
|
|
|
|
+# node3 receives input and broadcasts it to rest nodes.
|
|
|
|
|
+node3.receive_input("tx3")
|
|
|
|
|
+node3.broadcast([node0, node1, corruptedNode], "tx3")
|
|
|
|
|
+
|
|
|
|
|
+# Nodes finalize blocks
|
|
|
|
|
+node0.finalize_block()
|
|
|
|
|
+node1.finalize_block()
|
|
|
|
|
+corruptedNode.finalize_block()
|
|
|
|
|
+node3.finalize_block()
|
|
|
|
|
+
|
|
|
|
|
+# Consistency testing
|
|
|
|
|
+# node0 and node1 remained honest, therefore their outputs must be the same
|
|
|
|
|
+assert(node0.output() == node1.output())
|
|
|
|
|
+
|
|
|
|
|
+# Since node3 joined later, node0 and node1 outputs are a prefix or equal to his output.
|
|
|
|
|
+# Based on that, node3 output is a suffix of node0 and node1 outputs.
|
|
|
|
|
+assert(node0.output()[-len(node3.output()):] == node3.output().blocks)
|
|
|
|
|
+assert(node1.output()[-len(node3.output()):] == node3.output().blocks)
|
|
|
|
|
+
|
|
|
|
|
+# Below assertion will fail, as corrupt node deviated from the protocol.
|
|
|
|
|
+# assert(node0.output() == corruptedNode.output())
|