From 08515ccd198e2a2277e2ef6d429bf154c2c0eb28 Mon Sep 17 00:00:00 2001 From: VincenzoImp Date: Mon, 23 Feb 2026 17:12:22 +0100 Subject: [PATCH 1/5] Fix 6 critical bugs preventing node from running 1. main.py: Add missing `state` parameter to `_run_node()` signature. The function was called with 6 arguments but only accepted 5, causing TypeError at startup. 2. network/p2p.py: Make `handler_callback` optional in P2PNetwork.__init__(). node_loop() creates P2PNetwork before defining the callback, so passing None caused ValueError. 3. network/p2p.py: Add missing `stop()` method. node_loop() calls `await network.stop()` in its finally block, but the method didn't exist, causing AttributeError on shutdown. 4. core/chain.py: Accept optional `state` parameter in Blockchain.__init__(). node_loop() passes a State instance to Blockchain(), but the constructor didn't accept arguments, causing TypeError. This also fixes the duplicate-State bug where node_loop and Blockchain each had separate State instances, leading to completely desynchronized balances and nonces. 5. core/transaction.py: Fix timestamp double-multiplication on network reconstruction. When a transaction was deserialized from JSON (where timestamp is already in milliseconds), the constructor multiplied it by 1000 again, corrupting the hash and invalidating signatures for all network-received transactions. --- core/chain.py | 4 ++-- core/transaction.py | 2 +- main.py | 2 +- network/p2p.py | 11 ++++++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/core/chain.py b/core/chain.py index 9545864..91449e2 100644 --- a/core/chain.py +++ b/core/chain.py @@ -12,9 +12,9 @@ class Blockchain: Manages the blockchain, validates blocks, and commits state transitions. """ - def __init__(self): + def __init__(self, state=None): self.chain = [] - self.state = State() + self.state = state if state is not None else State() self._lock = threading.RLock() self._create_genesis_block() diff --git a/core/transaction.py b/core/transaction.py index cdf4d99..e695bcf 100644 --- a/core/transaction.py +++ b/core/transaction.py @@ -12,7 +12,7 @@ def __init__(self, sender, receiver, amount, nonce, data=None, signature=None, t self.amount = amount self.nonce = nonce self.data = data # Preserve None (do NOT normalize to "") - self.timestamp = round(timestamp * 1000) if timestamp is not None else round(time.time() * 1000) # Integer milliseconds for determinism + self.timestamp = timestamp if timestamp is not None else round(time.time() * 1000) # Integer milliseconds for determinism self.signature = signature # Hex str def to_dict(self): diff --git a/main.py b/main.py index d9670c0..cee8922 100644 --- a/main.py +++ b/main.py @@ -135,7 +135,7 @@ async def _handle_network_data(data): await network.stop() -async def _run_node(network, chain, mempool, pending_nonce_map, get_next_nonce): +async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce): await network.start() alice_sk, alice_pk = create_wallet() diff --git a/network/p2p.py b/network/p2p.py index ef0a9dd..5b1717a 100644 --- a/network/p2p.py +++ b/network/p2p.py @@ -19,9 +19,7 @@ class P2PNetwork: } """ - def __init__(self, handler_callback): - if not callable(handler_callback): - raise ValueError("handler_callback must be callable") + def __init__(self, handler_callback=None): self.handler_callback = handler_callback self.pubsub = None # Will be set in real implementation @@ -53,10 +51,17 @@ async def broadcast_block(self, block): logger.info("Network: Broadcasting Block #%d", block.index) await self._broadcast_message("minichain-global", "block", block.to_dict()) + async def stop(self): + logger.info("Network: Shutting down") + # In real libp2p, we would close the host and pubsub here + async def handle_message(self, msg): """ Callback when a p2p message is received. """ + if not callable(self.handler_callback): + logger.warning("Network: No handler callback set, ignoring message") + return try: if not hasattr(msg, "data"): From 109c7e8de882198b67212dff892cd690a38efed4 Mon Sep 17 00:00:00 2001 From: VincenzoImp Date: Mon, 23 Feb 2026 17:31:04 +0100 Subject: [PATCH 2/5] Address code review feedback - Remove unused `state` parameter from `_run_node` (accessed via `chain.state`) - Coerce transaction timestamp to int for deterministic serialization - Downgrade no-handler log from warning to debug (normal during startup) --- core/transaction.py | 2 +- main.py | 4 ++-- network/p2p.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/transaction.py b/core/transaction.py index e695bcf..e039084 100644 --- a/core/transaction.py +++ b/core/transaction.py @@ -12,7 +12,7 @@ def __init__(self, sender, receiver, amount, nonce, data=None, signature=None, t self.amount = amount self.nonce = nonce self.data = data # Preserve None (do NOT normalize to "") - self.timestamp = timestamp if timestamp is not None else round(time.time() * 1000) # Integer milliseconds for determinism + self.timestamp = int(timestamp) if timestamp is not None else int(time.time() * 1000) self.signature = signature # Hex str def to_dict(self): diff --git a/main.py b/main.py index cee8922..89e5421 100644 --- a/main.py +++ b/main.py @@ -130,12 +130,12 @@ async def _handle_network_data(data): network.handler_callback = _handle_network_data try: - await _run_node(network, state, chain, mempool, pending_nonce_map, claim_nonce) + await _run_node(network, chain, mempool, pending_nonce_map, claim_nonce) finally: await network.stop() -async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce): +async def _run_node(network, chain, mempool, pending_nonce_map, get_next_nonce): await network.start() alice_sk, alice_pk = create_wallet() diff --git a/network/p2p.py b/network/p2p.py index 5b1717a..3c55729 100644 --- a/network/p2p.py +++ b/network/p2p.py @@ -60,7 +60,7 @@ async def handle_message(self, msg): Callback when a p2p message is received. """ if not callable(self.handler_callback): - logger.warning("Network: No handler callback set, ignoring message") + logger.debug("Network: No handler callback set, ignoring message") return try: From c673b289668fcc832326ac17370ad2d1ec0d4548 Mon Sep 17 00:00:00 2001 From: VincenzoImp Date: Mon, 23 Feb 2026 21:28:12 +0100 Subject: [PATCH 3/5] Fix stale state reference in claim_nonce closure claim_nonce captured the local `state` variable, but add_block() replaces chain.state with a deep copy on every block commit. After the first block, claim_nonce was reading from the original (now stale) state object instead of the authoritative chain.state. This caused desynchronized nonce tracking in multi-block scenarios. --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 89e5421..c143b5a 100644 --- a/main.py +++ b/main.py @@ -87,7 +87,7 @@ async def node_loop(): pending_nonce_map = {} def claim_nonce(address): - account = state.get_account(address) + account = chain.state.get_account(address) account_nonce = account.get("nonce", 0) if account else 0 local_nonce = pending_nonce_map.get(address, account_nonce) next_nonce = max(account_nonce, local_nonce) From 46451cbd416c8627151ad3061088074bb998105c Mon Sep 17 00:00:00 2001 From: VincenzoImp Date: Mon, 23 Feb 2026 21:40:22 +0100 Subject: [PATCH 4/5] Add return type annotation to P2PNetwork.__init__ Addresses Ruff ANN204 warning flagged in code review. --- network/p2p.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/p2p.py b/network/p2p.py index 3c55729..a91c325 100644 --- a/network/p2p.py +++ b/network/p2p.py @@ -19,7 +19,7 @@ class P2PNetwork: } """ - def __init__(self, handler_callback=None): + def __init__(self, handler_callback=None) -> None: self.handler_callback = handler_callback self.pubsub = None # Will be set in real implementation From 8dc2d91b4c0d2bdfd99ea07bd537d100842d3229 Mon Sep 17 00:00:00 2001 From: VincenzoImp Date: Thu, 26 Feb 2026 11:16:58 +0100 Subject: [PATCH 5/5] Remove orphaned State variable from node_loop Blockchain.__init__ already creates its own State internally when none is provided. The local `state` variable was no longer referenced after the claim_nonce fix (which correctly uses chain.state), so it was just a dangling reference waiting to cause confusion. Also removes the now-unused State import. --- main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index c143b5a..66e7dbd 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from nacl.signing import SigningKey from nacl.encoding import HexEncoder -from core import Transaction, Blockchain, Block, State +from core import Transaction, Blockchain, Block from node import Mempool from network import P2PNetwork from consensus import mine_block @@ -80,8 +80,7 @@ def sync_nonce(state, pending_nonce_map, address): async def node_loop(): logger.info("Starting MiniChain Node with Smart Contracts") - state = State() - chain = Blockchain(state) + chain = Blockchain() mempool = Mempool() pending_nonce_map = {}