aggstam 4 anni fa
parent
commit
7f1a2f4b14

+ 7 - 7
script/research/streamlet/2-execution-model-and-definitions.py

@@ -71,7 +71,7 @@ corruptedNode = Node(2, "dummy_secret_key2", "dummy_public_key2")
 
 
 # We simulate some rounds to test consistency.
 # We simulate some rounds to test consistency.
 
 
-# Round 0 synchronization period
+# Round 0 synchronization period.
 # node0 receives input and broadcasts it to rest nodes.
 # node0 receives input and broadcasts it to rest nodes.
 node0.receive_input("tx0")
 node0.receive_input("tx0")
 node0.broadcast([node1, corruptedNode], "tx0")
 node0.broadcast([node1, corruptedNode], "tx0")
@@ -83,29 +83,29 @@ node1.broadcast([node0, corruptedNode], "tx1")
 # corruptedNode receives input but doesn't broadcast to rest nodes.
 # corruptedNode receives input but doesn't broadcast to rest nodes.
 corruptedNode.receive_input("tx2")
 corruptedNode.receive_input("tx2")
 
 
-# We assume nodes finalize blocks(append to blockchain) at the end of each round
+# We assume nodes finalize blocks(append to blockchain) at the end of each round.
 node0.finalize_block()
 node0.finalize_block()
 node1.finalize_block()
 node1.finalize_block()
 corruptedNode.finalize_block()
 corruptedNode.finalize_block()
 
 
-# In round 1, a new node joins
+# In round 1, a new node joins.
 node3 = Node(3, "dummy_secret_key3", "dummy_public_key3")
 node3 = Node(3, "dummy_secret_key3", "dummy_public_key3")
 
 
 # node3 receives input and broadcasts it to rest nodes.
 # node3 receives input and broadcasts it to rest nodes.
 node3.receive_input("tx3")
 node3.receive_input("tx3")
 node3.broadcast([node0, node1, corruptedNode], "tx3")
 node3.broadcast([node0, node1, corruptedNode], "tx3")
 
 
-# Nodes finalize blocks
+# Nodes finalize blocks.
 node0.finalize_block()
 node0.finalize_block()
 node1.finalize_block()
 node1.finalize_block()
 corruptedNode.finalize_block()
 corruptedNode.finalize_block()
 node3.finalize_block()
 node3.finalize_block()
 
 
-# Consistency testing
-# node0 and node1 remained honest, therefore their outputs must be the same
+# Consistency testing.
+# node0 and node1 remained honest, therefore their outputs must be the same.
 assert(node0.output() == node1.output())
 assert(node0.output() == node1.output())
 
 
-# Since node3 joined later, node0 and node1 outputs are a prefix or equal to his output.
+# Since node3 joined later, node0 and node1 outputs are a prefix or equal to node3 output.
 # Based on that, node3 output is a suffix of node0 and node1 outputs.
 # Based on that, node3 output is a suffix of node0 and node1 outputs.
 assert(node0.output()[-len(node3.output()):] == node3.output().blocks)
 assert(node0.output()[-len(node3.output()):] == node3.output().blocks)
 assert(node1.output()[-len(node3.output()):] == node3.output().blocks)
 assert(node1.output()[-len(node3.output()):] == node3.output().blocks)

+ 3 - 3
script/research/streamlet/3.3-votes-and-notarization.py

@@ -24,7 +24,7 @@ def generate_keys(private_key_password):
 	
 	
 	return encrypted_pem_private_key, pem_public_key
 	return encrypted_pem_private_key, pem_public_key
 
 
-# Signs a message using private_key
+# Signs a message using private_key.
 def sign_message(password, private_key, message):
 def sign_message(password, private_key, message):
 	privkey = serialization.load_pem_private_key(private_key, password=password.encode(), backend=default_backend())
 	privkey = serialization.load_pem_private_key(private_key, password=password.encode(), backend=default_backend())
 	signed_message = privkey.sign(
 	signed_message = privkey.sign(
@@ -36,7 +36,7 @@ def sign_message(password, private_key, message):
 	)
 	)
 	return signed_message
 	return signed_message
 	
 	
-# Verifies a message against a public key
+# Verifies a message against a public key.
 def verify_signature(public_key, message, signed_message):
 def verify_signature(public_key, message, signed_message):
 	pubkey = serialization.load_pem_public_key(public_key, backend=default_backend())
 	pubkey = serialization.load_pem_public_key(public_key, backend=default_backend())
 	try:
 	try:
@@ -59,4 +59,4 @@ signed_message = sign_message(node_password, node_private_key, message)
 
 
 # When nodes receive votes, they verify them against nodes public key.
 # When nodes receive votes, they verify them against nodes public key.
 assert(verify_signature(node_public_key, message, signed_message))
 assert(verify_signature(node_public_key, message, signed_message))
-# If votes for that specific block are >=2n/3, node marks block as notarized
+# If votes for that specific block are >=2n/3, node marks block as notarized.

+ 78 - 0
script/research/streamlet/3.4-protocol.py

@@ -0,0 +1,78 @@
+# Section 3.4 from "Streamlet: Textbook Streamlined Blockchains"
+
+from block import Block
+from node import Node
+
+# Genesis block is generated.
+genesis_block = Block("⊥", 0, '⊥')
+
+# We create some nodes to participate in the Protocol.
+# There are in total n nodes numbered.
+node0 = Node(0, "clock", "node_password0", genesis_block)
+node1 = Node(1, "clock", "node_password1", genesis_block)
+node2 = Node(2, "clock", "node_password2", genesis_block)
+node3 = Node(3, "clock", "node_password3", genesis_block)
+node4 = Node(4, "clock", "node_password4", genesis_block)
+node5 = Node(5, "clock", "node_password5", genesis_block)
+
+nodes = [node0, node1, node2, node3, node4, node5]
+
+# We simulate some rounds to test consistency.
+epoch = 1
+
+# Nodes receive transactions and broacasts them between them.
+# node0 receives input and broadcasts it to rest nodes.
+node0.receive_transaction("tx0")
+node0.broadcast_transaction([node1, node2, node3, node4, node5], "tx0")
+# node1 receives input and broadcasts it to rest nodes.
+node1.receive_transaction("tx2")
+node1.broadcast_transaction([node0, node2, node3, node4, node5], "tx2")
+# node4 receives input and broadcasts it to rest nodes.
+node4.receive_transaction("tx3")
+node4.broadcast_transaction([node0, node1, node2, node3, node5], "tx3")
+
+# A random leader is selected.
+leader = nodes[hash(str(epoch))%len(nodes)]
+
+# Leader forms a block and broadcasts it.
+leader.propose_block(epoch, nodes)
+
+# Nodes vote on the block and broadcast their vote to rest nodes.
+for node in nodes:
+	node.vote_on_round_block(nodes)
+
+# We verify that all nodes have the same blockchain on round end.
+assert(node0.output() == node1.output() == node2.output() == node3.output() == node4.output() == node5.output())
+
+epoch = 2
+
+# We introduce a new node. Assumption: no history sync, a Node starts participating in next epoch.
+node6 = Node(6, "clock", "node_password5", node0.output()[-1])
+nodes.append(node6)
+
+# node3 receives input and broadcasts it to rest nodes.
+node3.receive_transaction("tx4")
+node3.broadcast_transaction([node0, node1, node2, node4, node5, node6], "tx4")
+# node5 receives input and broadcasts it to rest nodes.
+node5.receive_transaction("tx5")
+node5.broadcast_transaction([node0, node1, node2, node3, node4, node6], "tx5")
+# node6 receives input and broadcasts it to rest nodes.
+node6.receive_transaction("tx6")
+node6.broadcast_transaction([node0, node1, node2, node3, node4, node5], "tx6")
+
+# A random leader is selected.
+leader = nodes[hash(str(epoch))%len(nodes)]
+
+# Leader forms a block and broadcasts it.
+leader.propose_block(epoch, nodes)
+
+# Nodes vote on the block and broadcast their vote to rest nodes.
+for node in nodes:
+	node.vote_on_round_block(nodes)
+
+# We verify that all nodes have the same blockchain on round end.
+assert(node0.output() == node1.output() == node2.output() == node3.output() == node4.output() == node5.output())
+
+# Since node6 joined later, node0 output is a prefix or equal to node6 output.
+# Based on that, node6 output is a suffix of node0 output.
+assert(node0.output().blocks[-len(node6.output()):] == node6.output().blocks)

+ 8 - 2
script/research/streamlet/block.py

@@ -5,12 +5,18 @@ class Block:
 		self.h = h # parent hash
 		self.h = h # parent hash
 		self.e = e # epoch number
 		self.e = e # epoch number
 		self.txs = txs # transactions payload
 		self.txs = txs # transactions payload
+		self.votes = [] # Epoch votes
+		self.notarized = False # block notarization flag
+		self.finalized = False # block finalization flag
 	
 	
 	def __repr__(self):
 	def __repr__(self):
-		return "Block=[h={0}, e={1}, txs={2}]".format(self.h, self.e, self.txs)
+		return "Block=[h={0}, e={1}, txs={2}, notarized={3}, finalized={4}]".format(self.h, self.e, self.txs, self.notarized, self.finalized)
 	
 	
 	def __hash__(self):
 	def __hash__(self):
-		return hash((self.h, self.e, self.txs)) # python hash is used for demostranation porpuses only.
+		return hash((self.h, self.e, str(self.txs))) # python hash is used for demostranation porpuses only.
 		
 		
 	def __eq__(self, other):
 	def __eq__(self, other):
 		return self.h == other.h and self.e == other.e and self.txs == other.txs
 		return self.h == other.h and self.e == other.e and self.txs == other.txs
+		
+	def encode(self):
+		return(("{0},{1},{2}".format(self.h, self.e, self.txs)).encode())

+ 43 - 10
script/research/streamlet/node.py

@@ -1,6 +1,7 @@
-import utils
+import copy, utils
 from block import Block
 from block import Block
 from blockchain import Blockchain
 from blockchain import Blockchain
+from vote import Vote
 
 
 class Node:
 class Node:
 	''' This class represents a protocol node.
 	''' This class represents a protocol node.
@@ -18,19 +19,51 @@ class Node:
 	
 	
 	def __repr__(self):
 	def __repr__(self):
 		return "Node=[id={0}, clock={1}, password={2}, private_key={3}, public_key={4}, blockchain={5}, unconfirmed_transactions={6}".format(self.id, self.clock, self.password, self.private_key, self.public_key, self.blockchain, self.unconfirmed_transactions)
 		return "Node=[id={0}, clock={1}, password={2}, private_key={3}, public_key={4}, blockchain={5}, unconfirmed_transactions={6}".format(self.id, self.clock, self.password, self.private_key, self.public_key, self.blockchain, self.unconfirmed_transactions)
-		
-	def receive_transaction(self, transaction):
-		# Additional validity rules must be defined by the protocol for its blockchain data structure.
-		self.unconfirmed_transactions.append(transaction)
 	
 	
 	def output(self):
 	def output(self):
 		return self.blockchain
 		return self.blockchain
 	
 	
-	def broadcast(self, nodes, transaction):
+	def receive_transaction(self, transaction):
+		# Additional validity rules must be defined by the protocol for its blockchain data structure.
+		self.unconfirmed_transactions.append(transaction)
+	
+	def broadcast_transaction(self, nodes, transaction):
 		for node in nodes:
 		for node in nodes:
 			node.receive_transaction(transaction)
 			node.receive_transaction(transaction)
 			
 			
-	def finalize_block(self, epoch):
-		block = Block(hash(self.blockchain.blocks[-1]), epoch, str(self.unconfirmed_transactions))
-		self.blockchain.add_block(block) # Block is appended to nodes blockchain
-		self.unconfirmed_transactions = []
+	def propose_block(self, epoch, nodes):
+		propozed_block = Block(hash(self.blockchain.blocks[-1]), epoch, self.unconfirmed_transactions)
+		for node in nodes:
+			node.receive_proposed_block(copy.deepcopy(propozed_block))
+	
+	def receive_proposed_block(self, round_block):
+		self.round_block = round_block
+		
+	def vote_on_round_block(self, nodes):
+		# Node verifies proposed block extends from one of the longest notarized chains that node has seen at the time.
+		# Already notarized check.
+		if (self.round_block != self.blockchain.blocks[-1]):
+			self.blockchain.check_block_validity(self.round_block, self.blockchain.blocks[-1])
+		signed_block = utils.sign_message(self.password, self.private_key, self.round_block)
+		vote = Vote(signed_block, self.round_block, self.id)
+		for node in nodes:
+			node.receive_vote(self.public_key, vote, nodes)
+
+	def receive_vote(self, node_public_key, vote, nodes):
+		# We verify we haven't received a vote from that node again.
+		assert(vote not in self.round_block.votes)
+		# When nodes receive votes, they verify them against nodes public key.
+		assert(utils.verify_signature(node_public_key, vote.block, vote.vote))
+		assert(self.round_block == vote.block)
+		# Additional rules must be defined by the protocol for its voting system.
+		self.round_block.votes.append(vote)
+		# When a node sees 2n/3 votes for a block it notarizes it
+		if (self.round_block != self.blockchain.blocks[-1] and len(self.round_block.votes) > (2 * len(nodes) / 3)):
+			notarized_block = copy.deepcopy(self.round_block)
+			notarized_block.notarized = True
+			self.blockchain.add_block(notarized_block)
+			# Node removes block transactions from unconfirmed_transactions array
+			for transaction in notarized_block.txs:
+				self.unconfirmed_transactions.remove(transaction)
+			
+