Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion minichain/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
logger = logging.getLogger(__name__)


class InvalidProofOfWorkError(ValueError):
pass


def validate_block_link_and_hash(previous_block, block):
Comment thread
Jiyacodex marked this conversation as resolved.
Outdated
if block.previous_hash != previous_block.hash:
raise ValueError(
Expand All @@ -25,6 +29,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 InvalidProofOfWorkError(
f"invalid PoW: hash {block.hash} does not satisfy difficulty {block.difficulty}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
)

class Blockchain:
"""
Expand Down Expand Up @@ -139,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
Expand Down
36 changes: 36 additions & 0 deletions tests/test_pow_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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