Skip to content

Fix 7 critical bugs preventing node from running correctly#32

Closed
VincenzoImp wants to merge 6 commits intoStabilityNexus:mainfrom
VincenzoImp:fix/critical-runtime-bugs
Closed

Fix 7 critical bugs preventing node from running correctly#32
VincenzoImp wants to merge 6 commits intoStabilityNexus:mainfrom
VincenzoImp:fix/critical-runtime-bugs

Conversation

@VincenzoImp
Copy link
Copy Markdown

@VincenzoImp VincenzoImp commented Feb 23, 2026

Summary

While reviewing the codebase, I found several critical bugs that prevent the node from starting or cause silent state corruption. The application crashes immediately with TypeError, ValueError, or AttributeError before any blockchain logic executes. This PR fixes all of them with minimal, targeted changes.

Bugs Fixed

1. _run_node() parameter mismatch — TypeError

File: main.py

_run_node is called with 6 arguments (line 133) but defined with only 5 parameters (line 138). The state argument is passed but missing from the signature.

Fix: Removed the unnecessary state argument from the call site since _run_node correctly accesses state through chain.state.

2. P2PNetwork(None)ValueError

File: network/p2p.py

node_loop() creates P2PNetwork(None) before defining _handle_network_data, but the constructor validates callable(handler_callback) and raises ValueError.

Fix: Made handler_callback optional (default None), moved validation to handle_message() with a debug-level log (since this is a normal transient state during startup).

3. Missing stop() method — AttributeError

File: network/p2p.py

node_loop() calls await network.stop() in its finally block, but P2PNetwork has no stop() method.

Fix: Added stop() method stub.

4. Blockchain(state) constructor doesn't accept arguments — TypeError

File: core/chain.py

node_loop() passes a State instance to Blockchain(state), but __init__ takes no parameters.

Fix: Made state an optional parameter.

5. Duplicate State instances — silent state corruption

Files: main.py + core/chain.py

Two separate State objects existed: one in node_loop() and one created inside Blockchain.__init__(). Changes to one didn't affect the other, causing completely desynchronized balances and nonces.

Fix: Resolved by bug 4's fix — Blockchain now accepts and uses the shared State instance.

6. Transaction timestamp corruption — breaks P2P communication

File: core/transaction.py

When reconstructing a transaction from network JSON, the timestamp (already in milliseconds) was multiplied by 1000 again: round(timestamp * 1000). This changed hash_payload, invalidating the signature for every network-received transaction.

Fix: Coerce timestamp to int (for deterministic serialization) when provided, only generate millisecond timestamp when None: int(timestamp) if timestamp is not None else int(time.time() * 1000).

7. Stale state reference in claim_nonce — nonce desynchronization

File: main.py

claim_nonce closure 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.

Fix: Changed state.get_account(address)chain.state.get_account(address) in claim_nonce.

Test Results

After applying all fixes, 10/11 existing tests pass. The single failure (test_deploy_and_execute) is a pre-existing issue caused by macOS rlimit restrictions in the contract sandbox — unrelated to these changes.

tests/test_contract.py::test_balance_and_nonce_updates        PASSED
tests/test_contract.py::test_call_non_existent_contract       PASSED
tests/test_contract.py::test_contract_runtime_exception       PASSED
tests/test_contract.py::test_deploy_and_execute               FAILED (pre-existing)
tests/test_contract.py::test_deploy_insufficient_balance      PASSED
tests/test_contract.py::test_redeploy_same_address            PASSED
tests/test_core.py::test_genesis_block                        PASSED
tests/test_core.py::test_insufficient_funds                   PASSED
tests/test_core.py::test_state_transfer                       PASSED
tests/test_core.py::test_transaction_signature                PASSED
tests/test_core.py::test_transaction_wrong_signer             PASSED

Files Changed

File What
main.py Removed unused state param from _run_node, fixed claim_nonce to use chain.state
network/p2p.py Optional callback, stop() method, validation guard in handle_message()
core/chain.py Optional state param in Blockchain.__init__()
core/transaction.py Fixed timestamp: coerce to int, no double-multiplication

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for initializing blockchain with an optional external state.
  • Bug Fixes

    • Improved timestamp handling for better precision and consistency.
    • Enhanced network shutdown process to properly handle asynchronous operations.
  • Refactor

    • Removed unused import to streamline dependencies.
    • Added validation checks for network message handling.

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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 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

The PR removes the State import from main.py, makes the Blockchain constructor accept an optional state parameter, adds return type annotations to P2PNetwork's __init__, enhances the stop() method to await coroutines, adds an early guard in handle_message(), and changes timestamp casting behavior in the Transaction class.

Changes

Cohort / File(s) Summary
Import cleanup
main.py
Removed State from imports, no longer directly importing or using the State class.
Blockchain constructor enhancement
minichain/chain.py
Updated __init__ to accept optional state=None parameter; initializes with provided state or creates a new State() instance.
P2P Network improvements
minichain/p2p.py
Added return type annotation (-> None) to __init__, enhanced stop() to await coroutines, added early guard in handle_message() to check callback callability, and initialized self.pubsub = None.
Transaction timestamp handling
minichain/transaction.py
Changed timestamp casting from rounding (seconds to milliseconds) to direct int conversion; affects how provided timestamps are interpreted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Python Lang

Suggested reviewers

  • Zahnentferner

Poem

🐰 A hop through the code, some tweaks to refine,
State takes its leave, constructors align,
Timestamps now cast with a different dance,
Coroutines await their moment's chance! ✨

🚥 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 reflects the main objective of the PR: fixing critical runtime bugs that prevented the node from running correctly, with multiple targeted fixes across several files.

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

✨ Finishing Touches
🧪 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.

Copy link
Copy Markdown
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: 3

♻️ Duplicate comments (1)
main.py (1)

138-138: Ruff ARG001 (state unused) is a valid warning — addressed by the fix proposed in the core/chain.py comment above.

state is accepted but never referenced inside _run_node; every state access goes through chain.state. The proposed fix there removes this parameter and its callsite.

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

In `@main.py` at line 138, The _run_node function currently accepts an unused
parameter state which triggers Ruff ARG001; remove the state parameter from the
async def _run_node(...) signature and update every callsite that passes state
to instead rely on chain.state (as other code does), ensuring the function
definition and all invocations (including any tests or task schedulers that call
_run_node) are updated to match the new signature and that no remaining
references to the removed parameter remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/transaction.py`:
- Line 15: The constructor currently assigns self.timestamp potentially as a
float, breaking deterministic serialization; update the Transaction constructor
(where self.timestamp is set) to coerce any provided timestamp to an integer
(e.g., use int(...) on the provided timestamp) and when generating a default
timestamp use int(round(time.time() * 1000)) or int(time.time() * 1000) so
hash_payload and signature logic see a consistent integer-millisecond value.

In `@network/p2p.py`:
- Around line 62-64: The logger call in the message handling path uses
logger.warning when no handler is set, which is noisy; change the log level to
logger.debug in the method that checks self.handler_callback (referencing
handler_callback and the message handling block in the P2PNetwork class /
function where the current check if not callable(self.handler_callback) exists)
so that receiving messages with a None callback is logged at DEBUG instead of
WARNING; update the log text if needed but keep the same intent.

---

Duplicate comments:
In `@main.py`:
- Line 138: The _run_node function currently accepts an unused parameter state
which triggers Ruff ARG001; remove the state parameter from the async def
_run_node(...) signature and update every callsite that passes state to instead
rely on chain.state (as other code does), ensuring the function definition and
all invocations (including any tests or task schedulers that call _run_node) are
updated to match the new signature and that no remaining references to the
removed parameter remain.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d2792d and 08515cc.

📒 Files selected for processing (4)
  • core/chain.py
  • core/transaction.py
  • main.py
  • network/p2p.py

- 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)
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.
@VincenzoImp VincenzoImp changed the title Fix 6 critical bugs preventing node from running Fix 7 critical bugs preventing node from running correctly Feb 23, 2026
Copy link
Copy Markdown
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: 1

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)

83-84: 🧹 Nitpick | 🔵 Trivial

Remove the orphaned state variable — Blockchain() now creates its own.

After chain = Blockchain(state), the local state variable is never read again; all subsequent state access correctly goes through chain.state. Once add_block() replaces chain.state with a deep copy, state silently diverges — any future developer who reaches for state by mistake will introduce exactly the bug this PR just fixed.

Since Blockchain.__init__ accepts state=None and creates State() internally, simplify to:

Proposed change
-    state = State()
-    chain = Blockchain(state)
+    chain = Blockchain()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.py` around lines 83 - 84, Remove the unused local State instance and
stop passing it into the Blockchain constructor: delete the orphaned variable
"state" and construct the chain with "chain = Blockchain()" since
Blockchain.__init__(state=None) already creates its own State; also ensure no
other code references the removed local "state" (especially around add_block
which replaces chain.state), so update any accidental uses to reference
chain.state instead.
♻️ Duplicate comments (1)
network/p2p.py (1)

62-64: Previously flagged log-level issue is now resolved.

Guard correctly short-circuits on non-callable callbacks; logger.debug (rather than logger.warning) avoids noise during the transient window between P2PNetwork(None) construction and callback assignment.

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

In `@network/p2p.py` around lines 62 - 64, The guard that short-circuits when
self.handler_callback is not callable should remain using logger.debug rather
than logger.warning to avoid noisy logs during the transient P2PNetwork(None)
construction window; in the method that checks handler_callback (look for the
callable(self.handler_callback) check in network/p2p.py) keep the early return
and the logger.debug("Network: No handler callback set, ignoring message") line
as-is so non-callable callbacks are silently ignored at debug level.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@network/p2p.py`:
- Around line 22-23: Add an explicit return annotation to the constructor:
change the method signature of __init__(self, handler_callback=None) to include
"-> None" (i.e., __init__(self, handler_callback=None) -> None) so the __init__
method has an explicit None return type; keep the existing handler_callback
parameter as-is.

---

Outside diff comments:
In `@main.py`:
- Around line 83-84: Remove the unused local State instance and stop passing it
into the Blockchain constructor: delete the orphaned variable "state" and
construct the chain with "chain = Blockchain()" since
Blockchain.__init__(state=None) already creates its own State; also ensure no
other code references the removed local "state" (especially around add_block
which replaces chain.state), so update any accidental uses to reference
chain.state instead.

---

Duplicate comments:
In `@network/p2p.py`:
- Around line 62-64: The guard that short-circuits when self.handler_callback is
not callable should remain using logger.debug rather than logger.warning to
avoid noisy logs during the transient P2PNetwork(None) construction window; in
the method that checks handler_callback (look for the
callable(self.handler_callback) check in network/p2p.py) keep the early return
and the logger.debug("Network: No handler callback set, ignoring message") line
as-is so non-callable callbacks are silently ignored at debug level.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08515cc and c673b28.

📒 Files selected for processing (3)
  • core/transaction.py
  • main.py
  • network/p2p.py

Addresses Ruff ANN204 warning flagged in code review.
@VincenzoImp
Copy link
Copy Markdown
Author

@coderabbitai resolve

Copy link
Copy Markdown
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: 1

♻️ Duplicate comments (1)
network/p2p.py (1)

62-64: LGTM — logger.debug correctly addresses the past review.

The early-return guard gracefully handles the transient state where no callback is wired yet, and the log level no longer produces noise in production or mock mode.

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

In `@network/p2p.py` around lines 62 - 64, The guard correctly checks if
self.handler_callback is callable and logs at debug level, so no code change is
required; keep the early-return in the method that contains the callable check
(reference: self.handler_callback and logger.debug in network/p2p.py) as-is to
handle transient uninitialized state without noisy logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@network/p2p.py`:
- Around line 54-56: Add a return type annotation -> None to the async stop
method to match the project's ANN204 convention applied to __init__ and other
methods; update the method signature async def stop(self): to async def
stop(self) -> None: (also consider adding the same annotation to start() if you
plan to update older methods for consistency) so tools like Ruff recognize the
explicit None return type for stop().

---

Duplicate comments:
In `@network/p2p.py`:
- Around line 62-64: The guard correctly checks if self.handler_callback is
callable and logs at debug level, so no code change is required; keep the
early-return in the method that contains the callable check (reference:
self.handler_callback and logger.debug in network/p2p.py) as-is to handle
transient uninitialized state without noisy logs.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c673b28 and 46451cb.

📒 Files selected for processing (1)
  • network/p2p.py

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 2026

✅ Actions performed

Comments resolved and changes approved.

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.
Resolve conflicts from upstream PR #19 (flattened directory structure
core/, network/, node/, consensus/ → minichain/) while preserving
all bug fixes from this branch. Kept State removed from imports
(unused after previous fix) and accepted upstream type hints.
@VincenzoImp VincenzoImp force-pushed the fix/critical-runtime-bugs branch from e00c24a to 7b00276 Compare February 26, 2026 10:31
Copy link
Copy Markdown
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: 1

🤖 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 86-88: The guard that returns when self._handler_callback is not
callable makes the later "else" branch unreachable; remove the redundant
conditional and else branch and directly use the callable path: ensure the
initial check either is removed or changed to a warning without return, and
replace the later "if/else" around invoking self._handler_callback with a single
direct invocation (or a simple callable check + invoke) so only one canonical
call-site for self._handler_callback exists; target references:
self._handler_callback and the message-handling block that currently contains
the unreachable else (the two redundant checks noted).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc2d91 and e00c24a.

📒 Files selected for processing (4)
  • main.py
  • minichain/chain.py
  • minichain/p2p.py
  • minichain/transaction.py

Comment on lines +86 to +88
if not callable(self._handler_callback):
logger.debug("Network: No handler callback set, ignoring message")
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant check creates unreachable code.

The early guard at lines 86-88 returns if self._handler_callback is not callable. This makes the else branch at lines 118-119 unreachable—if execution reaches line 116, the callback is guaranteed to be callable (and thus truthy).

♻️ Suggested simplification
         try:
-            if self._handler_callback:
-                await self._handler_callback(data)
-            else:
-                logger.warning("Network Error: No handler_callback registered")
+            await self._handler_callback(data)
         except Exception:
             logger.exception("Error in network handler callback for data: %s", data)

Also applies to: 116-119

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

In `@minichain/p2p.py` around lines 86 - 88, The guard that returns when
self._handler_callback is not callable makes the later "else" branch
unreachable; remove the redundant conditional and else branch and directly use
the callable path: ensure the initial check either is removed or changed to a
warning without return, and replace the later "if/else" around invoking
self._handler_callback with a single direct invocation (or a simple callable
check + invoke) so only one canonical call-site for self._handler_callback
exists; target references: self._handler_callback and the message-handling block
that currently contains the unreachable else (the two redundant checks noted).

@VincenzoImp VincenzoImp closed this by deleting the head repository Feb 26, 2026
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