forked from WikiDeck/wikideck
119 lines
4.3 KiB
Python
119 lines
4.3 KiB
Python
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
|