From f57eab2e3e717f81a8e3063295e927319eddafa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20F=C3=BCrderer?= Date: Sun, 25 Feb 2024 16:29:05 +0100 Subject: [PATCH] Create a first draft of the blockchain class --- .gitignore | 1 + blockchain.py | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 .gitignore create mode 100644 blockchain.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/blockchain.py b/blockchain.py new file mode 100644 index 0000000..e5f289a --- /dev/null +++ b/blockchain.py @@ -0,0 +1,92 @@ +from dataclasses import dataclass +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey +from cryptography.exceptions import InvalidSignature +import hashlib +import time + +@dataclass +class Transaction: + id: int + sender: bytes + receiver: bytes + amount: int + transaction_fee: int + signature: bytes + + def is_valid(self): + sender_pubkey = Ed25519PublicKey.from_public_bytes(self.sender) + msg = self.id.to_bytes(4, "big") + \ + self.sender + \ + self.receiver + \ + self.amount.to_bytes(8, "big") + \ + self.transaction_fee(8, "big") + try: + sender_pubkey.verify(self.signature, msg) + except InvalidSignature: + return False + return self.amount >= 1 + def is_valid_after_block(self, block): + if (self.sender, self.id) in block.used_transaction_ids: + return False + balance = block.balances.get(self.sender) + if balance is None: + return False + return balance >= self.amount + self.transaction_fee + +@dataclass +class FullBlock: + nonce: int + timestamp: int + previous_hash: bytes + message: bytes + difficulty_sum: int + miner_pubkey: bytes + transaction: Transaction + balances: dict + # (sender_pubkey, id) tuples + used_transaction_ids: set + + def is_valid(self, blockchain): + if self.transaction is not None: + if not self.transaction.is_valid(): + return False + if self.previous_hash != 32 * b"\0": + prev_block = blockchain.get_block(self.previous_hash) + if type(prev_block) is not FullBlock: + return False + if self.timestamp <= prev_block.timestamp: + return False + if self.timestamp > time.time(): + return False + if not self.transaction.is_valid_after_block(prev_block): + return False + elif self.transaction is not None: + return False + B_1_difficulty_sum, B_1_timestamp = self.get_difficulty_info(1, blockchain) + B_10_difficulty_sum, B_10_timestamp = self.get_difficulty_info(10, blockchain) + D = B_1_difficulty_sum - B_10_difficulty_sum + T = self.timestamp - B_10_timestamp + calculated_difficulty = D * 3000 // 9 // T + block_difficulty = max(calculated_difficulty, 2**28) + if B_1_difficulty_sum + block_difficulty != self.difficulty_sum: + return False + block_raw = self.get_block_raw() + block_hash = hashlib.sha256(block_raw).digest() + return int.from_bytes(block_hash, "big") * block_difficulty < 2**256 + def get_difficulty_info(self, steps, blockchain): + if steps == 0: + return self.difficulty_sum, self.timestamp + if self.previous_hash == 32 * b"\0": + difficulty_sum = 2**29 - steps * 2**28 + timestamp = self.timestamp - steps * 300 + return difficulty_sum, timestamp + else: + previous_block = blockchain.get_block(self.previous_hash) + return previous_block.get_difficulty_info(steps-1, blockchain) + +class Blockchain: + def __init__(self): + # maps block hashes to block instances + self.__block_map = {} + def get_block(self, hash): + return self.__block_map.get(hash)