wikideck/wikideck/Mine/Block.py
Eric Meehan 741b1f5b2a Attempting server startup.
* Created basic client

* Many bug fixes
2025-05-31 18:14:33 -04:00

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