Skip to content

Comments

refactor: flatten directory structure to single minichain package#19

Open
SIDDHANTCOOKIE wants to merge 5 commits intoStabilityNexus:mainfrom
SIDDHANTCOOKIE:feature/flatten-structure
Open

refactor: flatten directory structure to single minichain package#19
SIDDHANTCOOKIE wants to merge 5 commits intoStabilityNexus:mainfrom
SIDDHANTCOOKIE:feature/flatten-structure

Conversation

@SIDDHANTCOOKIE
Copy link

@SIDDHANTCOOKIE SIDDHANTCOOKIE commented Feb 21, 2026

Description

Hey @Zahnentferner
This PR is a direct follow-up to the recent discussions on discord and addresses the feedback regarding the project's folder structure:
To improve minimalism and make the codebase easier for new developers to explore, this PR flattens the architecture by consolidating the single-file packages into a single cohesive minichain directory.

Changes Made:

  • Consolidated Folders: Moved the contents of consensus/, core/, [network/] and [node/] into a single minichain/ directory.
  • Removed Boilerplate: Deleted the empty [init.py]) files that were required by the previous nested structure.
  • Updated Imports: Refactored internal module imports and updated [main.py] and the tests/ directory to import directly from the unified minichain package.

Addressed Issues:

Resolves maintainer request on discord to flatten the directory structure.

Screenshots/Recordings:

image

Additional Notes:

  • Simpler navigation: Instead of opening 4 different directories to find 4 files, everything core to the blockchain logic lives in one place.
  • Testing: Verified that all unit tests still pass successfully and [main.py] executes without errors.

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions.
  • If applicable, I have made corresponding changes or additions to the documentation.
  • If applicable, I have made corresponding changes or additions to tests.
  • My changes generate no new warnings or errors.
  • I have joined the Stability Nexus's Discord server and I will share a link to this PR with the project maintainers there.
  • I have read the Contribution Guidelines.
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.

AI Usage Disclosure

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: N/A

Summary by CodeRabbit

  • Refactor

    • Reorganized package exports and public API surface (some symbols moved between packages).
    • Simplified component initialization and runtime call interfaces; updated network handler registration.
    • Switched many imports to package-relative resolution.
    • Added async network stop method for cleaner shutdown.
  • Tests

    • Updated tests to use the reorganized package surface.
  • Chores

    • Added Python build-artifact patterns to .gitignore.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Consolidates public exports into minichain (removing re-exports from consensus, network, and node), converts several absolute imports to relative within minichain, updates main.py wiring to use new constructors/signatures, adds Python ignore patterns to .gitignore, and adds handler registration plus async stop() to minichain.p2p.

Changes

Cohort / File(s) Summary
Build config
/.gitignore
Added Python ignore patterns: __pycache__/, *.py[cod], *$py.class, *.so.
API re-exports moved
consensus/__init__.py, minichain/__init__.py, network/__init__.py, node/__init__.py
Removed re-exports from consensus, network, and node; added re-exports in minichain for mine_block, calculate_hash, MiningExceededError, P2PNetwork, and Mempool, shifting package-level public API.
Relative import refactors
minichain/block.py, minichain/chain.py, minichain/mempool.py, minichain/state.py
Replaced absolute imports with relative imports (.transaction, .block, .state, .pow, .contract) to use package-local modules.
Main runtime wiring
main.py
Switched to minichain imports; Blockchain(state)Blockchain(); P2PNetwork(None)P2PNetwork(); updated _run_node signature and call site to remove state and accept get_next_nonce/claim_nonce accordingly; replaced network.handler_callback with network.register_handler.
P2P API changes
minichain/p2p.py
P2PNetwork constructor now accepts optional handler and stores _handler_callback; added register_handler to set handler; improved handle_message to use internal callback or warn; added async stop() to shut down/clear pubsub.
Tests updated
tests/test_contract.py, tests/test_core.py
Tests now import State, Transaction, and Blockchain from minichain instead of core.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Python Lang

Suggested reviewers

  • Zahnentferner

Poem

🐇 I hopped through modules, neat and spry,
I moved exports and waved old paths goodbye.
I taught the network how to register and stop,
I nudged imports closer—now they fit the crop.
Hop—package tidy, and I'm off to hop-hop-hop. 🥕

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the changeset: consolidating nested single-file packages (consensus, core, network, node) into a unified minichain directory structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SIDDHANTCOOKIE SIDDHANTCOOKIE force-pushed the feature/flatten-structure branch from 592e7a2 to 59b0093 Compare February 21, 2026 11:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.py (1)

80-92: ⚠️ Potential issue | 🟠 Major

state and chain.state are now two different, permanently diverged objects — claim_nonce reads stale data.

Blockchain() (line 81) creates its own internal State() instance. The standalone state = State() on line 80 is never mutated by any transaction or block addition; state.get_account(address) will always return a freshly-initialized account with nonce = 0.

claim_nonce only produces correct nonces by coincidence: max(account_nonce=0, local_nonce) reduces to pending_nonce_map[address], which is kept in sync with chain.state via sync_nonce. If pending_nonce_map has no entry for an address (first call before any sync_nonce for that address), the function returns 0 regardless of what chain.state actually holds for that address's nonce.

The AI summary confirms this was previously Blockchain(state) — the two objects were the same. That sharing was lost in this PR.

The fix is to remove state and close over chain instead:

🐛 Proposed fix
 async def node_loop():
     logger.info("Starting MiniChain Node with Smart Contracts")
 
-    state = State()
     chain = Blockchain()
     mempool = Mempool()
 
     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)
         pending_nonce_map[address] = next_nonce + 1
         return next_nonce
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.py` around lines 80 - 92, The standalone State() instance is stale and
claim_nonce reads from it; remove the unused state variable and have claim_nonce
consult the blockchain's canonical state (chain.state) instead of state; update
any uses of state.get_account(...) inside claim_nonce to use
chain.state.get_account(...), ensure pending_nonce_map and sync_nonce continue
to coordinate with chain.state, and delete the now-unused State() instantiation
to avoid diverged state objects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.gitignore:
- Around line 328-332: Update the Python ignore block in .gitignore to cover
common artifacts missing from the current entries (__pycache__/, *.py[cod],
*$py.class, *.so): add virtualenv folders (e.g., .venv/, venv/, env/),
build/dist metadata (build/, dist/, *.egg-info/, pip-wheel-metadata/), test and
cache outputs (.pytest_cache/, .mypy_cache/, .cache/, .eggs/), coverage and
report files (.coverage, htmlcov/), and packaging helpers (eggs, wheel caches);
modify the Python block around the existing patterns so these extra patterns are
included together for clearer maintenance.

In `@main.py`:
- Line 94: Replace the unused lambda parameter name to follow convention: change
the anonymous callback passed to P2PNetwork from "lambda x: None" to "lambda _:
None" so the unused argument is deliberately named as a discard and silences
ARG005; update the instantiation at the P2PNetwork call site accordingly.

---

Outside diff comments:
In `@main.py`:
- Around line 80-92: The standalone State() instance is stale and claim_nonce
reads from it; remove the unused state variable and have claim_nonce consult the
blockchain's canonical state (chain.state) instead of state; update any uses of
state.get_account(...) inside claim_nonce to use chain.state.get_account(...),
ensure pending_nonce_map and sync_nonce continue to coordinate with chain.state,
and delete the now-unused State() instantiation to avoid diverged state objects.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.gitignore:
- Around line 327-332: The Python ignore block currently includes __pycache__/,
*.py[cod], *$py.class, and *.so but should be expanded to cover common
virtualenvs, build artifacts and test/coverage outputs; update the .gitignore by
adding patterns for virtual environments (.venv/, venv/, env/, ENV/), build/dist
and packaging metadata (build/, dist/, *.egg-info/, pip-wheel-metadata/),
testing and coverage caches (.pytest_cache/, .coverage, coverage.xml, htmlcov/),
and tool caches (mypy cache: .mypy_cache/, pip cache if needed) so files
produced by functions/tools referenced in the repo are not accidentally
committed. Ensure the new entries are placed near the existing Python block for
clarity and do not remove the current patterns (__pycache__/, *.py[cod],
*$py.class, *.so).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@main.py`:
- Around line 85-91: The function claim_nonce lacks a return type annotation;
add an explicit return type of int to its signature (i.e., annotate claim_nonce
with "-> int") so the function declaration reflects that it always returns an
integer and satisfies Ruff ANN202; update the def line for claim_nonce
accordingly and run linters to confirm the warning is resolved.
- Around line 128-131: The finally block in main.py that follows await
_run_node(...) is a no-op and can leak resources opened by network.start();
implement P2PNetwork.stop() to perform async cleanup and then call await
network.stop() in that finally block (or if stop() may not exist yet, attempt a
guarded await network.stop() and fall back to cancelling/closing any network
attributes/tasks), ensuring network.start()/network.stop() are paired; update
the P2PNetwork class to provide an async stop method and wire it into the main
finally to always release connections/tasks on exceptions or KeyboardInterrupt.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@main.py`:
- Line 93: P2PNetwork is being constructed with a dummy lambda to satisfy a
callable check then immediately reassigned to the real handler, which defeats
validation; update P2PNetwork.__init__ to accept an optional handler_callback:
allow None as a valid initial value, store it (preferably as a private attribute
like self._handler_callback) and provide a setter or register_handler(method) to
validate callables when assigning later, then change the caller (where network =
P2PNetwork(lambda _: None)) to pass None or omit the argument and register the
real callback via the new setter (or property) instead of reassigning the public
attribute directly.

In `@minichain/p2p.py`:
- Around line 32-37: The stop() method currently drops the pubsub reference
(self.pubsub = None) without closing it; update stop() to gracefully shut down
any real pubsub: if self.pubsub exists, call its shutdown method (try common
names like close(), stop(), or async aclose()/shutdown() as applicable), await
it when it returns a coroutine, catch and log exceptions from that shutdown
call, then set self.pubsub = None; use the stop() function and self.pubsub
identifiers when modifying the code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@minichain/p2p.py`:
- Around line 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.
- Around line 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.
- Around line 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.
- Around line 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.

Comment on lines +28 to +31
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
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.

Comment on lines +49 to +52
import asyncio
res = shutdown_meth()
if asyncio.iscoroutine(res):
await res
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.

Comment on lines +53 to +54
except Exception as e:
logger.error("Network: Error shutting down pubsub: %s", e)
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.

Comment on lines +113 to +114
if self._handler_callback:
await self._handler_callback(data)
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant