import datetime import hashlib import json import uuid from Mine.Card import Card from Mine.Transaction import Transaction class Block(): def __init__(self, blockId=uuid.uuid4(), previousHash="0", timestamp=datetime.datetime.now(datetime.timezone.utc), height=0, nonce=0, difficulty=0, card=Card(), transactions=[], data=None): if data: self.load_from_data(data) else: self.blockId = blockId self.previousHash = previousHash self.timestamp = timestamp self.height = height self.difficulty = difficulty self.nonce = nonce self.card = card self.transactions = transactions self.update() def update(self): self.blockHash = hashlib.sha256(str(self).encode('utf-8')) def mine(self, authorPrivateKey): self.transactions.append( Transaction( cardId = self.card.cardId, sender = authorPrivateKey.public_key(), receiver = authorPrivateKey.public_key(), authorPrivateKey = authorPrivateKey ) ) while int(self.blockHash.hexdigest(), 16) > 2**(256-self.difficulty): self.nonce += 1 self.update() ### # Validate the internal block structure. # This method confirms that the correct data types have been used and that # values are basically valid. # Further validations against the existing chain must be done elsewhere. # This method calls the Card.validate() and Transaction.validate() methods to validate the # respective schemas. ### def validate(self): # TODO: validate blockId is uuid # TODO: validate previousHash is sha256 hash if not int(self.blockHash.hexdigest(), 16) <= 2**(256-self.difficulty): raise self.Invalid("Hash does not meet difficulty requirement.") # TODO: validate timestamp is UTC timestamp if not self.timestamp < datetime.datetime.now(datetime.timezone.utc): raise self.Invalid("Timestamp in the future.") if not self.height >= 0: raise self.Invalid("Height less than 0.") if not self.difficulty >= 0: raise self.Invalid("Difficulty less than 0.") if not self.nonce >= 0: raise self.Invalid("Nonce less than 0.") self.card.validate() for transaction in self.transactions: transaction.validate() # TODO validate that one transaction gives the card to the author def load_from_data(self, data): self.blockId = uuid.UUID(data['blockId']) self.previousHash = data['previousHash'] self.timestamp = datetime.datetime.strptime( data['timestamp'], "%Y-%m-%d %H:%M:%S.%f%z" ) self.height = data['height'] self.difficulty = data['difficulty'] self.nonce = data['nonce'] self.card = Card( cardId = uuid.UUID(data['card']['cardId']), pageId = data['card']['pageId'] ) self.transactions = [ Transaction( transactionId = uuid.UUID(each['transactionId']), timestamp = datetime.datetime.strptime( each['timestamp'], "%Y-%m-%d %H:%M:%S.%f" ), cardId = uuid.UUID(each['cardId']), sender = each['sender'], receiver = each['receiver'], signature = each['signature'] ) for each in data['transactions'] ] self.update() def as_dict(self): return { "blockId": str(self.blockId), "previousHash": self.previousHash, "timestamp": str(self.timestamp), "height": self.height, "difficulty": self.difficulty, "nonce": self.nonce, "card": self.card.as_dict(), "transactions": [each.as_dict() for each in self.transactions] } def __str__(self): # The hash of the block is the SHA256 hash of what this method returns. return json.dumps(self.as_dict()) class Invalid(Exception): def __init__(self, message, status_code=406): super().__init__(message) self.status_code = status_code