From 799550dd234b1a6e1881b269215aeaf8941a36e2 Mon Sep 17 00:00:00 2001 From: mac Date: Sun, 5 Jul 2026 02:11:05 +0530 Subject: [PATCH 1/3] fix: validate proof of work on block acceptance --- minichain/chain.py | 4 ++++ tests/test_pow_validation.py | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/test_pow_validation.py diff --git a/minichain/chain.py b/minichain/chain.py index fa7d0dd..de9a007 100644 --- a/minichain/chain.py +++ b/minichain/chain.py @@ -25,6 +25,10 @@ def validate_block_link_and_hash(previous_block, block): if block.hash != expected_hash: raise ValueError(f"invalid hash {block.hash}") + if not block.hash.startswith("0" * block.difficulty): + raise ValueError( + f"invalid PoW: hash {block.hash} does not satisfy difficulty {block.difficulty}" + ) class Blockchain: """ diff --git a/tests/test_pow_validation.py b/tests/test_pow_validation.py new file mode 100644 index 0000000..6a2f4da --- /dev/null +++ b/tests/test_pow_validation.py @@ -0,0 +1,37 @@ +from minichain import Blockchain, Block +from minichain.validators import ValidationStatus + + +def _make_block_with_invalid_pow(chain): + block = Block( + index=chain.last_block.index + 1, + previous_hash=chain.last_block.hash, + transactions=[], + difficulty=chain.current_difficulty, + state_root=chain.state.state_root(), + ) + + while True: + block.hash = block.compute_hash() + if not block.hash.startswith("0" * block.difficulty): + return block + block.nonce += 1 + + +def test_add_block_rejects_invalid_pow(): + chain = Blockchain() + block = _make_block_with_invalid_pow(chain) + + assert chain.add_block(block) == ValidationStatus.INVALID + assert len(chain.chain) == 1 + + +def test_resolve_conflicts_rejects_invalid_pow(): + chain = Blockchain() + block = _make_block_with_invalid_pow(chain) + + success, _ = chain.resolve_conflicts([chain.chain[0], block]) + + assert success is False + assert len(chain.chain) == 1 + \ No newline at end of file From 24636cf154ba81ac547268a4fdfb5e4d04f1557c Mon Sep 17 00:00:00 2001 From: mac Date: Sun, 5 Jul 2026 03:18:43 +0530 Subject: [PATCH 2/3] fix: handle invalid proof of work explicitly --- minichain/chain.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/minichain/chain.py b/minichain/chain.py index de9a007..cdbef56 100644 --- a/minichain/chain.py +++ b/minichain/chain.py @@ -10,6 +10,10 @@ logger = logging.getLogger(__name__) +class InvalidProofOfWorkError(ValueError): + pass + + def validate_block_link_and_hash(previous_block, block): if block.previous_hash != previous_block.hash: raise ValueError( @@ -26,7 +30,7 @@ def validate_block_link_and_hash(previous_block, block): raise ValueError(f"invalid hash {block.hash}") if not block.hash.startswith("0" * block.difficulty): - raise ValueError( + raise InvalidProofOfWorkError( f"invalid PoW: hash {block.hash} does not satisfy difficulty {block.difficulty}" ) @@ -143,11 +147,13 @@ def _apply_block(self, prev_block, block, state, difficulty, avg_block_time): try: validate_block_link_and_hash(prev_block, block) + except InvalidProofOfWorkError as exc: + logger.warning("Block %s rejected: %s", block.index, exc) + return ValidationStatus.INVALID, difficulty, avg_block_time except ValueError as exc: logger.warning("Block %s rejected: %s", block.index, exc) status = ValidationStatus.INVALID if "hash" in str(exc) else ValidationStatus.FAILED return status, difficulty, avg_block_time - if block.difficulty != difficulty: logger.warning("Block %s rejected: Invalid difficulty. Expected %s, got %s", block.index, difficulty, block.difficulty) return ValidationStatus.INVALID, difficulty, avg_block_time From 94231730f1eb6e68ad75df7635b5c0e5ac015d91 Mon Sep 17 00:00:00 2001 From: mac Date: Mon, 6 Jul 2026 17:22:24 +0530 Subject: [PATCH 3/3] style: remove trailing whitespace --- tests/test_pow_validation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_pow_validation.py b/tests/test_pow_validation.py index 6a2f4da..77695a5 100644 --- a/tests/test_pow_validation.py +++ b/tests/test_pow_validation.py @@ -34,4 +34,3 @@ def test_resolve_conflicts_rejects_invalid_pow(): assert success is False assert len(chain.chain) == 1 - \ No newline at end of file