Skip to content

refactor: change PoW difficulty to numeric target hash threshold - #133

Open
SIDDHANTCOOKIE wants to merge 5 commits into
mainfrom
target-hash-threshold
Open

refactor: change PoW difficulty to numeric target hash threshold#133
SIDDHANTCOOKIE wants to merge 5 commits into
mainfrom
target-hash-threshold

Conversation

@SIDDHANTCOOKIE

@SIDDHANTCOOKIE SIDDHANTCOOKIE commented Jul 29, 2026

Copy link
Copy Markdown
Member

Addressed Issues:

Replaces the leading-zero difficulty mechanic with a granular target hash threshold for Proof of Work validation.

  • Validates PoW via int(hash, 16) < target
  • Adjusts target smoothly by +/- 1 based on average block time
  • Defines MAX_TARGET in network_config.py for modularity
  • Updates all tests and JSON representations to use the new target threshold

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

    • Proof-of-work now uses numeric target thresholds with automatic adjustment.
    • Genesis configurations support an initial mining target.
    • Mining begins from configurable nonce ranges.
    • Block serialization consistently represents proof-of-work targets.
  • Bug Fixes

    • Strengthened chain validation during block addition and reorganization.
    • Added stricter receipt and target consistency checks.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR replaces difficulty-based proof of work with bounded numeric targets across block serialization, mining, validation, retargeting, genesis configuration, and chain reorganization. Tests and mining configuration now use the target model.

Changes

Target-Based Proof-of-Work Migration

Layer / File(s) Summary
Target block and genesis contracts
minichain/block.py, minichain/network_config.py, minichain/chain.py, genesis.json
Block headers and payloads use target. Genesis parsing validates bounded numeric or hexadecimal targets.
Target mining and block validation
minichain/pow.py, minichain/node_config.py, main.py, minichain/chain.py
Mining accepts numeric targets, starts from a configurable random nonce, and accepts hashes below the target.
Target work, retargeting, and reorganization
minichain/chain.py
Cumulative work, EMA updates, block application, chain addition, and reorganization propagate target state.
Target integration coverage
tests/test_core.py, tests/test_persistence*.py, tests/test_protocol_hardening.py, tests/test_reorg.py, tests/test_serialization.py, tests/test_target.py
Tests update target-based fixtures and verify persistence, serialization, protocol validation, reorganization, and EMA target changes.

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

Sequence Diagram(s)

sequenceDiagram
  participant main.py
  participant mine_block
  participant Blockchain
  main.py->>mine_block: pass current_target
  mine_block->>mine_block: find hash below target
  mine_block-->>main.py: return mined block
  main.py->>Blockchain: add block
  Blockchain->>Blockchain: validate and update target state
Loading

Possibly related PRs

Suggested labels: Python Lang, Documentation

Suggested reviewers: zahnentferner

Poem

I hop through targets, crisp and bright,
Hashes fall below the line just right.
Blocks carry fields both clear and true,
Chains retarget as rabbits do.
Tests keep watch from dawn till night.

🚥 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 clearly and concisely describes the main change from difficulty-based PoW to a numeric target hash threshold.
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 target-hash-threshold

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.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

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

🤖 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 `@minichain/chain.py`:
- Around line 30-31: Enforce target validation at both consensus entry points in
minichain/chain.py: lines 30-31, validate that block.target is an integer within
the valid range before comparing the hash; lines 90-95, validate the genesis
target before assigning self.current_target. Reject non-integer, non-positive,
or MAX_TARGET-exceeding targets consistently at both sites.

In `@minichain/pow.py`:
- Around line 26-31: Update the mining flow around target selection and
block.to_header_dict() so an override target is committed to the mined
block/header before returning success. Ensure the hash is computed against the
same target that validation will inspect; alternatively reject overrides that
differ from block.target before mining.

In `@tests/test_persistence_runtime.py`:
- Around line 74-79: Update the block construction and mining call in the
persistence runtime test to use the chain’s configured target from the
Blockchain instance instead of hard-coded int("F"*64, 16) values. Reuse the
target exposed by bc so the test block matches the target enforced by
bc.add_block().

In `@tests/test_target.py`:
- Around line 13-14: Keep genesis block hashes consistent with their targets in
tests/test_target.py at lines 13-14, 37-38, and 44-45: initialize each chain
from target-configured genesis data or recompute the corresponding genesis block
hash after changing its target, applying the same correction to the chains in
each site.
- Line 54: Update the test call to chain1.resolve_conflicts so it does not bind
the unused orphan result: unpack it into success and _, unless the test contract
requires verifying that the orphan list is empty, in which case assert that
condition explicitly.
🪄 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 Plus

Run ID: 68b9b698-3641-49e3-a618-bca450af0881

📥 Commits

Reviewing files that changed from the base of the PR and between 20d148e and 99da332.

📒 Files selected for processing (13)
  • genesis.json
  • main.py
  • minichain/block.py
  • minichain/chain.py
  • minichain/network_config.py
  • minichain/pow.py
  • tests/test_core.py
  • tests/test_persistence.py
  • tests/test_persistence_runtime.py
  • tests/test_protocol_hardening.py
  • tests/test_reorg.py
  • tests/test_serialization.py
  • tests/test_target.py

Comment thread minichain/chain.py Outdated
Comment thread tests/test_persistence_runtime.py Outdated
Comment thread tests/test_target.py Outdated
Comment thread tests/test_target.py Outdated

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

🤖 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 `@minichain/chain.py`:
- Around line 96-102: In minichain/chain.py lines 96-102, update target parsing
to accept only non-boolean integers or valid hexadecimal strings, reject
booleans and floats without coercion, and catch malformed conversion values
before logging and exiting. In minichain/chain.py lines 30-34, update the
received block target validation to explicitly exclude booleans while preserving
non-boolean integer validation and existing bounds checks.

In `@minichain/pow.py`:
- Around line 26-32: Keep the target override local throughout the mining
operation and assign it to block.target only after mining succeeds; do not
mutate the input before timeout, cancellation, or max-nonce failures. Extend
target validation to reject values above MAX_TARGET while preserving the
positive-integer requirement. Ensure header construction and hashing use the
validated candidate target, and leave block.target unchanged on every failure
path.

In `@minichain/state.py`:
- Around line 189-204: Wrap the journaled transaction execution in the relevant
state-processing method, including the sender debit, nonce increment, and
downstream execution, with exception handling that calls the existing
rollback_and_refund() and restores original_accounts before propagating the
exception or creating a failure receipt. Ensure no unexpected exception leaves
self.accounts pointing to the StateJournal.
🪄 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 Plus

Run ID: 658aca11-589d-42c0-8ab5-d512605d902d

📥 Commits

Reviewing files that changed from the base of the PR and between 99da332 and eb58853.

📒 Files selected for processing (5)
  • minichain/chain.py
  • minichain/pow.py
  • minichain/state.py
  • tests/test_persistence_runtime.py
  • tests/test_target.py

Comment thread minichain/chain.py Outdated
Comment on lines +96 to +102
raw_target = config.get("target")
if raw_target is not None:
self.current_target = int(raw_target, 16) if isinstance(raw_target, str) else int(raw_target)
from .network_config import MAX_TARGET
if not isinstance(self.current_target, int) or self.current_target <= 0 or self.current_target > MAX_TARGET:
logger.error("Genesis target out of bounds: %s", self.current_target)
sys.exit(1)

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

Parse protocol targets strictly before validating bounds. int(raw_target) silently converts values such as true and 1.9, while isinstance(block.target, int) also accepts True. Restrict targets to non-boolean integers or valid hex strings, and handle conversion failures consistently.

  • minichain/chain.py#L96-L102: reject booleans/floats instead of coercing them, and catch malformed hex or numeric values before exiting.
  • minichain/chain.py#L30-L34: explicitly reject booleans when validating a received block target.
📍 Affects 1 file
  • minichain/chain.py#L96-L102 (this comment)
  • minichain/chain.py#L30-L34
🤖 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 96 - 102, In minichain/chain.py lines
96-102, update target parsing to accept only non-boolean integers or valid
hexadecimal strings, reject booleans and floats without coercion, and catch
malformed conversion values before logging and exiting. In minichain/chain.py
lines 30-34, update the received block target validation to explicitly exclude
booleans while preserving non-boolean integer validation and existing bounds
checks.

Comment thread minichain/pow.py
Comment on lines +26 to 32
target = target if target is not None else block.target
if not isinstance(target, int) or target <= 0:
raise ValueError("Target must be a positive integer.")
block.target = target

target = "0" * difficulty
local_nonce = 0
header_dict = block.to_header_dict() # Construct header dict once outside loop

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

Keep target overrides atomic and consensus-valid.

Line 29 mutates the input before timeout/cancellation/max-nonce failure, contradicting the documented contract; a later retry can mine with the stale override. Also reject targets above MAX_TARGET, otherwise mining can succeed for a block add_block must reject.

Proposed fix
 def mine_block(...):
+    from .network_config import MAX_TARGET
+
     target = target if target is not None else block.target
-    if not isinstance(target, int) or target <= 0:
-        raise ValueError("Target must be a positive integer.")
-    block.target = target
+    if isinstance(target, bool) or not isinstance(target, int) or not 0 < target <= MAX_TARGET:
+        raise ValueError("Target must be an integer within consensus bounds.")
 
     local_nonce = 0
-    header_dict = block.to_header_dict()
+    header_dict = block.to_header_dict()
+    header_dict["target"] = hex(target)
 ...
     if int(block_hash, 16) < target:
+        block.target = target
         block.nonce = local_nonce
📝 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
target = target if target is not None else block.target
if not isinstance(target, int) or target <= 0:
raise ValueError("Target must be a positive integer.")
block.target = target
target = "0" * difficulty
local_nonce = 0
header_dict = block.to_header_dict() # Construct header dict once outside loop
from .network_config import MAX_TARGET
target = target if target is not None else block.target
if isinstance(target, bool) or not isinstance(target, int) or not 0 < target <= MAX_TARGET:
raise ValueError("Target must be an integer within consensus bounds.")
local_nonce = 0
header_dict = block.to_header_dict()
header_dict["target"] = hex(target) # Construct header dict once outside loop
🧰 Tools
🪛 Ruff (0.16.0)

[warning] 28-28: 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/pow.py` around lines 26 - 32, Keep the target override local
throughout the mining operation and assign it to block.target only after mining
succeeds; do not mutate the input before timeout, cancellation, or max-nonce
failures. Extend target validation to reject values above MAX_TARGET while
preserving the positive-integer requirement. Ensure header construction and
hashing use the validated candidate target, and leave block.target unchanged on
every failure path.

Comment thread minichain/state.py Outdated
Comment on lines +189 to +204
original_accounts = self.accounts
journal = StateJournal(original_accounts)
self.accounts = journal

sender = self.accounts[tx.sender]
total_cost = tx.amount + (getattr(tx, 'gas_limit', 0) * getattr(tx, 'fee_per_gas', 0))

sender['balance'] -= total_cost
sender['nonce'] += 1

import copy
state_snapshot = copy.deepcopy(self.accounts)

def rollback_and_refund(error_message, gas_used):
self.accounts = copy.deepcopy(state_snapshot)
journal.rollback()
self.accounts = original_accounts
refund_acc = self.accounts[tx.sender]
refund_acc['balance'] += tx.amount
gas_refund = getattr(tx, 'gas_limit', 0) - gas_used
if gas_refund > 0:
refund_acc['balance'] += (gas_refund * getattr(tx, 'fee_per_gas', 0))
refund_acc['balance'] -= (gas_used * getattr(tx, 'fee_per_gas', 0))
refund_acc['nonce'] += 1

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore state when execution raises unexpectedly.

An exception after Lines 193-197 bypasses rollback_and_refund(), leaving self.accounts set to journal; its cached sender debit and nonce increment remain visible despite the failed transaction. Wrap the journaled body in exception handling that rolls back and restores original_accounts before propagating or converting the failure to a receipt.

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 199-199: Missing return type annotation for private function rollback_and_refund

(ANN202)

🤖 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 189 - 204, Wrap the journaled transaction
execution in the relevant state-processing method, including the sender debit,
nonce increment, and downstream execution, with exception handling that calls
the existing rollback_and_refund() and restores original_accounts before
propagating the exception or creating a failure receipt. Ensure no unexpected
exception leaves self.accounts pointing to the StateJournal.

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

Caution

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

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

36-51: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Bound the full nonce search window to the configured range.

The config comment states that MINING_INITIAL_NONCE_MIN and MINING_INITIAL_NONCE_MAX can create unique non-overlapping ranges, but local_nonce can reach seed + MINING_MAX_NONCE - 1. That can hash outside the configured initial range and reopen duplicate work. Enforce the upper bound on the search window instead of only on start_nonce.

🤖 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/pow.py` around lines 36 - 51, Update the nonce-bound logic in the
mining loop to cap the effective search window at MINING_INITIAL_NONCE_MAX,
rather than allowing local_nonce to advance beyond the configured initial range
via max_nonce. Preserve the existing start_nonce-based limit while ensuring
hashing never occurs outside the selected non-overlapping range.
🤖 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 `@minichain/pow.py`:
- Line 2: Update the random starting-point generation in the PoW logic around
the random import and lines 36–37 to use secrets.randbelow when unpredictability
is required, replacing random.randint and its import. If unpredictability is
intentionally not required, retain the existing source but add a narrowly scoped
Ruff S311 suppression with a rationale and document that it only reduces
overlap.

---

Outside diff comments:
In `@minichain/pow.py`:
- Around line 36-51: Update the nonce-bound logic in the mining loop to cap the
effective search window at MINING_INITIAL_NONCE_MAX, rather than allowing
local_nonce to advance beyond the configured initial range via max_nonce.
Preserve the existing start_nonce-based limit while ensuring hashing never
occurs outside the selected non-overlapping range.
🪄 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 Plus

Run ID: 6d39914d-a3b6-43a5-987c-217251f87b74

📥 Commits

Reviewing files that changed from the base of the PR and between eb58853 and 0d90055.

📒 Files selected for processing (3)
  • Roadmap.md
  • minichain/node_config.py
  • minichain/pow.py
💤 Files with no reviewable changes (1)
  • Roadmap.md

Comment thread minichain/pow.py
@@ -1,6 +1,11 @@
import time
import random

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

Resolve the random-source contract and Ruff S311 finding.

random.randint is predictable. This does not invalidate a PoW hash, but it does not provide an unpredictable starting point for the overlap-reduction goal. If unpredictability is required, use secrets.randbelow. Otherwise, suppress S311 with a rationale and document that this only reduces overlap.

Proposed fix
-import random
+import secrets
...
-    start_nonce = random.randint(MINING_INITIAL_NONCE_MIN, MINING_INITIAL_NONCE_MAX)
+    start_nonce = MINING_INITIAL_NONCE_MIN + secrets.randbelow(
+        MINING_INITIAL_NONCE_MAX - MINING_INITIAL_NONCE_MIN + 1
+    )

Also applies to: 36-37

🤖 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/pow.py` at line 2, Update the random starting-point generation in
the PoW logic around the random import and lines 36–37 to use secrets.randbelow
when unpredictability is required, replacing random.randint and its import. If
unpredictability is intentionally not required, retain the existing source but
add a narrowly scoped Ruff S311 suppression with a rationale and document that
it only reduces overlap.

Source: Linters/SAST tools

@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

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)

146-151: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reject invalid targets before calculating cumulative work.

get_total_work processes candidate blocks before _apply_block validates them. The or 1 fallback maps target 0 or False to 1, which grants the candidate maximum work. A non-numeric target can raise TypeError and abort resolve_conflicts.

Validate every target before comparing chain work. Remove the fallback and reject invalid candidates.

🤖 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 146 - 151, Update get_total_work to validate
each block.target before performing cumulative-work arithmetic, rejecting zero,
false, and other invalid targets instead of converting them to 1. Remove the “or
1” fallback, and ensure resolve_conflicts handles the resulting invalid
candidate without aborting.
🤖 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.

Outside diff comments:
In `@minichain/chain.py`:
- Around line 146-151: Update get_total_work to validate each block.target
before performing cumulative-work arithmetic, rejecting zero, false, and other
invalid targets instead of converting them to 1. Remove the “or 1” fallback, and
ensure resolve_conflicts handles the resulting invalid candidate without
aborting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fd361269-a6fe-4808-b58f-7c0f29fe0ba8

📥 Commits

Reviewing files that changed from the base of the PR and between 0d90055 and 9611a66.

📒 Files selected for processing (1)
  • minichain/chain.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.

Actionable comments posted: 3

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)

299-304: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Commit account storage only after the full reorganization validates.

_apply_block mutates temp_state before it can return an invalid status. Lines 302-304 commit those mutations before Line 305 checks the status. A rejected reorganization can therefore persist partial account changes. Keep the temporary account state isolated for the full loop, then commit only after every block is valid.

🤖 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 299 - 304, The reorganization loop commits
account mutations immediately after each _apply_block call, before validating
the returned status. Remove the per-block commit and backing reassignment from
the loop, keep temp_state.accounts isolated throughout all _apply_block
iterations, and commit the temporary account storage only after every block has
returned a valid status.
🤖 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 `@minichain/chain.py`:
- Around line 156-162: Validate genesis configuration’s target_block_time before
it reaches the proportional retargeting calculation in the chain
initialization/loading flow: reject booleans, non-integer values, and values
less than or equal to zero. Ensure only a positive integer is stored and used by
the new_target computation, preventing division by zero and fractional targets.
- Around line 272-278: Update the fast PoW validation loop in the reorg handling
code to first require each b.hash to be a string, then safely convert it from
hexadecimal while handling conversion errors. For missing or malformed hashes,
log the existing invalid-target/hash validation failure and return (False, [])
instead of allowing int(b.hash, 16) to raise.

In `@tests/test_target.py`:
- Around line 14-38: Update the retarget expectations in the affected test to
match the stated additive rule: expect start_target - 1 after the fast block and
expected_target_fast + 1 after the slow block, including the repeated assertions
noted in the comment. Modify Blockchain._next_target to apply a ±1 change based
on average block time instead of proportional scaling.

---

Outside diff comments:
In `@minichain/chain.py`:
- Around line 299-304: The reorganization loop commits account mutations
immediately after each _apply_block call, before validating the returned status.
Remove the per-block commit and backing reassignment from the loop, keep
temp_state.accounts isolated throughout all _apply_block iterations, and commit
the temporary account storage only after every block has returned a valid
status.
🪄 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 Plus

Run ID: b9349c2a-c355-459e-b601-88027380db5a

📥 Commits

Reviewing files that changed from the base of the PR and between 9611a66 and 02f8d0b.

📒 Files selected for processing (3)
  • minichain/block.py
  • minichain/chain.py
  • tests/test_target.py

Comment thread minichain/chain.py
Comment on lines +156 to +162

# Proportional difficulty adjustment:
# If blocks are too slow (avg_block_time > target_block_time), the target INCREASES (easier)
# If blocks are too fast (avg_block_time < target_block_time), the target DECREASES (harder)
new_target = (target * int(avg_block_time)) // self.target_block_time

return max(MIN_TARGET, min(MAX_TARGET, new_target))

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate target_block_time before proportional retargeting.

If genesis config sets target_block_time to zero, Line 160 raises ZeroDivisionError. If it is a float, Line 160 can produce a non-integer target. Reject boolean, non-integer, and non-positive values when loading genesis config.

🤖 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 156 - 162, Validate genesis configuration’s
target_block_time before it reaches the proportional retargeting calculation in
the chain initialization/loading flow: reject booleans, non-integer values, and
values less than or equal to zero. Ensure only a positive integer is stored and
used by the new_target computation, preventing division by zero and fractional
targets.

Comment thread minichain/chain.py
Comment on lines +272 to +278
for b in new_chain_list:
if not isinstance(b.target, int) or b.target <= 0 or b.target > MAX_TARGET:
logger.warning("Reorg failed: Fast PoW check failed for block %s (invalid target)", b.index)
return False, []
if int(b.hash, 16) >= b.target:
logger.warning("Reorg failed: Fast PoW check failed for block %s (hash >= target)", b.index)
return False, []

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject malformed hashes without raising.

int(b.hash, 16) raises for None or malformed values. Block.from_dict in minichain/block.py Lines 157-162 permits a missing hash, so a peer can make conflict resolution fail before _apply_block returns an invalid status. Validate that b.hash is a string and catch conversion errors before this comparison.

Proposed fix
             for b in new_chain_list:
                 if not isinstance(b.target, int) or b.target <= 0 or b.target > MAX_TARGET:
                     logger.warning("Reorg failed: Fast PoW check failed for block %s (invalid target)", b.index)
                     return False, []
-                if int(b.hash, 16) >= b.target:
+                try:
+                    hash_value = int(b.hash, 16)
+                except (TypeError, ValueError):
+                    logger.warning("Reorg failed: Fast PoW check failed for block %s (invalid hash)", b.index)
+                    return False, []
+                if hash_value >= b.target:
                     logger.warning("Reorg failed: Fast PoW check failed for block %s (hash >= target)", b.index)
                     return False, []
📝 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
for b in new_chain_list:
if not isinstance(b.target, int) or b.target <= 0 or b.target > MAX_TARGET:
logger.warning("Reorg failed: Fast PoW check failed for block %s (invalid target)", b.index)
return False, []
if int(b.hash, 16) >= b.target:
logger.warning("Reorg failed: Fast PoW check failed for block %s (hash >= target)", b.index)
return False, []
for b in new_chain_list:
if not isinstance(b.target, int) or b.target <= 0 or b.target > MAX_TARGET:
logger.warning("Reorg failed: Fast PoW check failed for block %s (invalid target)", b.index)
return False, []
try:
hash_value = int(b.hash, 16)
except (TypeError, ValueError):
logger.warning("Reorg failed: Fast PoW check failed for block %s (invalid hash)", b.index)
return False, []
if hash_value >= b.target:
logger.warning("Reorg failed: Fast PoW check failed for block %s (hash >= target)", b.index)
return False, []
🤖 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 272 - 278, Update the fast PoW validation
loop in the reorg handling code to first require each b.hash to be a string,
then safely convert it from hexadecimal while handling conversion errors. For
missing or malformed hashes, log the existing invalid-target/hash validation
failure and return (False, []) instead of allowing int(b.hash, 16) to raise.

Comment thread tests/test_target.py
Comment on lines +14 to +38
# Start with a target comfortably in the middle
start_target = MAX_TARGET // 2
chain.current_target = start_target
chain.chain[0].target = start_target
chain.chain[0].hash = chain.chain[0].compute_hash()

# Fast mining: timestamps only 1ms apart
# avg = 0.5 * 1 + 0.5 * 1000 = 500.5 (which truncates to 500 in integer ops if needed, but in Python it's a float)
# new_target = (start_target * int(500.5)) // 1000 = (start_target * 500) // 1000 = start_target // 2
ts = chain.last_block.timestamp + 1
block1 = Block(index=1, previous_hash=chain.last_block.hash, transactions=[], timestamp=ts, target=chain.current_target, state_root=chain.state.state_root())
mined_block1 = mine_block(block1)
self.assertEqual(chain.add_block(mined_block1), ValidationStatus.VALID)
expected_target_fast = (start_target * 500) // 1000
self.assertEqual(chain.current_target, expected_target_fast)

# Slow mining: timestamp 5000ms apart
# avg = 0.5 * 5000 + 0.5 * 500.5 = 2750.25
# new_target = (expected_target_fast * int(2750.25)) // 1000 = (expected_target_fast * 2750) // 1000
ts = chain.last_block.timestamp + 5000
block2 = Block(index=2, previous_hash=chain.last_block.hash, transactions=[], timestamp=ts, target=chain.current_target, state_root=chain.state.state_root())
mined_block2 = mine_block(block2)
self.assertEqual(chain.add_block(mined_block2), ValidationStatus.VALID)
expected_target_slow = (expected_target_fast * 2750) // 1000
self.assertEqual(chain.current_target, expected_target_slow)

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

Align target expectations with the stated retarget rule.

Lines 27 and 37 expect proportional target changes. Lines 63-64 repeat the same expectation. The PR objective specifies a target change of ±1 from the average block time.

This test currently accepts multiplicative consensus retargeting. Update these expectations to start_target - 1 after the fast block and expected_target_fast + 1 after the slow block. Update Blockchain._next_target in the same change.

Proposed test changes
-        expected_target_fast = (start_target * 500) // 1000
+        expected_target_fast = start_target - 1
...
-        expected_target_slow = (expected_target_fast * 2750) // 1000
+        expected_target_slow = expected_target_fast + 1

Also applies to: 63-64

🤖 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_target.py` around lines 14 - 38, Update the retarget expectations
in the affected test to match the stated additive rule: expect start_target - 1
after the fast block and expected_target_fast + 1 after the slow block,
including the repeated assertions noted in the comment. Modify
Blockchain._next_target to apply a ±1 change based on average block time instead
of proportional scaling.

Comment thread minichain/chain.py
with self._lock:
chain_list = self.chain
return sum(2 ** (block.difficulty or 1) for block in chain_list)
return sum((1 << 256) // (block.target or 1) for block in chain_list)

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.

Write a comment explaining what this does.

Comment thread minichain/chain.py
Validates `block` against `prev_block` and applies its transactions to `state`
(mutated in place). On any non-VALID status the caller must discard `state`.
Returns: (ValidationStatus, new_difficulty, new_avg_block_time)
Returns: (ValidationStatus, new_target, new_avg_block_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.

Is a new_avg_block_time being computed? I don't see it.

Potential critical bug.

Comment thread minichain/block.py
transactions: Optional[Sequence[Transaction]] = None,
timestamp: Optional[float] = None,
difficulty: Optional[int] = None,
target: Optional[int] = None,

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.

A target is not Optional. Every block must have have a target.

Every dev goes through a phase of abusing the use of Optional. This leads to anti-patterns in the code, where one needs to be explicitly unwrapping Optional, checking for None and handling None in ad-hoc ways. I have the impression that this is happening to you currently.

Inspect every use of Optional (not only for target and not only for Block) and ask yourself whether you really need it.

For example, for transactions, one could use an empty sequence of transactions, instead of None.

Often special cases can be handled by using a natural default value instead of None.

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