Skip to content

Feat/peer blacklisting#106

Open
SIDDHANTCOOKIE wants to merge 1 commit into
feat/difficultyfrom
feat/peer-blacklisting
Open

Feat/peer blacklisting#106
SIDDHANTCOOKIE wants to merge 1 commit into
feat/difficultyfrom
feat/peer-blacklisting

Conversation

@SIDDHANTCOOKIE

@SIDDHANTCOOKIE SIDDHANTCOOKIE commented Jun 26, 2026

Copy link
Copy Markdown
Member

Addressed Issues:

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 adjustable block-time and difficulty tuning in chain settings.
    • Introduced peer banning controls, including ban, unban, and list banned peers in the CLI.
    • Mining now uses the block’s current difficulty automatically.
  • Bug Fixes

    • Improved block and transaction validation for stricter consensus checks.
    • Reorgs now preserve updated difficulty settings after chain changes.
    • Added persistence for banned peers across restarts.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d0772ce-56ab-4d82-b8b3-327422ef6d55

📥 Commits

Reviewing files that changed from the base of the PR and between 95f6089 and 824e21a.

📒 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

Walkthrough

Genesis, mining, transaction validation, block acceptance, and reorg handling now use EMA difficulty tracking and validation statuses. SQLite-backed banned-peer storage and CLI commands were added, and related tests and fixtures were updated for the new block difficulty behavior.

Changes

Consensus difficulty and validation

Layer / File(s) Summary
Validation status API
minichain/validators.py, minichain/state.py
ValidationStatus is added and transaction validation returns status values instead of booleans.
Genesis and mining difficulty
genesis.json, minichain/chain.py, minichain/pow.py, main.py, tests/test_persistence.py
Genesis config seeds EMA timing fields, mine_block resolves difficulty from the block when needed, the mining path passes current difficulty, and persistence tests build blocks with updated timestamps and difficulty.
Block acceptance and difficulty update
minichain/chain.py, tests/test_difficulty.py
add_block enforces matching difficulty, receipt equality, and state-root checks before updating EMA difficulty, and the difficulty test covers the fast and slow block timing path.
Reorg validation and EMA carryover
minichain/chain.py, tests/test_difficulty.py, tests/test_reorg.py
resolve_conflicts replays candidate chains with temporary difficulty tracking and status-based transaction validation, and the reorg tests cover difficulty adoption and forged-chain rejection.

Banned peer persistence and CLI

Layer / File(s) Summary
Ban storage helpers
minichain/persistence.py
SQLite-backed banned-peer table and helper functions for ban, unban, lookup, and listing are added.
Ban CLI commands
main.py
CLI help text and command-loop branches add list-banned, ban, and unban commands that call the persistence helpers.

Sequence Diagram(s)

sequenceDiagram
  participant MainPy as "main.py"
  participant MineBlock as "mine_block"
  participant BlockchainAddBlock as "Blockchain.add_block"
  participant StateValidateAndApplyWithStatus as "State.validate_and_apply_with_status"

  MainPy->>MineBlock: build Block with chain.current_difficulty
  MineBlock-->>MainPy: mined Block
  MainPy->>BlockchainAddBlock: submit Block to Blockchain.add_block
  BlockchainAddBlock->>StateValidateAndApplyWithStatus: validate transaction
  StateValidateAndApplyWithStatus-->>BlockchainAddBlock: ValidationStatus + Receipt
  BlockchainAddBlock-->>MainPy: VALID or INVALID
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Python Lang

Suggested reviewers

  • Zahnentferner

Poem

A rabbit hopped through hashes bright,
with EMA paws and blocks in flight.
Ban a peer? Thump! Unban with cheer!
The burrow’s rules are crisp and clear.
I nibble clover, then declare:
“This chain feels neat beyond compare!”

🚥 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 is concise and matches a real major part of the PR: adding peer blacklisting.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/peer-blacklisting

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 changed the base branch from main to feat/difficulty June 26, 2026 22:12

@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 (2)
main.py (1)

95-100: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Compare add_block() against ValidationStatus.VALID. ValidationStatus members are truthy, so INVALID/FAILED/MALFORMED still enter the success path here and can drop txs from the mempool and broadcast a rejected 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 `@main.py` around lines 95 - 100, The success check around add_block() is
treating every ValidationStatus as truthy, so rejected outcomes can still follow
the success path. Update the block-processing flow in main.py to explicitly
compare the result of chain.add_block(mined_block) against
ValidationStatus.VALID, and only perform mempool cleanup and block broadcast
when that exact status is returned. Use the add_block() call and
ValidationStatus enum as the key symbols when locating the fix.
minichain/chain.py (1)

251-258: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reorg replay skips the receipts payload check.

add_block rejects mismatched block.receipts, but resolve_conflicts only validates receipt_root. A heavier chain can therefore be accepted with forged or stale receipt payloads, and self.chain = new_chain_list preserves that bad data.

Suggested fix
                 computed_receipt_root = calculate_receipt_root(receipts)
                 if block.receipt_root != computed_receipt_root:
                     logger.warning("Reorg failed: Invalid receipt root at block %s. Expected %s, got %s", block.index, computed_receipt_root, block.receipt_root)
                     return False, []
+                if [r.to_dict() for r in block.receipts] != [r.to_dict() for r in receipts]:
+                    logger.warning("Reorg failed: Receipts payload mismatch at block %s", block.index)
+                    return False, []

                 if block.state_root != temp_state.state_root():
🤖 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 251 - 258, resolve_conflicts currently only
checks receipt_root, so it can accept a replayed block with tampered or stale
receipts payloads; add an explicit receipts payload validation in the reorg
replay path. Update the validation logic around calculate_receipt_root,
add_block, and resolve_conflicts so the reconstructed receipts list is compared
against block.receipts before accepting the new chain, and reject the reorg if
they do not match.
🤖 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 `@genesis.json`:
- Around line 5-6: The chain identity currently only commits the genesis Block
fields, so peers can share a genesis hash while using different
target_block_time and alpha values and later diverge on difficulty validation.
Update the genesis identity/hash path to include these EMA parameters as part of
the committed genesis configuration, using the existing genesis Block/chain
identity generation flow so any change to target_block_time or alpha produces a
different network identity.

In `@minichain/chain.py`:
- Around line 137-139: The PoW validation in the block validation path only
compares the advertised difficulty and does not verify that block.hash actually
meets the target. Update the validation logic in the block-checking code
(including the current difficulty comparison in Chain’s block validation and the
other referenced validation path) to compute/compare the hash against the
difficulty target, and reject blocks whose hash does not satisfy
block.difficulty even if the header fields are self-consistent.
- Around line 174-181: Reject non-monotonic block timestamps before updating the
EMA in the difficulty adjustment logic. In the block-processing path that uses
self.last_block.timestamp, validate that the new block timestamp is strictly
greater than the previous block’s timestamp and discard/reject the block if not,
so time_diff never goes negative. Apply the same timestamp monotonicity check in
both affected difficulty-update sections (the one around the EMA update and the
corresponding duplicate path) before modifying self.avg_block_time or
self.current_difficulty.
- Around line 131-135: The status handling in chain.py is incorrectly inferred
from the ValueError message text in the validate_block_link_and_hash try/except
path, which misclassifies peer input like invalid index. Update the rejection
logic in the block validation flow around validate_block_link_and_hash to map
exceptions to ValidationStatus explicitly based on the actual failure type or
validation outcome, not by checking substrings in exc. Keep the logger.warning
in place, and ensure all invalid peer-supplied blocks return the appropriate
invalid status consistently.
- Around line 128-152: `Chain.add_block()` is no longer boolean-compatible, but
existing callers like `main.py` still rely on `if chain.add_block(...)`
semantics. Update `add_block()` so it preserves the old truthy/falsey contract
for success versus rejection, or ensure all callers are changed to compare
against `ValidationStatus.VALID`; use the `Chain.add_block` return path and the
`ValidationStatus` enum as the key points to fix.

In `@minichain/persistence.py`:
- Around line 285-288: `unban_peer()` and `get_banned_peers()` currently treat a
missing `data.db` as a successful no-op, which makes callers like `main.py`
report misleading success. Update the missing-storage path in these persistence
helpers to return an explicit status or raise a dedicated error instead of
silently returning an empty result, and then adjust the `main.py` caller
handling so it can distinguish “storage missing” from “peer unbanned” or “no
banned peers.” Focus on the `unban_peer` and `get_banned_peers` functions and
their call sites.

In `@minichain/state.py`:
- Around line 108-113: validate_and_apply_with_status() is double-running
transaction validation because it calls verify_transaction_logic(tx) and then
apply_transaction(tx), which verifies again. Refactor State by extracting the
mutation path into a prevalidated helper (for example, a private apply_* method
used by both apply_transaction and validate_and_apply_with_status) so
signature/logic checks happen only once. Update the acceptance/reorg replay flow
to use the prevalidated helper, and keep verify_transaction_logic as the single
validation entrypoint.
- Around line 57-60: The transaction validation in state.py is using tx.fee in
consensus accounting without first checking that it is present and non-negative.
Update the validation path around the balance check in the
transaction-processing logic (including the code that computes total_cost and
later gas_used) to reject any tx with a missing, non-numeric, or negative fee
before it can affect balances or rewards. Use the existing transaction
validation flow and symbols like tx.amount, getattr(tx, 'fee', 0), and the
validation methods around the referenced sections to apply the same fee guard
consistently wherever fee is consumed.

---

Outside diff comments:
In `@main.py`:
- Around line 95-100: The success check around add_block() is treating every
ValidationStatus as truthy, so rejected outcomes can still follow the success
path. Update the block-processing flow in main.py to explicitly compare the
result of chain.add_block(mined_block) against ValidationStatus.VALID, and only
perform mempool cleanup and block broadcast when that exact status is returned.
Use the add_block() call and ValidationStatus enum as the key symbols when
locating the fix.

In `@minichain/chain.py`:
- Around line 251-258: resolve_conflicts currently only checks receipt_root, so
it can accept a replayed block with tampered or stale receipts payloads; add an
explicit receipts payload validation in the reorg replay path. Update the
validation logic around calculate_receipt_root, add_block, and resolve_conflicts
so the reconstructed receipts list is compared against block.receipts before
accepting the new chain, and reject the reorg if they do not match.
🪄 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: 3d0772ce-56ab-4d82-b8b3-327422ef6d55

📥 Commits

Reviewing files that changed from the base of the PR and between 95f6089 and 824e21a.

📒 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

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

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 (2)
main.py (1)

95-100: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Compare add_block() against ValidationStatus.VALID. ValidationStatus members are truthy, so INVALID/FAILED/MALFORMED still enter the success path here and can drop txs from the mempool and broadcast a rejected 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 `@main.py` around lines 95 - 100, The success check around add_block() is
treating every ValidationStatus as truthy, so rejected outcomes can still follow
the success path. Update the block-processing flow in main.py to explicitly
compare the result of chain.add_block(mined_block) against
ValidationStatus.VALID, and only perform mempool cleanup and block broadcast
when that exact status is returned. Use the add_block() call and
ValidationStatus enum as the key symbols when locating the fix.
minichain/chain.py (1)

251-258: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reorg replay skips the receipts payload check.

add_block rejects mismatched block.receipts, but resolve_conflicts only validates receipt_root. A heavier chain can therefore be accepted with forged or stale receipt payloads, and self.chain = new_chain_list preserves that bad data.

Suggested fix
                 computed_receipt_root = calculate_receipt_root(receipts)
                 if block.receipt_root != computed_receipt_root:
                     logger.warning("Reorg failed: Invalid receipt root at block %s. Expected %s, got %s", block.index, computed_receipt_root, block.receipt_root)
                     return False, []
+                if [r.to_dict() for r in block.receipts] != [r.to_dict() for r in receipts]:
+                    logger.warning("Reorg failed: Receipts payload mismatch at block %s", block.index)
+                    return False, []

                 if block.state_root != temp_state.state_root():
🤖 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 251 - 258, resolve_conflicts currently only
checks receipt_root, so it can accept a replayed block with tampered or stale
receipts payloads; add an explicit receipts payload validation in the reorg
replay path. Update the validation logic around calculate_receipt_root,
add_block, and resolve_conflicts so the reconstructed receipts list is compared
against block.receipts before accepting the new chain, and reject the reorg if
they do not match.
🤖 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 `@genesis.json`:
- Around line 5-6: The chain identity currently only commits the genesis Block
fields, so peers can share a genesis hash while using different
target_block_time and alpha values and later diverge on difficulty validation.
Update the genesis identity/hash path to include these EMA parameters as part of
the committed genesis configuration, using the existing genesis Block/chain
identity generation flow so any change to target_block_time or alpha produces a
different network identity.

In `@minichain/chain.py`:
- Around line 137-139: The PoW validation in the block validation path only
compares the advertised difficulty and does not verify that block.hash actually
meets the target. Update the validation logic in the block-checking code
(including the current difficulty comparison in Chain’s block validation and the
other referenced validation path) to compute/compare the hash against the
difficulty target, and reject blocks whose hash does not satisfy
block.difficulty even if the header fields are self-consistent.
- Around line 174-181: Reject non-monotonic block timestamps before updating the
EMA in the difficulty adjustment logic. In the block-processing path that uses
self.last_block.timestamp, validate that the new block timestamp is strictly
greater than the previous block’s timestamp and discard/reject the block if not,
so time_diff never goes negative. Apply the same timestamp monotonicity check in
both affected difficulty-update sections (the one around the EMA update and the
corresponding duplicate path) before modifying self.avg_block_time or
self.current_difficulty.
- Around line 131-135: The status handling in chain.py is incorrectly inferred
from the ValueError message text in the validate_block_link_and_hash try/except
path, which misclassifies peer input like invalid index. Update the rejection
logic in the block validation flow around validate_block_link_and_hash to map
exceptions to ValidationStatus explicitly based on the actual failure type or
validation outcome, not by checking substrings in exc. Keep the logger.warning
in place, and ensure all invalid peer-supplied blocks return the appropriate
invalid status consistently.
- Around line 128-152: `Chain.add_block()` is no longer boolean-compatible, but
existing callers like `main.py` still rely on `if chain.add_block(...)`
semantics. Update `add_block()` so it preserves the old truthy/falsey contract
for success versus rejection, or ensure all callers are changed to compare
against `ValidationStatus.VALID`; use the `Chain.add_block` return path and the
`ValidationStatus` enum as the key points to fix.

In `@minichain/persistence.py`:
- Around line 285-288: `unban_peer()` and `get_banned_peers()` currently treat a
missing `data.db` as a successful no-op, which makes callers like `main.py`
report misleading success. Update the missing-storage path in these persistence
helpers to return an explicit status or raise a dedicated error instead of
silently returning an empty result, and then adjust the `main.py` caller
handling so it can distinguish “storage missing” from “peer unbanned” or “no
banned peers.” Focus on the `unban_peer` and `get_banned_peers` functions and
their call sites.

In `@minichain/state.py`:
- Around line 108-113: validate_and_apply_with_status() is double-running
transaction validation because it calls verify_transaction_logic(tx) and then
apply_transaction(tx), which verifies again. Refactor State by extracting the
mutation path into a prevalidated helper (for example, a private apply_* method
used by both apply_transaction and validate_and_apply_with_status) so
signature/logic checks happen only once. Update the acceptance/reorg replay flow
to use the prevalidated helper, and keep verify_transaction_logic as the single
validation entrypoint.
- Around line 57-60: The transaction validation in state.py is using tx.fee in
consensus accounting without first checking that it is present and non-negative.
Update the validation path around the balance check in the
transaction-processing logic (including the code that computes total_cost and
later gas_used) to reject any tx with a missing, non-numeric, or negative fee
before it can affect balances or rewards. Use the existing transaction
validation flow and symbols like tx.amount, getattr(tx, 'fee', 0), and the
validation methods around the referenced sections to apply the same fee guard
consistently wherever fee is consumed.

---

Outside diff comments:
In `@main.py`:
- Around line 95-100: The success check around add_block() is treating every
ValidationStatus as truthy, so rejected outcomes can still follow the success
path. Update the block-processing flow in main.py to explicitly compare the
result of chain.add_block(mined_block) against ValidationStatus.VALID, and only
perform mempool cleanup and block broadcast when that exact status is returned.
Use the add_block() call and ValidationStatus enum as the key symbols when
locating the fix.

In `@minichain/chain.py`:
- Around line 251-258: resolve_conflicts currently only checks receipt_root, so
it can accept a replayed block with tampered or stale receipts payloads; add an
explicit receipts payload validation in the reorg replay path. Update the
validation logic around calculate_receipt_root, add_block, and resolve_conflicts
so the reconstructed receipts list is compared against block.receipts before
accepting the new chain, and reject the reorg if they do not match.
🪄 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: 3d0772ce-56ab-4d82-b8b3-327422ef6d55

📥 Commits

Reviewing files that changed from the base of the PR and between 95f6089 and 824e21a.

📒 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
🛑 Comments failed to post (8)
genesis.json (1)

5-6: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Commit EMA parameters to the chain identity.

target_block_time and alpha now affect difficulty validation, but the provided genesis hash path only hashes the genesis Block fields. Peers can therefore share a genesis hash while using different EMA parameters and later disagree on block validity.

🤖 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 `@genesis.json` around lines 5 - 6, The chain identity currently only commits
the genesis Block fields, so peers can share a genesis hash while using
different target_block_time and alpha values and later diverge on difficulty
validation. Update the genesis identity/hash path to include these EMA
parameters as part of the committed genesis configuration, using the existing
genesis Block/chain identity generation flow so any change to target_block_time
or alpha produces a different network identity.
minichain/chain.py (4)

128-152: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 '\badd_block\s*\(' main.py tests minichain

Repository: StabilityNexus/MiniChain

Length of output: 3583


Keep add_block() boolean-compatible main.py still uses if chain.add_block(...), so this return-type change breaks the existing contract unless every caller switches to an explicit == ValidationStatus.VALID check. Otherwise rejected blocks can follow the success path and their transactions can be dropped from the mempool.

🤖 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 128 - 152, `Chain.add_block()` is no longer
boolean-compatible, but existing callers like `main.py` still rely on `if
chain.add_block(...)` semantics. Update `add_block()` so it preserves the old
truthy/falsey contract for success versus rejection, or ensure all callers are
changed to compare against `ValidationStatus.VALID`; use the `Chain.add_block`
return path and the `ValidationStatus` enum as the key points to fix.

131-135: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not derive block status from exception text.

"invalid index" currently returns FAILED only because the message does not contain "hash". That is still invalid peer input, and any retry/blacklist logic built on these statuses will misclassify it.

Suggested fix
             try:
                 validate_block_link_and_hash(self.last_block, block)
             except ValueError as exc:
                 logger.warning("Block %s rejected: %s", block.index, exc)
-                return ValidationStatus.INVALID if "hash" in str(exc) else ValidationStatus.FAILED
+                return ValidationStatus.INVALID
📝 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.

            try:
                validate_block_link_and_hash(self.last_block, block)
            except ValueError as exc:
                logger.warning("Block %s rejected: %s", block.index, exc)
                return ValidationStatus.INVALID
🤖 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 131 - 135, The status handling in chain.py
is incorrectly inferred from the ValueError message text in the
validate_block_link_and_hash try/except path, which misclassifies peer input
like invalid index. Update the rejection logic in the block validation flow
around validate_block_link_and_hash to map exceptions to ValidationStatus
explicitly based on the actual failure type or validation outcome, not by
checking substrings in exc. Keep the logger.warning in place, and ensure all
invalid peer-supplied blocks return the appropriate invalid status consistently.

137-139: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Validate the PoW target, not just the difficulty field.

Both paths only compare the advertised difficulty value. Neither verifies that block.hash actually satisfies block.difficulty, so a peer can submit any self-consistent header and bypass mining entirely.

Suggested fix
+            if not block.hash.startswith("0" * block.difficulty):
+                logger.warning(
+                    "Block %s rejected: hash does not satisfy difficulty %s",
+                    block.index,
+                    block.difficulty,
+                )
+                return ValidationStatus.INVALID
+
             if block.difficulty != self.current_difficulty:
                 logger.warning("Block %s rejected: Invalid difficulty. Expected %s, got %s", block.index, self.current_difficulty, block.difficulty)
                 return ValidationStatus.INVALID

Also applies to: 228-230

🤖 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 137 - 139, The PoW validation in the block
validation path only compares the advertised difficulty and does not verify that
block.hash actually meets the target. Update the validation logic in the
block-checking code (including the current difficulty comparison in Chain’s
block validation and the other referenced validation path) to compute/compare
the hash against the difficulty target, and reject blocks whose hash does not
satisfy block.difficulty even if the header fields are self-consistent.

174-181: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject non-monotonic timestamps before feeding the EMA.

time_diff can go negative here. With the current millisecond genesis timestamps, a child block built from int(time.time()) seconds is still accepted and produces a huge negative delta, letting peers skew difficulty arbitrarily.

Suggested fix
+            if block.timestamp <= self.last_block.timestamp:
+                logger.warning(
+                    "Block %s rejected: non-monotonic timestamp %s <= %s",
+                    block.index,
+                    block.timestamp,
+                    self.last_block.timestamp,
+                )
+                return ValidationStatus.INVALID
+
             # Update EMA difficulty state
             time_diff = block.timestamp - self.last_block.timestamp
             self.avg_block_time = self.alpha * time_diff + (1 - self.alpha) * self.avg_block_time

Also applies to: 260-266

🤖 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 174 - 181, Reject non-monotonic block
timestamps before updating the EMA in the difficulty adjustment logic. In the
block-processing path that uses self.last_block.timestamp, validate that the new
block timestamp is strictly greater than the previous block’s timestamp and
discard/reject the block if not, so time_diff never goes negative. Apply the
same timestamp monotonicity check in both affected difficulty-update sections
(the one around the EMA update and the corresponding duplicate path) before
modifying self.avg_block_time or self.current_difficulty.
minichain/persistence.py (1)

285-288: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Differentiate “missing storage” from a successful no-op.

unban_peer() returns silently when data.db is missing, and get_banned_peers() maps the same condition to []. In main.py, that becomes “peer unbanned” / “none”, so operators get a success-looking result even when no blacklist state was read or changed. Please return an explicit status (or raise) for the missing-DB case so callers can report it correctly.

Also applies to: 309-312

🤖 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 285 - 288, `unban_peer()` and
`get_banned_peers()` currently treat a missing `data.db` as a successful no-op,
which makes callers like `main.py` report misleading success. Update the
missing-storage path in these persistence helpers to return an explicit status
or raise a dedicated error instead of silently returning an empty result, and
then adjust the `main.py` caller handling so it can distinguish “storage
missing” from “peer unbanned” or “no banned peers.” Focus on the `unban_peer`
and `get_banned_peers` functions and their call sites.
minichain/state.py (2)

57-60: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Validate fee before using it in consensus accounting.

fee is added to total_cost and later used as gas_used, but only amount is checked. A negative fee can make total_cost smaller than amount and corrupt balances/rewards.

Proposed fix
         from .validators import ValidationStatus
+        fee = getattr(tx, 'fee', 0)
+        if not isinstance(tx.amount, int) or tx.amount < 0 or not isinstance(fee, int) or fee < 0:
+            return ValidationStatus.MALFORMED
         if not tx.verify():
             logger.error("Error: Invalid signature for tx from %s...", tx.sender[:8])
             return ValidationStatus.INVALID
@@
-        total_cost = tx.amount + getattr(tx, 'fee', 0)
+        total_cost = tx.amount + fee
@@
-        if not isinstance(tx.amount, int) or tx.amount < 0:
+        fee = getattr(tx, 'fee', 0)
+        if not isinstance(tx.amount, int) or tx.amount < 0 or not isinstance(fee, int) or fee < 0:
             return None
@@
-        if not isinstance(tx.amount, int) or tx.amount < 0:
+        fee = getattr(tx, 'fee', 0)
+        if not isinstance(tx.amount, int) or tx.amount < 0 or not isinstance(fee, int) or fee < 0:
             return ValidationStatus.MALFORMED, None

Also applies to: 94-106, 120-122

🤖 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/state.py` around lines 57 - 60, The transaction validation in
state.py is using tx.fee in consensus accounting without first checking that it
is present and non-negative. Update the validation path around the balance check
in the transaction-processing logic (including the code that computes total_cost
and later gas_used) to reject any tx with a missing, non-numeric, or negative
fee before it can affect balances or rewards. Use the existing transaction
validation flow and symbols like tx.amount, getattr(tx, 'fee', 0), and the
validation methods around the referenced sections to apply the same fee guard
consistently wherever fee is consumed.

108-113: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid validating the same transaction twice.

validate_and_apply_with_status() verifies the transaction, then apply_transaction() verifies it again. Split the mutation logic into a prevalidated helper so block acceptance/reorg replay do not double-run signature validation.

Also applies to: 120-122

🤖 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/state.py` around lines 108 - 113, validate_and_apply_with_status()
is double-running transaction validation because it calls
verify_transaction_logic(tx) and then apply_transaction(tx), which verifies
again. Refactor State by extracting the mutation path into a prevalidated helper
(for example, a private apply_* method used by both apply_transaction and
validate_and_apply_with_status) so signature/logic checks happen only once.
Update the acceptance/reorg replay flow to use the prevalidated helper, and keep
verify_transaction_logic as the single validation entrypoint.

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.

1 participant