Skip to content

Feat/contract transfers#101

Open
SIDDHANTCOOKIE wants to merge 4 commits into
mainfrom
feat/contract-transfers
Open

Feat/contract transfers#101
SIDDHANTCOOKIE wants to merge 4 commits into
mainfrom
feat/contract-transfers

Conversation

@SIDDHANTCOOKIE

@SIDDHANTCOOKIE SIDDHANTCOOKIE commented Jun 20, 2026

Copy link
Copy Markdown
Member

Addressed Issues: #94

  • Inject transfer_out(address, amount) built-in to the sandbox environment.
  • Capture outgoing transfer requests securely without direct ledger access.
  • Refactor State.apply_transaction to validate and process contract transfers.
  • Enforce strict atomic rollbacks if a contract attempts to overspend its balance.
  • Add comprehensive test suite in test_contract_transfers.py covering successful transfers, insufficient balance failures, and immediate transfer of incoming msg['value'].

Screenshots/Recordings:

TODO: If applicable, add screenshots or recordings that demonstrate the interface before and after the changes.

Additional Notes:

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: TODO

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 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.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

  • New Features

    • Added HTTP JSON-RPC server enabling external blockchain queries and transaction submission
    • Automatic chain synchronization and conflict resolution for improved consensus
    • Contract-based outbound transfer functionality with transaction balance validation
    • Extended P2P protocol to support chain synchronization messaging
  • Tests

    • Comprehensive test coverage for contract transfers, chain reorganization scenarios, and RPC endpoints

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR introduces contract transfer_out support with balance validation and rollback, chain conflict resolution using cumulative proof-of-work comparison with full state rebuild from a genesis snapshot, a sorted-list mempool replacing the nested-dict structure, two new P2P protocol messages (chain_request/chain_response) for chain sync, and a new aiohttp JSON-RPC server (JSONRPCServer) wired into node startup.

Changes

Blockchain node feature expansion

Layer / File(s) Summary
Contract transfer_out: sandbox, state commit/rollback
minichain/contract.py, minichain/state.py, tests/test_contract_transfers.py
Worker exposes a sandboxed transfer_out function recording transfers in a list; ContractMachine.execute now returns transfers and storage directly. State.apply_transaction validates total outbound transfers against the contract balance, rolls back on failure via new snapshot()/restore() methods, otherwise commits storage and credits each transfer destination. Three new test cases cover success, rollback on insufficient balance, and transfer of incoming funds.
Mempool sorted-list refactor
minichain/mempool.py
Replaces nested-dict _pool/_size with a single sorted _list. add_transaction inserts by fee ordering within a nonce window; get_transactions_for_block slices the list directly; remove_transactions filters by (sender, nonce) set; __len__ delegates to list length.
Chain reorg: genesis snapshot, PoW comparison, resolve_conflicts
minichain/chain.py, tests/test_reorg.py
Genesis block creation captures a post-allocation state snapshot. get_total_work computes cumulative PoW. resolve_conflicts compares total work, verifies genesis hash, rebuilds a temporary state from the snapshot by re-applying and verifying each block, then swaps chain/state and returns orphaned transactions. Three tests cover heavier chain acceptance, reorg with orphan detection, and lighter chain rejection.
P2P chain sync protocol extension
minichain/p2p.py
Adds chain_request and chain_response to SUPPORTED_MESSAGE_TYPES, registers validators for both, and adds broadcast_chain_request() and send_chain_response(blocks_dicts, writer) to P2PNetwork.
JSON-RPC server (aiohttp)
minichain/rpc.py, tests/test_rpc.py
JSONRPCServer exposes a single POST endpoint handling batch and single JSON-RPC requests. Implements mc_blockNumber, mc_getBlockByNumber, mc_getBalance, and mc_sendTransaction with standard error codes. Three tests cover block number retrieval, block lookup by index, and unknown-method error response.
Node startup: handler wiring, RPC boot, chain sync handlers, shutdown
main.py
make_network_handler gains network parameter. Block handler triggers broadcast_chain_request() when ahead; new chain_request/chain_response handlers perform chain sync and mempool orphan restoration. JSONRPCServer is instantiated and started on P2P port offset; its task is cancelled on shutdown.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant JSONRPCServer
  participant ExternalPeer
  participant P2PNetwork
  participant make_network_handler
  participant Blockchain
  participant Mempool

  rect rgba(100, 149, 237, 0.5)
    Note over ExternalPeer,Mempool: Chain sync via P2P
    ExternalPeer->>P2PNetwork: block (index ahead of local tip)
    P2PNetwork->>make_network_handler: dispatch block msg
    make_network_handler->>P2PNetwork: broadcast_chain_request()
    P2PNetwork->>ExternalPeer: chain_request {}
    ExternalPeer->>P2PNetwork: chain_response {blocks: [...]}
    P2PNetwork->>make_network_handler: dispatch chain_response
    make_network_handler->>Blockchain: resolve_conflicts(new_chain_list)
    Blockchain-->>make_network_handler: (True, orphans)
    make_network_handler->>Mempool: add each orphan tx
  end

  rect rgba(60, 179, 113, 0.5)
    Note over Client,P2PNetwork: RPC transaction submission
    Client->>JSONRPCServer: POST / mc_sendTransaction(payload)
    JSONRPCServer->>Mempool: add_transaction(tx)
    JSONRPCServer->>P2PNetwork: broadcast_transaction(tx) [async task]
    JSONRPCServer-->>Client: {result: tx_id}
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • StabilityNexus/MiniChain#46: The P2P message handler wiring in main.py and minichain/p2p.py builds directly on the earlier P2P networking/node startup refactor.
  • StabilityNexus/MiniChain#92: Both PRs change minichain/contract.py and minichain/state.py to return structured dict results from ContractMachine.execute and update state commit logic accordingly.
  • StabilityNexus/MiniChain#70: Both PRs directly rewrite the same core minichain/mempool.py methods (add_transaction, get_transactions_for_block, remove_transactions) around alternative list-based data structures.

Suggested labels

Python Lang

Suggested reviewers

  • Zahnentferner

Poem

🐇 Hoppity-hop through the chain I go,
Resolving forks with a PoW show!
Transfers out and the mempool sorted neat,
JSON-RPC thumping to a steady beat.
New blocks arrive? Just ask peers and sync —
This rabbit's node is never on the brink! 🌟

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/contract transfers' is directly related to the main objective of implementing contract transfer functionality, but uses a generic prefix format that is only partially descriptive of the core change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/contract-transfers

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
tests/test_rpc.py (1)

27-63: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a regression test for non-object JSON-RPC requests.

Please add a case like json=1 (or batch entry with a primitive) and assert {"error":{"code":-32600}}. This protects the invalid-request path from returning 500s.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_rpc.py` around lines 27 - 63, Add a new test function following
the same pattern as the existing test functions (test_rpc_blockNumber,
test_rpc_getBlockByNumber, test_rpc_invalid_method) that sends a non-object
JSON-RPC request to verify error handling for invalid request formats. The test
should send a primitive value like the integer 1 as the JSON payload using
json=1 in the post request, assert that the response status is 200, and verify
that the response contains an error object with error code -32600 (Invalid
Request) to ensure the invalid-request path does not return 500 errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@main.py`:
- Around line 440-441: The RPC server startup uses asyncio.create_task which can
hide failures and does not await the actual startup. Replace the create_task
call that assigns to rpc_task with an await of rpc_server.start() directly, and
store only the server reference if needed. At shutdown (around line 469 where
the task is currently canceled), replace the task cancellation with an explicit
await rpc_server.stop() call to reliably stop the RPC server instead of just
canceling the task.
- Around line 169-174: The chain response is being broadcasted to all peers via
network._broadcast_raw() when it should only be sent to the specific peer that
requested it. Replace the asyncio.create_task(network._broadcast_raw(payload))
call with a unicast method that sends the chain_response payload only to the
requesting peer connection. You will need to capture the requesting peer's
connection identifier from the message context and use an appropriate network
method to send the response back to only that peer rather than broadcasting to
all connected peers.

In `@minichain/chain.py`:
- Around line 188-190: Remove the unused variables state_snapshot and
original_chain from the reorg validation logic. These variables are captured at
the beginning but never actually used for rollback purposes since all validation
operations work with temp_state instead, and self.state is only replaced upon
successful reorg completion. Simply delete the lines that assign to
state_snapshot and original_chain to clean up unnecessary variable assignments.

In `@minichain/contract.py`:
- Around line 48-58: The address validation in the transfer_out function only
checks if the address is a string type but does not validate the actual format
of the address. Add additional validation to ensure the address has a valid
format by checking for non-empty strings and any other format requirements (such
as proper hex character format or minimum length). This validation should occur
after the type check for the address parameter and before appending the transfer
to the transfers list.

In `@minichain/mempool.py`:
- Around line 65-68: The comment in the get_transactions_for_block method
incorrectly states that the retrieval is O(1), but the list slicing operation
that creates a new list from self._list[:self.transactions_per_block] is
actually O(k) where k equals transactions_per_block. Update the comment to
accurately reflect that this is an O(k) operation, while noting it remains
efficient compared to alternative approaches.

In `@minichain/p2p.py`:
- Around line 410-413: The send_chain_response method currently serializes all
blocks from blocks_dicts into a single unbounded payload, which can create
oversized sync frames and degrade performance as the chain grows. Refactor this
method to implement chunked or paginated responses by defining explicit maximum
limits (either by block count or payload size in bytes), and send multiple
chain_response messages if the total blocks exceed the limit. Process
blocks_dicts in batches, serializing and sending each batch separately to ensure
each payload stays within the defined maximum bounds.

In `@minichain/rpc.py`:
- Around line 17-23: The `start` method creates `runner` (AppRunner) and `site`
(TCPSite) as local variables that go out of scope when the method returns,
making it impossible to cleanly shutdown the server later. Store these objects
as instance attributes by assigning them to `self.runner` and `self.site`
respectively, then create a new async method (e.g., `stop` or `shutdown`) that
properly cleans up these resources by calling their respective cleanup methods
to ensure proper lifecycle management and prevent resource leaks.
- Around line 40-41: The error response on line 41 calls `req.get("id")` without
verifying that `req` is a dict, but the condition on line 40 can be true when
`req` is not a dict (first part of the OR condition), causing an AttributeError
when non-dict objects don't have a get method. Fix this by checking if req is a
dict before calling `.get("id")`, otherwise use None as the id value in the
error response to safely handle non-dict request objects.
- Line 4: The aiohttp module is imported at the top of the file (from aiohttp
import web) and used throughout the code (web.Application, web.AppRunner,
web.TCPSite, web.post), but aiohttp is not declared as a dependency in the
project's requirements.txt file. Add aiohttp to the requirements.txt file to
ensure the dependency is properly declared and installed when the project is set
up, preventing ImportError at runtime.

In `@tests/test_contract_transfers.py`:
- Line 34: Update the comment that states "plus 10 fee" to reflect the actual
fee value of 1000 used in the transaction. Change the comment text from "Sender
sent 100 to contract, plus 10 fee" to "Sender sent 100 to contract, plus 1000
fee" to match the fee=1000 parameter in the actual code.

In `@tests/test_reorg.py`:
- Line 2: The import statement for os is duplicated in the test_reorg.py file,
appearing on both line 2 and line 11. Remove one of these duplicate import os
statements, keeping only a single import os declaration at the top of the file
to avoid redundancy and maintain clean import organization.

---

Outside diff comments:
In `@tests/test_rpc.py`:
- Around line 27-63: Add a new test function following the same pattern as the
existing test functions (test_rpc_blockNumber, test_rpc_getBlockByNumber,
test_rpc_invalid_method) that sends a non-object JSON-RPC request to verify
error handling for invalid request formats. The test should send a primitive
value like the integer 1 as the JSON payload using json=1 in the post request,
assert that the response status is 200, and verify that the response contains an
error object with error code -32600 (Invalid Request) to ensure the
invalid-request path does not return 500 errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba4720fa-9556-48f2-9038-4968a5cbe31e

📥 Commits

Reviewing files that changed from the base of the PR and between eb8e75d and 5ec7c0b.

📒 Files selected for processing (10)
  • main.py
  • minichain/chain.py
  • minichain/contract.py
  • minichain/mempool.py
  • minichain/p2p.py
  • minichain/rpc.py
  • minichain/state.py
  • tests/test_contract_transfers.py
  • tests/test_reorg.py
  • tests/test_rpc.py

Comment thread main.py
Comment on lines +169 to +174
elif msg_type == "chain_request":
logger.info("📡 Peer requested chain sync. Broadcasting our chain...")
blocks_dicts = [b.to_dict() for b in chain.chain]
payload = {"type": "chain_response", "data": {"blocks": blocks_dicts}}
asyncio.create_task(network._broadcast_raw(payload))

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid broadcasting chain_response to all peers for a single chain_request.

At Line 173, one requester triggers a full-chain broadcast to every peer, which creates amplification and unnecessary reorg parsing load. This should be a unicast reply to the requesting connection.

🧰 Tools
🪛 Ruff (0.15.17)

[warning] 173-173: Store a reference to the return value of asyncio.create_task

(RUF006)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 169 - 174, The chain response is being broadcasted to
all peers via network._broadcast_raw() when it should only be sent to the
specific peer that requested it. Replace the
asyncio.create_task(network._broadcast_raw(payload)) call with a unicast method
that sends the chain_response payload only to the requesting peer connection.
You will need to capture the requesting peer's connection identifier from the
message context and use an appropriate network method to send the response back
to only that peer rather than broadcasting to all connected peers.

Comment thread main.py
Comment on lines +440 to +441
rpc_port = 8545 + (port - 9000)
rpc_task = asyncio.create_task(rpc_server.start(host="127.0.0.1", port=rpc_port))

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not fire-and-forget RPC startup; await startup and stop explicitly.

At Line 441, create_task can hide startup/bind failures. At Line 469, canceling that task is not a reliable server shutdown mechanism. Start the RPC server with await and call an explicit await rpc_server.stop() during shutdown.

Also applies to: 468-469

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 440 - 441, The RPC server startup uses
asyncio.create_task which can hide failures and does not await the actual
startup. Replace the create_task call that assigns to rpc_task with an await of
rpc_server.start() directly, and store only the server reference if needed. At
shutdown (around line 469 where the task is currently canceled), replace the
task cancellation with an explicit await rpc_server.stop() call to reliably stop
the RPC server instead of just canceling the task.

Comment thread minichain/chain.py
Comment on lines +188 to +190
# 2. Snapshot current state and chain in case reorg fails validation
state_snapshot = self.state.snapshot()
original_chain = list(self.chain)

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 | 💤 Low value

Remove unused snapshot variables.

state_snapshot is captured but never used for rollback since all validation operates on temp_state. The original state is preserved naturally because self.state is only replaced upon successful reorg at line 234.

🧹 Suggested cleanup
             logger.info("Incoming chain is heavier (%s > %s). Attempting reorg...", new_work, current_work)
 
-            # 2. Snapshot current state and chain in case reorg fails validation
-            state_snapshot = self.state.snapshot()
+            # 2. Preserve reference to original chain for orphan calculation
             original_chain = list(self.chain)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/chain.py` around lines 188 - 190, Remove the unused variables
state_snapshot and original_chain from the reorg validation logic. These
variables are captured at the beginning but never actually used for rollback
purposes since all validation operations work with temp_state instead, and
self.state is only replaced upon successful reorg completion. Simply delete the
lines that assign to state_snapshot and original_chain to clean up unnecessary
variable assignments.

Comment thread minichain/contract.py
Comment on lines +48 to +58
transfers = []

def transfer_out(address, amount):
if not isinstance(amount, int) or amount <= 0:
raise ValueError("Invalid transfer amount")
if not isinstance(address, str):
raise ValueError("Invalid address type")
transfers.append({"to": address, "amount": amount})

globals_dict["__builtins__"]["transfer_out"] = transfer_out

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 | ⚡ Quick win

Consider validating address format in transfer_out.

The address validation only checks for str type but does not validate the format (e.g., length, hex characters). This allows contracts to transfer to invalid or malformed addresses like empty strings. While State.get_account() will create accounts for any string, this could lead to funds being sent to unretrievable addresses.

🛡️ Suggested validation
         def transfer_out(address, amount):
             if not isinstance(amount, int) or amount <= 0:
                 raise ValueError("Invalid transfer amount")
             if not isinstance(address, str):
-                raise ValueError("Invalid address type")
+                raise TypeError("Invalid address type")
+            if len(address) < 40 or not all(c in '0123456789abcdefABCDEF' for c in address):
+                raise ValueError("Invalid address format")
             transfers.append({"to": address, "amount": amount})
📝 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
transfers = []
def transfer_out(address, amount):
if not isinstance(amount, int) or amount <= 0:
raise ValueError("Invalid transfer amount")
if not isinstance(address, str):
raise ValueError("Invalid address type")
transfers.append({"to": address, "amount": amount})
globals_dict["__builtins__"]["transfer_out"] = transfer_out
transfers = []
def transfer_out(address, amount):
if not isinstance(amount, int) or amount <= 0:
raise ValueError("Invalid transfer amount")
if not isinstance(address, str):
raise TypeError("Invalid address type")
if len(address) < 40 or not all(c in '0123456789abcdefABCDEF' for c in address):
raise ValueError("Invalid address format")
transfers.append({"to": address, "amount": amount})
globals_dict["__builtins__"]["transfer_out"] = transfer_out
🧰 Tools
🪛 Ruff (0.15.17)

[warning] 50-50: Missing return type annotation for private function transfer_out

Add return type annotation: None

(ANN202)


[warning] 52-52: Abstract raise to an inner function

(TRY301)


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

(TRY003)


[warning] 54-54: Abstract raise to an inner function

(TRY301)


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

(TRY004)


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

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/contract.py` around lines 48 - 58, The address validation in the
transfer_out function only checks if the address is a string type but does not
validate the actual format of the address. Add additional validation to ensure
the address has a valid format by checking for non-empty strings and any other
format requirements (such as proper hex character format or minimum length).
This validation should occur after the type check for the address parameter and
before appending the transfer to the transfers list.

Comment thread minichain/mempool.py
Comment on lines +65 to +68
def get_transactions_for_block(self):
with self._lock:
# O(1) retrieval! The list is strictly ordered upon insertion.
return list(self._list[:self.transactions_per_block])

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 | 💤 Low value

Comment inaccuracy: slice is O(k), not O(1).

The retrieval is O(k) where k = transactions_per_block, not strictly O(1). This is still efficient and much better than the prior implementation, but the comment is slightly misleading.

📝 Suggested fix
     def get_transactions_for_block(self):
         with self._lock:
-            # O(1) retrieval! The list is strictly ordered upon insertion.
+            # O(k) retrieval where k = transactions_per_block. The list is strictly ordered upon insertion.
             return list(self._list[:self.transactions_per_block])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/mempool.py` around lines 65 - 68, The comment in the
get_transactions_for_block method incorrectly states that the retrieval is O(1),
but the list slicing operation that creates a new list from
self._list[:self.transactions_per_block] is actually O(k) where k equals
transactions_per_block. Update the comment to accurately reflect that this is an
O(k) operation, while noting it remains efficient compared to alternative
approaches.

Comment thread minichain/rpc.py
import logging
import json
import asyncio
from aiohttp import web

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification: locate dependency manifests and check whether aiohttp is declared.
set -euo pipefail

echo "== Candidate manifest files =="
fd -i 'pyproject.toml|requirements.*|setup.py|Pipfile|Pipfile.lock|poetry.lock' -t f

echo
echo "== Occurrences of aiohttp in manifests =="
rg -n --iglob 'pyproject.toml' --iglob 'requirements*' --iglob 'setup.py' --iglob 'Pipfile*' --iglob 'poetry.lock' '\baiohttp\b|install_requires|dependencies'

echo
echo "== aiohttp imports in code =="
rg -n '\bfrom aiohttp import\b|\bimport aiohttp\b'

Repository: StabilityNexus/MiniChain

Length of output: 159


🏁 Script executed:

cat requirements.txt

Repository: StabilityNexus/MiniChain

Length of output: 94


🏁 Script executed:

head -20 minichain/rpc.py

Repository: StabilityNexus/MiniChain

Length of output: 651


🏁 Script executed:

fd -type f -name '*.txt' -o -name '*.toml' -o -name 'setup.py' -o -name 'setup.cfg' | head -20

Repository: StabilityNexus/MiniChain

Length of output: 239


🏁 Script executed:

find . -maxdepth 2 -type f \( -name 'setup.py' -o -name 'setup.cfg' -o -name 'pyproject.toml' \)

Repository: StabilityNexus/MiniChain

Length of output: 50


Add aiohttp to requirements.txt

The code imports aiohttp at line 4 and uses it throughout the file (web.Application, web.AppRunner, web.TCPSite, web.post), but the dependency is not declared in requirements.txt. This will cause ImportError at runtime if aiohttp is not separately installed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/rpc.py` at line 4, The aiohttp module is imported at the top of the
file (from aiohttp import web) and used throughout the code (web.Application,
web.AppRunner, web.TCPSite, web.post), but aiohttp is not declared as a
dependency in the project's requirements.txt file. Add aiohttp to the
requirements.txt file to ensure the dependency is properly declared and
installed when the project is set up, preventing ImportError at runtime.

Comment thread minichain/rpc.py
Comment on lines +17 to +23
async def start(self, host="127.0.0.1", port=8545):
runner = web.AppRunner(self.app)
await runner.setup()
site = web.TCPSite(runner, host, port)
await site.start()
logger.info("🚀 JSON-RPC Server running on http://%s:%d", host, port)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist RPC runner/site and add explicit shutdown API.

At Lines 17-23, AppRunner/TCPSite are local-only, so the server has no reliable stop path. This leaks lifecycle control and makes shutdown coordination fragile.

Suggested fix
 class JSONRPCServer:
     def __init__(self, chain, mempool, network):
         self.chain = chain
         self.mempool = mempool
         self.network = network
         self.app = web.Application()
         self.app.add_routes([web.post('/', self.handle_rpc)])
+        self._runner = None
+        self._site = None

     async def start(self, host="127.0.0.1", port=8545):
-        runner = web.AppRunner(self.app)
-        await runner.setup()
-        site = web.TCPSite(runner, host, port)
-        await site.start()
+        self._runner = web.AppRunner(self.app)
+        await self._runner.setup()
+        self._site = web.TCPSite(self._runner, host, port)
+        await self._site.start()
         logger.info("🚀 JSON-RPC Server running on http://%s:%d", host, port)
+
+    async def stop(self):
+        if self._runner is not None:
+            await self._runner.cleanup()
+            self._runner = None
+            self._site = None
📝 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
async def start(self, host="127.0.0.1", port=8545):
runner = web.AppRunner(self.app)
await runner.setup()
site = web.TCPSite(runner, host, port)
await site.start()
logger.info("🚀 JSON-RPC Server running on http://%s:%d", host, port)
class JSONRPCServer:
def __init__(self, chain, mempool, network):
self.chain = chain
self.mempool = mempool
self.network = network
self.app = web.Application()
self.app.add_routes([web.post('/', self.handle_rpc)])
self._runner = None
self._site = None
async def start(self, host="127.0.0.1", port=8545):
self._runner = web.AppRunner(self.app)
await self._runner.setup()
self._site = web.TCPSite(self._runner, host, port)
await self._site.start()
logger.info("🚀 JSON-RPC Server running on http://%s:%d", host, port)
async def stop(self):
if self._runner is not None:
await self._runner.cleanup()
self._runner = None
self._site = None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/rpc.py` around lines 17 - 23, The `start` method creates `runner`
(AppRunner) and `site` (TCPSite) as local variables that go out of scope when
the method returns, making it impossible to cleanly shutdown the server later.
Store these objects as instance attributes by assigning them to `self.runner`
and `self.site` respectively, then create a new async method (e.g., `stop` or
`shutdown`) that properly cleans up these resources by calling their respective
cleanup methods to ensure proper lifecycle management and prevent resource
leaks.

Comment thread minichain/rpc.py
Comment on lines +40 to +41
if not isinstance(req, dict) or "method" not in req or req.get("jsonrpc") != "2.0":
return {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": req.get("id")}

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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix invalid-request handling crash for non-dict JSON-RPC entries.

At Line 41, req.get("id") is executed even when req is not a dict, which raises before your error response path and returns 500 instead of -32600.

Suggested fix
 async def _process_single(self, req):
-    if not isinstance(req, dict) or "method" not in req or req.get("jsonrpc") != "2.0":
-        return {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": req.get("id")}
+    req_id = req.get("id") if isinstance(req, dict) else None
+    if not isinstance(req, dict) or "method" not in req or req.get("jsonrpc") != "2.0":
+        return {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": req_id}
📝 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
if not isinstance(req, dict) or "method" not in req or req.get("jsonrpc") != "2.0":
return {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": req.get("id")}
req_id = req.get("id") if isinstance(req, dict) else None
if not isinstance(req, dict) or "method" not in req or req.get("jsonrpc") != "2.0":
return {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": req_id}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/rpc.py` around lines 40 - 41, The error response on line 41 calls
`req.get("id")` without verifying that `req` is a dict, but the condition on
line 40 can be true when `req` is not a dict (first part of the OR condition),
causing an AttributeError when non-dict objects don't have a get method. Fix
this by checking if req is a dict before calling `.get("id")`, otherwise use
None as the id value in the error response to safely handle non-dict request
objects.

self.assertEqual(receipt.status, 1)
contract_addr = receipt.contract_address

# Sender sent 100 to contract, plus 10 fee

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Minor comment typo: fee is 1000, not 10.

The comment says "plus 10 fee" but the transaction uses fee=1000.

📝 Suggested fix
-        # Sender sent 100 to contract, plus 10 fee
+        # Sender sent 100 to contract (fee=1000 is separate)
📝 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
# Sender sent 100 to contract, plus 10 fee
# Sender sent 100 to contract (fee=1000 is separate)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_contract_transfers.py` at line 34, Update the comment that states
"plus 10 fee" to reflect the actual fee value of 1000 used in the transaction.
Change the comment text from "Sender sent 100 to contract, plus 10 fee" to
"Sender sent 100 to contract, plus 1000 fee" to match the fee=1000 parameter in
the actual code.

Comment thread tests/test_reorg.py
@@ -0,0 +1,117 @@
import pytest
import os

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove duplicate import os.

os is imported on both line 2 and line 11.

🧹 Suggested fix
 import pytest
 import os
 import json
 import time
 
 from minichain.chain import Blockchain
 from minichain.transaction import Transaction
 from minichain.mempool import Mempool
 
 import sys
-import os
 sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 from main import mine_and_process_block

Also applies to: 10-11

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_reorg.py` at line 2, The import statement for os is duplicated in
the test_reorg.py file, appearing on both line 2 and line 11. Remove one of
these duplicate import os statements, keeping only a single import os
declaration at the top of the file to avoid redundancy and maintain clean import
organization.

@4555jan

4555jan commented Jun 21, 2026

Copy link
Copy Markdown

@SIDDHANTCOOKIE LGTM

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants