Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,9 @@ TSWLatexianTemp*
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
# Uncomment the next line to have this generated file ignored.
#*Notes.bib

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
3 changes: 0 additions & 3 deletions consensus/__init__.py

This file was deleted.

18 changes: 7 additions & 11 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
from nacl.signing import SigningKey
from nacl.encoding import HexEncoder

from core import Transaction, Blockchain, Block, State
from node import Mempool
from network import P2PNetwork
from consensus import mine_block
from minichain import Transaction, Blockchain, Block, State, Mempool, P2PNetwork, mine_block


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -80,21 +77,20 @@ 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 = {}

def claim_nonce(address):
account = state.get_account(address)
def claim_nonce(address) -> int:
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)
pending_nonce_map[address] = next_nonce + 1
return next_nonce

network = P2PNetwork(None)
network = P2PNetwork()

async def _handle_network_data(data):
logger.info("Received network data: %s", data)
Expand Down Expand Up @@ -127,10 +123,10 @@ async def _handle_network_data(data):
except Exception:
logger.exception("Error processing network data: %s", data)

network.handler_callback = _handle_network_data
network.register_handler(_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()

Expand Down
8 changes: 8 additions & 0 deletions core/__init__.py → minichain/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
from .pow import mine_block, calculate_hash, MiningExceededError
from .block import Block
from .chain import Blockchain
from .transaction import Transaction
from .state import State
from .contract import ContractMachine
from .p2p import P2PNetwork
from .mempool import Mempool

__all__ = [
"mine_block",
"calculate_hash",
"MiningExceededError",
"Block",
"Blockchain",
"Transaction",
"State",
"ContractMachine",
"P2PNetwork",
"Mempool",
]
3 changes: 1 addition & 2 deletions core/block.py → minichain/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import hashlib
import json
from typing import List, Optional
from core.transaction import Transaction

from .transaction import Transaction

def _sha256(data: str) -> str:
return hashlib.sha256(data.encode()).hexdigest()
Expand Down
6 changes: 3 additions & 3 deletions core/chain.py → minichain/chain.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from core.block import Block
from core.state import State
from consensus import calculate_hash
from .block import Block
from .state import State
from .pow import calculate_hash
import logging
import threading

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion node/mempool.py → minichain/mempool.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from consensus.pow import calculate_hash
from .pow import calculate_hash
import logging
import threading

Expand Down
37 changes: 33 additions & 4 deletions network/p2p.py → minichain/p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,42 @@ class P2PNetwork:
}
"""

def __init__(self, handler_callback):
def __init__(self, handler_callback=None):
self._handler_callback = None
if handler_callback is not None:
self.register_handler(handler_callback)
self.pubsub = None # Will be set in real implementation

def register_handler(self, handler_callback):
if not callable(handler_callback):
raise ValueError("handler_callback must be callable")
self.handler_callback = handler_callback
self.pubsub = None # Will be set in real implementation
self._handler_callback = handler_callback
Comment on lines +28 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

ValueError should be TypeError for a callable-type guard.

Raising ValueError for a wrong-argument-type is semantically incorrect; TypeError is the stdlib convention (e.g., threading.Thread(target=42) raises TypeError).

♻️ Proposed fix
 def register_handler(self, handler_callback):
-    if not callable(handler_callback):
-        raise ValueError("handler_callback must be callable")
+    if not callable(handler_callback):
+        raise TypeError("handler_callback must be callable")
     self._handler_callback = handler_callback
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 30-30: Prefer TypeError exception for invalid type

(TRY004)


[warning] 30-30: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@minichain/p2p.py` around lines 28 - 31, The register_handler method currently
raises ValueError when handler_callback is not callable; change that to raise
TypeError instead to follow stdlib conventions for wrong argument types. Locate
register_handler in the class (the method checking callable(handler_callback)),
replace the raised exception from ValueError("handler_callback must be
callable") to TypeError with the same or similar message, ensuring any
callers/tests expecting ValueError are updated if necessary.


async def start(self):
logger.info("Network: Listening on /ip4/0.0.0.0/tcp/0")
# In real libp2p, we would await host.start() here

async def stop(self):
"""Clean up network resources cleanly upon shutdown."""
logger.info("Network: Shutting down")
if self.pubsub:
try:
shutdown_meth = None
for method_name in ('close', 'stop', 'aclose', 'shutdown'):
if hasattr(self.pubsub, method_name):
shutdown_meth = getattr(self.pubsub, method_name)
break

if shutdown_meth:
import asyncio
res = shutdown_meth()
if asyncio.iscoroutine(res):
await res
Comment on lines +49 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Move import asyncio to the top of the file and prefer inspect.isawaitable over asyncio.iscoroutine.

Two issues:

  1. Importing inside the method body is non-idiomatic and runs on every stop() call; asyncio is a stdlib module with no circular-import risk.
  2. asyncio.iscoroutine(res) only matches coroutine objects; it misses asyncio.Future, asyncio.Task, and any custom __await__ implementor. inspect.isawaitable(res) is the correct, broader guard.
♻️ Proposed fix

At the top of the file:

 import json
 import logging
+import inspect

In stop():

-                if shutdown_meth:
-                    import asyncio
-                    res = shutdown_meth()
-                    if asyncio.iscoroutine(res):
-                        await res
+                if shutdown_meth:
+                    res = shutdown_meth()
+                    if inspect.isawaitable(res):
+                        await res
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@minichain/p2p.py` around lines 49 - 52, Move the local import out of the
stop() body by adding "import asyncio" (and "import inspect") at the top of the
module, and replace the asyncio.iscoroutine check with inspect.isawaitable for
the shutdown result: call shutdown_meth(), assign to res, and if
inspect.isawaitable(res) then await res; remove the in-function "import asyncio"
and ensure you reference shutdown_meth and stop() exactly as in the diff.

except Exception as e:
logger.error("Network: Error shutting down pubsub: %s", e)
Comment on lines +53 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use logger.exception instead of logger.error to capture the full traceback.

logger.error logs only the formatted message string; logger.exception automatically appends the current exception's traceback, which is far more useful when diagnosing unexpected shutdown failures. (Ruff TRY400)

♻️ Proposed fix
-            except Exception as e:
-                logger.error("Network: Error shutting down pubsub: %s", e)
+            except Exception:
+                logger.exception("Network: Error shutting down pubsub")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as e:
logger.error("Network: Error shutting down pubsub: %s", e)
try:
await self.pubsub.close()
except Exception:
logger.exception("Network: Error shutting down pubsub")
finally:
self.pubsub = None
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 53-53: Do not catch blind exception: Exception

(BLE001)


[warning] 54-54: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@minichain/p2p.py` around lines 53 - 54, In the except block that catches
exceptions during pubsub shutdown (the block logging "Network: Error shutting
down pubsub"), replace the call to logger.error(...) with logger.exception(...)
so the full traceback is captured; keep the existing message text and exception
variable/context but use logger.exception in the same except Exception as e
handler (within the pubsub shutdown logic) to include stack traces for
debugging.

finally:
self.pubsub = None

async def _broadcast_message(self, topic, msg_type, payload):
msg = json.dumps({"type": msg_type, "data": payload})
if self.pubsub:
Expand Down Expand Up @@ -84,6 +110,9 @@ async def handle_message(self, msg):
return

try:
await self.handler_callback(data)
if self._handler_callback:
await self._handler_callback(data)
Comment on lines +113 to +114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

await self._handler_callback(data) silently fails for sync callbacks.

register_handler accepts any callable, but handle_message unconditionally awaits the result. A plain (non-async) callable registered as the handler will cause TypeError: object ... can't be used in 'await' expression, which is silently swallowed by the outer except Exception block with a misleading log message. Enforce that only coroutine functions can be registered.

🛡️ Proposed fix in register_handler
+import inspect

 def register_handler(self, handler_callback):
-    if not callable(handler_callback):
-        raise TypeError("handler_callback must be callable")
+    if not inspect.iscoroutinefunction(handler_callback):
+        raise TypeError("handler_callback must be an async (coroutine) function")
     self._handler_callback = handler_callback
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@minichain/p2p.py` around lines 113 - 114, The handler registration currently
allows any callable but handle_message unconditionally awaits
self._handler_callback which causes a TypeError for sync functions; update
register_handler to validate the callable is a coroutine function (use
inspect.iscoroutinefunction or asyncio.iscoroutinefunction) and raise a clear
TypeError/ValueError if not, ensuring only async handlers are accepted; update
the register_handler signature/type hints to reflect coroutine-only callbacks
and keep handle_message unchanged to safely await _handler_callback.

else:
logger.warning("Network Error: No handler_callback registered")
except Exception:
logger.exception("Error in network handler callback for data: %s", data)
File renamed without changes.
2 changes: 1 addition & 1 deletion core/state.py → minichain/state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from nacl.hash import sha256
from nacl.encoding import HexEncoder
from core.contract import ContractMachine
from .contract import ContractMachine
import copy
import logging

Expand Down
File renamed without changes.
3 changes: 0 additions & 3 deletions network/__init__.py

This file was deleted.

3 changes: 0 additions & 3 deletions node/__init__.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/test_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import os

from core import State, Transaction
from minichain import State, Transaction
from nacl.signing import SigningKey
from nacl.encoding import HexEncoder

Expand Down
2 changes: 1 addition & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from nacl.signing import SigningKey
from nacl.encoding import HexEncoder

from core import Transaction, Blockchain, State # Removed unused imports
from minichain import Transaction, Blockchain, State # Removed unused imports

class TestCore(unittest.TestCase):
def setUp(self):
Expand Down