Skip to content

Feat/libp2p#111

Merged
SIDDHANTCOOKIE merged 6 commits into
mainfrom
feat/libp2p
Jul 3, 2026
Merged

Feat/libp2p#111
SIDDHANTCOOKIE merged 6 commits into
mainfrom
feat/libp2p

Conversation

@SIDDHANTCOOKIE

@SIDDHANTCOOKIE SIDDHANTCOOKIE commented Jul 3, 2026

Copy link
Copy Markdown
Member

Addressed Issues:

Fixes #(TODO:issue number)

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 peer ban management in the command-line interface, including listing banned peers and banning/unbanning a peer.
    • Introduced adaptive mining difficulty that updates based on recent block times.
  • Bug Fixes

    • Improved block and transaction validation for stricter chain consistency.
    • Reorg handling now preserves and checks difficulty changes more reliably.
    • Mining now automatically uses the block’s configured difficulty when none is provided.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

⚠️ This PR has merge conflicts.

Please resolve the merge conflicts before review.

Your PR will only be reviewed by a maintainer after all conflicts have been resolved.

📺 Watch this video to understand why conflicts occur and how to resolve them:
https://www.youtube.com/watch?v=Sqsz1-o7nXk

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@SIDDHANTCOOKIE, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 80131dc1-5dbb-43f3-a2d3-de4fb1317a4c

📥 Commits

Reviewing files that changed from the base of the PR and between baadfbb and 127b008.

📒 Files selected for processing (1)
  • main.py

Walkthrough

This PR adds EMA-based dynamic difficulty adjustment to the blockchain consensus (genesis config, block validation, reorg handling, mining) using a new ValidationStatus enum for transaction/state validation, and adds a SQLite-backed banned-peers persistence layer with corresponding CLI commands.

Changes

Dynamic Difficulty (EMA) Consensus

Layer / File(s) Summary
ValidationStatus enum and state validation refactor
minichain/validators.py, minichain/state.py
Adds ValidationStatus enum (VALID/INVALID/FAILED/MALFORMED) and refactors verify_transaction_logic, validate_and_apply, and new validate_and_apply_with_status to return status values.
Genesis config and difficulty parameters
genesis.json, minichain/chain.py
Genesis schema adds target_block_time/alpha; genesis block initialization sets current_difficulty and avg_block_time.
add_block difficulty enforcement and EMA update
minichain/chain.py
add_block enforces difficulty equality, validates transactions via status, checks receipts/state_root, and updates EMA-derived difficulty on success.
Reorg difficulty validation
minichain/chain.py
resolve_conflicts tracks temporary difficulty/EMA state, rejects mismatched block difficulty, and commits updated difficulty on success.
Mining and CLI wiring for difficulty
minichain/pow.py, main.py
mine_block defaults difficulty from the block, and CLI mining passes chain.current_difficulty when constructing blocks.
Difficulty tests and updated fixtures
tests/test_difficulty.py, tests/test_persistence.py, tests/test_reorg.py
New EMA difficulty/reorg tests added; existing tests updated to use current_difficulty and revised genesis difficulty.

Peer Banning Persistence and CLI

Layer / File(s) Summary
Banned peers persistence functions
minichain/persistence.py
Adds banned_peers table initializer plus ban_peer, unban_peer, is_peer_banned, get_banned_peers functions.
CLI ban/unban/list-banned commands
main.py
Adds help text and command dispatch for list-banned, ban <peer_id>, unban <peer_id>.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Miner
  participant PoW as mine_block
  participant Blockchain
  participant State
  participant ChainStore as chain/state storage

  Miner->>Blockchain: request current_difficulty
  Blockchain-->>Miner: current_difficulty
  Miner->>PoW: mine_block(block)
  PoW-->>Miner: mined block (nonce, hash)
  Miner->>Blockchain: add_block(block)
  Blockchain->>Blockchain: check block.difficulty == current_difficulty
  Blockchain->>State: validate_and_apply_with_status(tx)
  State-->>Blockchain: ValidationStatus, Receipt
  Blockchain->>Blockchain: verify receipts and state_root
  Blockchain->>Blockchain: update avg_block_time and current_difficulty (EMA)
  Blockchain->>ChainStore: commit state and chain
  Blockchain-->>Miner: ValidationStatus.VALID
Loading
sequenceDiagram
  participant CLI as main.py CLI
  participant Persistence as persistence.py
  participant DB as SQLite banned_peers

  CLI->>Persistence: ban_peer(peer_id, reason)
  Persistence->>DB: INSERT OR REPLACE with timestamp
  CLI->>Persistence: list-banned -> get_banned_peers()
  Persistence->>DB: SELECT ordered by timestamp
  DB-->>Persistence: rows
  Persistence-->>CLI: banned peers list
  CLI->>Persistence: unban_peer(peer_id)
  Persistence->>DB: DELETE peer_id row
Loading

Possibly related PRs

Suggested labels: Python Lang

Suggested reviewers: Zahnentferner

Poem

A rabbit hops through blocks so fine,
Difficulty dances up and down the line. 🐇
Ban a peer, unban it too,
SQLite keeps the list for you.
Hop, hop, VALID! the chain agrees—
Consensus tuned with EMA ease. 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too vague and branch-like; it does not describe the actual changes to difficulty adjustment and peer blacklisting. Replace it with a concise, specific title that names the main change, such as the new difficulty adjustment and banned-peers support.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/libp2p

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.

@SIDDHANTCOOKIE SIDDHANTCOOKIE merged commit 569b728 into main Jul 3, 2026
4 of 5 checks passed

@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: 8

Caution

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

⚠️ Outside diff range comments (1)
minichain/chain.py (1)

133-186: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Keep add_block boolean-compatible, or update every caller to compare against ValidationStatus.VALID. main.py still uses if chain.add_block(...) at multiple sites, so rejected blocks will follow the success path and be broadcast / removed from the mempool. The tests also still use assertTrue(chain.add_block(...)), so they no longer distinguish success from failure.

🤖 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 133 - 186, The add_block flow in chain.py
now returns ValidationStatus instead of a boolean, but existing callers still
treat it as truthy. Either make add_block boolean-compatible again or update
every caller of add_block, including the main.py broadcast/mempool paths and the
tests using assertTrue, to compare explicitly against ValidationStatus.VALID so
rejected blocks don’t follow the success path.
🤖 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 426-455: Banned peers are only being recorded in persistence, but
the P2P connection flow still ignores that state. Update the relevant logic in
minichain/p2p.py to consult is_peer_banned() before accepting or maintaining a
connection, and ensure any banned peer is rejected/disconnected there. Use the
existing ban_peer/unban_peer helpers and the P2P connection-handling code paths
so the ban status is enforced at runtime, not just stored in SQLite.

In `@minichain/persistence.py`:
- Around line 271-283: The ban_peer helper currently writes whatever peer_id and
reason it receives, so add input validation before
_connect/_ensure_banned_peers_table is used. In ban_peer, reject non-string or
empty/whitespace-only peer_id values, validate reason is a string, and enforce a
reasonable maximum length for reason before inserting into banned_peers. Keep
the checks close to ban_peer so the CLI path in main.py cannot create rows with
an empty or malformed peer_id.
- Around line 271-320: The banned-peer persistence helpers are doing blocking
SQLite work directly from the async CLI path, which can stall the event loop.
Update the call sites in cli_loop to run ban_peer, unban_peer, and
get_banned_peers off the loop (for example via an executor or async wrapper),
and keep the synchronous DB helpers in persistence.py as the isolated
implementation behind those async-safe wrappers. Use the ban_peer, unban_peer,
and get_banned_peers symbols to locate the affected flow.
- Around line 266-320: The new banned-peers helpers repeat the same database
setup and teardown logic in ban_peer, unban_peer, is_peer_banned, and
get_banned_peers. Factor the shared db_path creation, _connect call,
_ensure_banned_peers_table invocation, and conn.close handling into a small
shared helper such as a context manager or private function, then update each of
those functions to use it. Keep the existing behavior for missing databases in
the read/delete paths, but centralize the connection lifecycle around the
banned_peers table.
- Around line 260-264: The module-level import for time is misplaced in the
middle of the file near the “Banned Peers (Track 1)” section; move it into the
top import block alongside the existing imports such as os and sqlite3 in
minichain/persistence.py. Keep the import list organized with all standard
library imports together and remove the mid-file import from that section.

In `@tests/test_difficulty.py`:
- Line 52: The result of chain1.resolve_conflicts(chain2.chain) assigns orphans
but the variable is never used, triggering Ruff RUF059. Update the test to avoid
binding the unused value in the test_difficulty.py case, either by ignoring it
directly or only capturing the value that is asserted, keeping the call to
resolve_conflicts and the relevant chain1/chain2 test logic intact.
- Line 19: `Blockchain.add_block()` returns a ValidationStatus enum, so the
current truthiness checks can incorrectly pass for non-VALID outcomes. Update
the assertions in `tests/test_difficulty.py` to compare the returned value from
`chain.add_block(...)` directly against `ValidationStatus.VALID`, using the
existing `chain.add_block` call sites in the difficulty tests.

In `@tests/test_persistence.py`:
- Line 256: The assertion on restored.add_block(block2) is too loose because
assertTrue will pass for any truthy ValidationStatus value, including rejected
states. Update the persistence test to compare the result from
restored.add_block and the ValidationStatus enum explicitly, using
ValidationStatus.VALID as the expected status so the test only passes when the
block is actually accepted.

---

Outside diff comments:
In `@minichain/chain.py`:
- Around line 133-186: The add_block flow in chain.py now returns
ValidationStatus instead of a boolean, but existing callers still treat it as
truthy. Either make add_block boolean-compatible again or update every caller of
add_block, including the main.py broadcast/mempool paths and the tests using
assertTrue, to compare explicitly against ValidationStatus.VALID so rejected
blocks don’t follow the success path.
🪄 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: 0f1cc0c3-c7c8-4060-bccc-a4692824a6d8

📥 Commits

Reviewing files that changed from the base of the PR and between 3ea9909 and baadfbb.

📒 Files selected for processing (10)
  • genesis.json
  • main.py
  • minichain/chain.py
  • minichain/persistence.py
  • minichain/pow.py
  • minichain/state.py
  • minichain/validators.py
  • tests/test_difficulty.py
  • tests/test_persistence.py
  • tests/test_reorg.py

Comment thread main.py
Comment on lines +426 to +455
# ── list-banned ──
elif cmd == "list-banned":
from minichain.persistence import get_banned_peers
banned = get_banned_peers()
if not banned:
print(" No peers are currently banned.")
else:
print(f" {len(banned)} banned peer(s):")
for p in banned:
print(f" - {p['peer_id']} (Reason: {p['reason']}, Time: {p['timestamp']})")

# ── ban ──
elif cmd == "ban":
if len(parts) < 2:
print(" Usage: ban <peer_id>")
continue
peer_id = parts[1]
from minichain.persistence import ban_peer
ban_peer(peer_id, reason="Manual ban via CLI")
print(f" ✅ Peer {peer_id} banned.")

# ── unban ──
elif cmd == "unban":
if len(parts) < 2:
print(" Usage: unban <peer_id>")
continue
peer_id = parts[1]
from minichain.persistence import unban_peer
unban_peer(peer_id)
print(f" ✅ Peer {peer_id} unbanned.")

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm banned-peer checks are wired into the networking layer
rg -nP -C3 '\bis_peer_banned\s*\(' --type=py

Repository: StabilityNexus/MiniChain

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant files =="
git ls-files | rg '(^|/)(main\.py|network\.py|.*persistence.*\.py|.*peer.*\.py)$'

echo
echo "== search for ban-related symbols =="
rg -n -C 3 '\b(ban_peer|unban_peer|get_banned_peers|is_peer_banned|banned peer|banned_peers|ban)\b' --type=py .

echo
echo "== inspect likely networking files =="
for f in $(git ls-files | rg '(^|/)network\.py$|(^|/)persistence\.py$|(^|/)main\.py$'); do
  echo "--- $f ($(wc -l < "$f") lines) ---"
  sed -n '1,260p' "$f" | cat -n
done

Repository: StabilityNexus/MiniChain

Length of output: 28629


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all ban/network references =="
rg -n -C 3 '\b(is_peer_banned|banned_peers|ban_peer|unban_peer|get_banned_peers|disconnect_peer|connect_peer|_broadcast_raw|_unicast_raw|hello|chain_request|chain_response|tx|block)\b' . --type=py

echo
echo "== P2PNetwork definition(s) =="
rg -n -C 4 '\bclass\s+P2PNetwork\b|\bdef\s+(connect_peer|disconnect_peer|add_peer|remove_peer|handle_message|handle_connection|start|stop|listen)\b' . --type=py

echo
echo "== exported symbols around minichain package =="
git ls-files minichain | sort

Repository: StabilityNexus/MiniChain

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== minichain/p2p.py (focused ranges) =="
cat -n minichain/p2p.py | sed -n '1,220p'

echo
echo "== exact ban symbol usage =="
rg -n -C 2 '\bis_peer_banned\b|\bban_peer\b|\bunban_peer\b|\bget_banned_peers\b' minichain main.py tests --type=py

Repository: StabilityNexus/MiniChain

Length of output: 12428


Wire banned-peer checks into the P2P path. ban_peer() only persists to SQLite; minichain/p2p.py never calls is_peer_banned(), so a banned peer can remain connected and reconnect normally.

🤖 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 426 - 455, Banned peers are only being recorded in
persistence, but the P2P connection flow still ignores that state. Update the
relevant logic in minichain/p2p.py to consult is_peer_banned() before accepting
or maintaining a connection, and ensure any banned peer is rejected/disconnected
there. Use the existing ban_peer/unban_peer helpers and the P2P
connection-handling code paths so the ban status is enforced at runtime, not
just stored in SQLite.

Comment thread minichain/persistence.py
Comment on lines +260 to +264
# ---------------------------------------------------------------------------
# Banned Peers (Track 1)
# ---------------------------------------------------------------------------

import time

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.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move import time to the top-level imports.

Placing import time mid-file, inside a new section, is inconsistent with standard module structure and existing import conventions (likely already established at the top of this file for os, sqlite3, etc.).

♻️ Suggested fix
 # ---------------------------------------------------------------------------
 # Banned Peers (Track 1)
 # ---------------------------------------------------------------------------
 
-import time
-
 def _ensure_banned_peers_table(conn: sqlite3.Connection) -> None:

And add import time alongside the other imports at the top of the file.

🤖 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/persistence.py` around lines 260 - 264, The module-level import for
time is misplaced in the middle of the file near the “Banned Peers (Track 1)”
section; move it into the top import block alongside the existing imports such
as os and sqlite3 in minichain/persistence.py. Keep the import list organized
with all standard library imports together and remove the mid-file import from
that section.

Comment thread minichain/persistence.py
Comment on lines +266 to +320
def _ensure_banned_peers_table(conn: sqlite3.Connection) -> None:
conn.execute(
"CREATE TABLE IF NOT EXISTS banned_peers (peer_id TEXT PRIMARY KEY, reason TEXT, timestamp REAL)"
)

def ban_peer(peer_id: str, reason: str, path: str = ".") -> None:
db_path = os.path.join(path, _DB_FILE)
os.makedirs(path, exist_ok=True)
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
with conn:
conn.execute(
"INSERT OR REPLACE INTO banned_peers (peer_id, reason, timestamp) VALUES (?, ?, ?)",
(peer_id, reason, time.time())
)
finally:
conn.close()

def unban_peer(peer_id: str, path: str = ".") -> None:
db_path = os.path.join(path, _DB_FILE)
if not os.path.exists(db_path):
return
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
with conn:
conn.execute("DELETE FROM banned_peers WHERE peer_id = ?", (peer_id,))
finally:
conn.close()

def is_peer_banned(peer_id: str, path: str = ".") -> bool:
db_path = os.path.join(path, _DB_FILE)
if not os.path.exists(db_path):
return False
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
row = conn.execute("SELECT peer_id FROM banned_peers WHERE peer_id = ?", (peer_id,)).fetchone()
return row is not None
finally:
conn.close()

def get_banned_peers(path: str = ".") -> list[dict[str, Any]]:
db_path = os.path.join(path, _DB_FILE)
if not os.path.exists(db_path):
return []
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
rows = conn.execute("SELECT peer_id, reason, timestamp FROM banned_peers ORDER BY timestamp DESC").fetchall()
return [{"peer_id": r["peer_id"], "reason": r["reason"], "timestamp": r["timestamp"]} for r in rows]
finally:
conn.close()

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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Repeated connect/ensure-table/close boilerplate across all four functions.

Each of ban_peer, unban_peer, is_peer_banned, and get_banned_peers repeats the same connect → _ensure_banned_peers_table → try/finally close pattern, along with duplicated os.path.join(path, _DB_FILE) construction. Consider factoring this into a small context manager (e.g., _banned_peers_connection(path)) that yields a ready connection with the table ensured, or a decorator, to reduce duplication and centralize connection handling for this new table.

♻️ Example consolidation
+from contextlib import contextmanager
+
+@contextmanager
+def _banned_peers_conn(path: str, create_if_missing: bool = True):
+    db_path = os.path.join(path, _DB_FILE)
+    if not create_if_missing and not os.path.exists(db_path):
+        yield None
+        return
+    os.makedirs(path, exist_ok=True)
+    conn = _connect(db_path)
+    try:
+        _ensure_banned_peers_table(conn)
+        yield conn
+    finally:
+        conn.close()

Each function can then use this helper, handling the None case for read/delete operations against a nonexistent DB.

🤖 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/persistence.py` around lines 266 - 320, The new banned-peers
helpers repeat the same database setup and teardown logic in ban_peer,
unban_peer, is_peer_banned, and get_banned_peers. Factor the shared db_path
creation, _connect call, _ensure_banned_peers_table invocation, and conn.close
handling into a small shared helper such as a context manager or private
function, then update each of those functions to use it. Keep the existing
behavior for missing databases in the read/delete paths, but centralize the
connection lifecycle around the banned_peers table.

Comment thread minichain/persistence.py
Comment on lines +271 to +283
def ban_peer(peer_id: str, reason: str, path: str = ".") -> None:
db_path = os.path.join(path, _DB_FILE)
os.makedirs(path, exist_ok=True)
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
with conn:
conn.execute(
"INSERT OR REPLACE INTO banned_peers (peer_id, reason, timestamp) VALUES (?, ?, ?)",
(peer_id, reason, time.time())
)
finally:
conn.close()

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.

🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

No validation on peer_id/reason inputs.

ban_peer accepts arbitrary strings without checking for emptiness or type, and there's no upper bound on reason length. Given this is reachable from the CLI (ban <peer_id> in main.py) with user-supplied input, an empty or malformed peer_id would silently create a banned-peer row with an empty primary key.

🤖 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/persistence.py` around lines 271 - 283, The ban_peer helper
currently writes whatever peer_id and reason it receives, so add input
validation before _connect/_ensure_banned_peers_table is used. In ban_peer,
reject non-string or empty/whitespace-only peer_id values, validate reason is a
string, and enforce a reasonable maximum length for reason before inserting into
banned_peers. Keep the checks close to ban_peer so the CLI path in main.py
cannot create rows with an empty or malformed peer_id.

Comment thread minichain/persistence.py
Comment on lines +271 to +320
def ban_peer(peer_id: str, reason: str, path: str = ".") -> None:
db_path = os.path.join(path, _DB_FILE)
os.makedirs(path, exist_ok=True)
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
with conn:
conn.execute(
"INSERT OR REPLACE INTO banned_peers (peer_id, reason, timestamp) VALUES (?, ?, ?)",
(peer_id, reason, time.time())
)
finally:
conn.close()

def unban_peer(peer_id: str, path: str = ".") -> None:
db_path = os.path.join(path, _DB_FILE)
if not os.path.exists(db_path):
return
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
with conn:
conn.execute("DELETE FROM banned_peers WHERE peer_id = ?", (peer_id,))
finally:
conn.close()

def is_peer_banned(peer_id: str, path: str = ".") -> bool:
db_path = os.path.join(path, _DB_FILE)
if not os.path.exists(db_path):
return False
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
row = conn.execute("SELECT peer_id FROM banned_peers WHERE peer_id = ?", (peer_id,)).fetchone()
return row is not None
finally:
conn.close()

def get_banned_peers(path: str = ".") -> list[dict[str, Any]]:
db_path = os.path.join(path, _DB_FILE)
if not os.path.exists(db_path):
return []
conn = _connect(db_path)
try:
_ensure_banned_peers_table(conn)
rows = conn.execute("SELECT peer_id, reason, timestamp FROM banned_peers ORDER BY timestamp DESC").fetchall()
return [{"peer_id": r["peer_id"], "reason": r["reason"], "timestamp": r["timestamp"]} for r in rows]
finally:
conn.close()

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.

🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Synchronous, blocking sqlite calls invoked from async CLI context.

main.py's cli_loop is an async def that awaits input() via an executor, and directly calls ban_peer, unban_peer, and get_banned_peers synchronously inline. Each call opens/closes a fresh SQLite connection and executes a blocking CREATE TABLE IF NOT EXISTS. For a single-user interactive CLI this is low risk, but if this network layer / cli_loop shares an event loop with other async network I/O (peer connections, block broadcasting per the AI summary), these blocking calls will stall the loop until the DB operation completes.

🤖 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/persistence.py` around lines 271 - 320, The banned-peer persistence
helpers are doing blocking SQLite work directly from the async CLI path, which
can stall the event loop. Update the call sites in cli_loop to run ban_peer,
unban_peer, and get_banned_peers off the loop (for example via an executor or
async wrapper), and keep the synchronous DB helpers in persistence.py as the
isolated implementation behind those async-safe wrappers. Use the ban_peer,
unban_peer, and get_banned_peers symbols to locate the affected flow.

Comment thread tests/test_difficulty.py
ts = chain.last_block.timestamp + 1
block1 = Block(index=1, previous_hash=chain.last_block.hash, transactions=[], timestamp=ts, difficulty=chain.current_difficulty, state_root=chain.state.state_root())
mined_block1 = mine_block(block1)
self.assertTrue(chain.add_block(mined_block1))

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ValidationStatus definition/base type and VALID value
rg -n "class ValidationStatus" -A 10 minichain/validators.py

Repository: StabilityNexus/MiniChain

Length of output: 412


Assert ValidationStatus.VALID instead of truthiness. Blockchain.add_block() returns a ValidationStatus Enum with auto() members, so assertTrue(chain.add_block(...)) will pass for INVALID/FAILED too. Compare the result directly against ValidationStatus.VALID in tests/test_difficulty.py:19, 27, 48.

🤖 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_difficulty.py` at line 19, `Blockchain.add_block()` returns a
ValidationStatus enum, so the current truthiness checks can incorrectly pass for
non-VALID outcomes. Update the assertions in `tests/test_difficulty.py` to
compare the returned value from `chain.add_block(...)` directly against
`ValidationStatus.VALID`, using the existing `chain.add_block` call sites in the
difficulty tests.

Comment thread tests/test_difficulty.py
self.assertEqual(chain2.current_difficulty, 2)

# Reorg chain1 to chain2
success, orphans = chain1.resolve_conflicts(chain2.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.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused orphans variable (Ruff RUF059).

🔧 Proposed fix
-        success, orphans = chain1.resolve_conflicts(chain2.chain)
+        success, _orphans = chain1.resolve_conflicts(chain2.chain)
📝 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
success, orphans = chain1.resolve_conflicts(chain2.chain)
success, _orphans = chain1.resolve_conflicts(chain2.chain)
🧰 Tools
🪛 Ruff (0.15.20)

[warning] 52-52: Unpacked variable orphans is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 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_difficulty.py` at line 52, The result of
chain1.resolve_conflicts(chain2.chain) assigns orphans but the variable is never
used, triggering Ruff RUF059. Update the test to avoid binding the unused value
in the test_difficulty.py case, either by ignoring it directly or only capturing
the value that is asserted, keeping the call to resolve_conflicts and the
relevant chain1/chain2 test logic intact.

Source: Linters/SAST tools

Comment thread tests/test_persistence.py
mine_block(block2, difficulty=1)
mine_block(block2)

self.assertTrue(restored.add_block(block2))

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -e
printf 'Repo root: '; pwd
git ls-files | rg '(^tests/test_persistence\.py$|^tests/test_difficulty\.py$|ValidationStatus|add_block)'
echo '--- tests/test_persistence.py around target ---'
sed -n '220,280p' tests/test_persistence.py
echo '--- tests/test_difficulty.py around target ---'
sed -n '1,220p' tests/test_difficulty.py
echo '--- search ValidationStatus ---'
rg -n "class ValidationStatus|ValidationStatus|def add_block|return ValidationStatus|VALID" .

Repository: StabilityNexus/MiniChain

Length of output: 7528


🏁 Script executed:

set -e
echo '--- minichain/validators.py ---'
sed -n '1,120p' minichain/validators.py
echo '--- minichain/chain.py add_block ---'
sed -n '120,195p' minichain/chain.py

Repository: StabilityNexus/MiniChain

Length of output: 4176


Use an explicit status assertion here restored.add_block(block2) returns a ValidationStatus, and assertTrue will accept any enum member, including rejected states. Compare against ValidationStatus.VALID instead.

🤖 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_persistence.py` at line 256, The assertion on
restored.add_block(block2) is too loose because assertTrue will pass for any
truthy ValidationStatus value, including rejected states. Update the persistence
test to compare the result from restored.add_block and the ValidationStatus enum
explicitly, using ValidationStatus.VALID as the expected status so the test only
passes when the block is actually accepted.

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.

2 participants