# Section 3.2 from "Streamlet: Textbook Streamlined Blockchains"

class Block:
	''' This class represents a tuple of the form (h, e, txs).
		Each blocks parent hash h may be computed simply as a hash of the parent block. '''
		
	def __init__(self, h, e, txs):
		self.h = h # parent hash
		self.e = e # epoch number
		self.txs = txs # transactions payload
	
	def __repr__(self):
		return "Block=[h={0}, e={1}, txs={2}]".format(self.h, self.e, self.txs)
	
	def __hash__(self):
		return hash((self.h, self.e, self.txs)) # Python hash is used for demostranation porpuses only.

class Blockchain:
	''' This class represents a sequence of blocks starting with the genesis block. '''
	
	def __init__(self, genesis_block):
		self.chain = [genesis_block]
	
	def __repr__(self):
		return "Blockchain=[chain={0}]".format(self.chain)
	
	def check_block_validity(self, block, previous_block):
		''' A block is considered valid when its parent hash is equal to the hash of the 
			previous block and their epochs are incremental, exluding genesis. '''
		
		assert(block.h != '⊥') # genesis block check
		assert(block.h == hash(previous_block))
		assert(block.e > previous_block.e)

	def check_chain_validity(self):
		''' A blockchain is considered valid, when every block is valid, based on check_block_validity method. '''
		
		for index, block in enumerate(self.chain[1:]):
			self.check_block_validity(block, self.chain[index])
	
	def add_block(self, block):		
		''' Insertion of a valid block. '''	
		self.check_block_validity(block, self.chain[-1])
		self.chain.append(block)

# We generate a genesis block and a blockchain.
genesis_block = Block("⊥", 0, '⊥')
chain = Blockchain(genesis_block)

# A new block is generated and appended to the blockchain, since its valid.
block1 = Block(hash(genesis_block), 1, "tx1, tx2, tx3")
chain.add_block(block1)

# A new block is generated and appended to the blockchain, since its valid.
block2 = Block(hash(block1), 2, "tx4, tx5, tx6")
chain.add_block(block2)

# We check entire blockchain validity.
chain.check_chain_validity()

# Following code examples will fail, due to block validity checks:
# wrong_block = Block(hash(block1), 3, "tx4,tx5,tx6") # Previous block not last.
# chain.add_block(wrong_block)

# wrong_block = Block(hash(block2), 1, "tx4,tx5,tx6") # Epoch not incremental.
# chain.add_block(wrong_block)
