From 7eed7b7d26aab0716f96ca889e205687c5ac48a5 Mon Sep 17 00:00:00 2001 From: siddhant Date: Fri, 20 Mar 2026 20:02:19 +0530 Subject: [PATCH] refactor: reduce duplicate block/tx reconstruction and block validation paths --- main.py | 19 ++------------ minichain/block.py | 19 ++++++++++++++ minichain/chain.py | 33 +++++++++++++++---------- minichain/persistence.py | 53 +++++----------------------------------- minichain/transaction.py | 12 +++++++++ 5 files changed, 59 insertions(+), 77 deletions(-) diff --git a/main.py b/main.py index 0b21881..dd51fd5 100644 --- a/main.py +++ b/main.py @@ -137,24 +137,12 @@ async def handler(data): logger.info("🔄 Accepted state sync from %s — %d accounts", peer_addr, len(chain.state.accounts)) elif msg_type == "tx": - tx = Transaction(**payload) + tx = Transaction.from_dict(payload) if mempool.add_transaction(tx): logger.info("📥 Received tx from %s... (amount=%s)", tx.sender[:8], tx.amount) elif msg_type == "block": - txs_raw = payload.get("transactions", []) - block_hash = payload.get("hash") - transactions = [Transaction(**t) for t in txs_raw] - - block = Block( - index=payload["index"], - previous_hash=payload["previous_hash"], - transactions=transactions, - timestamp=payload.get("timestamp"), - difficulty=payload.get("difficulty"), - ) - block.nonce = payload.get("nonce", 0) - block.hash = block_hash + block = Block.from_dict(payload) if chain.add_block(block): logger.info("📥 Received Block #%d — added to chain", block.index) @@ -355,9 +343,6 @@ async def on_peer_connected(writer): chain.state.credit_mining_reward(pk, reward=fund) logger.info("💰 Funded %s... with %d coins", pk[:12], fund) - # Nonce counter kept as a mutable list so the CLI closure can mutate it - nonce_counter = [0] - try: await cli_loop(sk, pk, chain, mempool, network) finally: diff --git a/minichain/block.py b/minichain/block.py index c4b5623..9854cf4 100644 --- a/minichain/block.py +++ b/minichain/block.py @@ -98,3 +98,22 @@ def to_dict(self): # ------------------------- def compute_hash(self) -> str: return canonical_json_hash(self.to_header_dict()) + + @classmethod + def from_dict(cls, payload: dict): + transactions = [ + Transaction.from_dict(tx_payload) + for tx_payload in payload.get("transactions", []) + ] + block = cls( + index=payload["index"], + previous_hash=payload["previous_hash"], + transactions=transactions, + timestamp=payload.get("timestamp"), + difficulty=payload.get("difficulty"), + ) + block.nonce = payload.get("nonce", 0) + block.hash = payload.get("hash") + if "merkle_root" in payload: + block.merkle_root = payload["merkle_root"] + return block diff --git a/minichain/chain.py b/minichain/chain.py index 78ac73f..b65d575 100644 --- a/minichain/chain.py +++ b/minichain/chain.py @@ -7,6 +7,22 @@ logger = logging.getLogger(__name__) +def validate_block_link_and_hash(previous_block, block): + if block.previous_hash != previous_block.hash: + raise ValueError( + f"invalid previous hash {block.previous_hash} != {previous_block.hash}" + ) + + if block.index != previous_block.index + 1: + raise ValueError( + f"invalid index {block.index} != {previous_block.index + 1}" + ) + + expected_hash = calculate_hash(block.to_header_dict()) + if block.hash != expected_hash: + raise ValueError(f"invalid hash {block.hash}") + + class Blockchain: """ Manages the blockchain, validates blocks, and commits state transitions. @@ -45,19 +61,10 @@ def add_block(self, block): """ with self._lock: - # Check previous hash linkage - if block.previous_hash != self.last_block.hash: - logger.warning("Block %s rejected: Invalid previous hash %s != %s", block.index, block.previous_hash, self.last_block.hash) - return False - - # Check index linkage - if block.index != self.last_block.index + 1: - logger.warning("Block %s rejected: Invalid index %s != %s", block.index, block.index, self.last_block.index + 1) - return False - - # Verify block hash - if block.hash != calculate_hash(block.to_header_dict()): - logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash) + try: + validate_block_link_and_hash(self.last_block, block) + except ValueError as exc: + logger.warning("Block %s rejected: %s", block.index, exc) return False # Validate transactions on a temporary state copy diff --git a/minichain/persistence.py b/minichain/persistence.py index 367a41d..b49f307 100644 --- a/minichain/persistence.py +++ b/minichain/persistence.py @@ -22,10 +22,7 @@ import copy from .block import Block -from .transaction import Transaction -from .chain import Blockchain -from .state import State -from .pow import calculate_hash +from .chain import Blockchain, validate_block_link_and_hash logger = logging.getLogger(__name__) @@ -128,23 +125,10 @@ def _verify_chain_integrity(blocks: list) -> None: for i in range(1, len(blocks)): block = blocks[i] prev = blocks[i - 1] - - if block.index != prev.index + 1: - raise ValueError( - f"Block #{block.index}: index gap (expected {prev.index + 1})" - ) - - if block.previous_hash != prev.hash: - raise ValueError( - f"Block #{block.index}: previous_hash mismatch" - ) - - expected_hash = calculate_hash(block.to_header_dict()) - if block.hash != expected_hash: - raise ValueError( - f"Block #{block.index}: hash mismatch " - f"(stored={block.hash[:16]}..., computed={expected_hash[:16]}...)" - ) + try: + validate_block_link_and_hash(prev, block) + except ValueError as exc: + raise ValueError(f"Block #{block.index}: {exc}") from exc # --------------------------------------------------------------------------- @@ -190,29 +174,4 @@ def _read_json(filepath: str): def _deserialize_block(data: dict) -> Block: """Reconstruct a Block (including its transactions) from a plain dict.""" - transactions = [ - Transaction( - sender=tx["sender"], - receiver=tx["receiver"], - amount=tx["amount"], - nonce=tx["nonce"], - data=tx.get("data"), - signature=tx.get("signature"), - timestamp=tx["timestamp"], - ) - for tx in data.get("transactions", []) - ] - - block = Block( - index=data["index"], - previous_hash=data["previous_hash"], - transactions=transactions, - timestamp=data["timestamp"], - difficulty=data.get("difficulty"), - ) - block.nonce = data["nonce"] - block.hash = data["hash"] - # Only overwrite merkle_root if explicitly saved; otherwise keep computed value - if "merkle_root" in data: - block.merkle_root = data["merkle_root"] - return block + return Block.from_dict(data) diff --git a/minichain/transaction.py b/minichain/transaction.py index c17137e..27f41f3 100644 --- a/minichain/transaction.py +++ b/minichain/transaction.py @@ -41,6 +41,18 @@ def to_signing_dict(self): "timestamp": self.timestamp, } + @classmethod + def from_dict(cls, payload: dict): + return cls( + sender=payload["sender"], + receiver=payload.get("receiver"), + amount=payload["amount"], + nonce=payload["nonce"], + data=payload.get("data"), + signature=payload.get("signature"), + timestamp=payload.get("timestamp"), + ) + @property def hash_payload(self): """Returns the bytes to be signed."""