Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from nacl.encoding import HexEncoder

from minichain import Transaction, Blockchain, Block, State, Mempool, P2PNetwork, mine_block
from minichain.validators import is_valid_receiver



logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -67,7 +67,7 @@ def mine_and_process_block(chain, mempool, miner_pk):
if tx.nonce < expected_nonce:
stale_txs.append(tx)
continue
if temp_state.validate_and_apply(tx):
if temp_state.apply_transaction(tx):
mineable_txs.append(tx)

if stale_txs:
Expand Down Expand Up @@ -212,7 +212,7 @@ async def cli_loop(sk, pk, chain, mempool, network):
print(" Usage: send <receiver_address> <amount>")
continue
receiver = parts[1]
if not is_valid_receiver(receiver):
if not Transaction.is_valid_address(receiver):
print(" Invalid receiver format. Expected 40 or 64 hex characters.")
continue
try:
Expand Down
45 changes: 29 additions & 16 deletions minichain/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,32 @@ def compute_hash(self) -> str:

@classmethod
def from_dict(cls, payload: dict):
transactions = [
Transaction.from_dict(tx_payload)
for tx_payload in payload.get("transactions", [])
]
block = cls(
index=payload["index"],
previous_hash=payload["previous_hash"],
transactions=transactions,
timestamp=payload.get("timestamp"),
difficulty=payload.get("difficulty"),
)
block.nonce = payload.get("nonce", 0)
block.hash = payload.get("hash")
if "merkle_root" in payload:
block.merkle_root = payload["merkle_root"]
return block
try:
transactions = [
Transaction.from_dict(tx_payload)
for tx_payload in payload.get("transactions", [])
]
if any(tx is None for tx in transactions):
return None

block = cls(
index=payload["index"],
previous_hash=payload["previous_hash"],
transactions=transactions,
timestamp=payload.get("timestamp"),
difficulty=payload.get("difficulty"),
)
block.nonce = payload.get("nonce", 0)
block.hash = payload.get("hash")
if "merkle_root" in payload:
block.merkle_root = payload["merkle_root"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return block
except (KeyError, TypeError, ValueError):
return None

def is_valid(self):
if type(self.index) is not int or type(self.nonce) is not int or type(self.timestamp) is not int:
return False
if not isinstance(self.previous_hash, str) or (self.hash is not None and not isinstance(self.hash, str)):
return False
return all(tx.is_valid() for tx in self.transactions)
2 changes: 1 addition & 1 deletion minichain/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def add_block(self, block):
temp_state = self.state.copy()

for tx in block.transactions:
result = temp_state.validate_and_apply(tx)
result = temp_state.apply_transaction(tx)

# Reject block if any transaction fails
if not result:
Expand Down
4 changes: 2 additions & 2 deletions minichain/mempool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def __init__(self, max_size=1000, transactions_per_block=100):
self.transactions_per_block = transactions_per_block

def add_transaction(self, tx):
if not tx.verify():
logger.warning("Mempool: Invalid signature rejected")
if not tx.is_valid():
logger.warning("Mempool: Invalid transaction rejected")
return False

with self._lock:
Expand Down
68 changes: 7 additions & 61 deletions minichain/p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import logging

from .serialization import canonical_json_hash
from .validators import is_valid_receiver

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -114,41 +113,13 @@ async def _handle_incoming(
await self._notify_peer_connected(writer, "Network: Error during peer sync")

def _validate_transaction_payload(self, payload):
from .transaction import Transaction
if not isinstance(payload, dict):
return False

required_fields = {
"sender": str,
"amount": int,
"nonce": int,
"timestamp": int,
"signature": str,
}
optional_fields = {
"receiver": (str, type(None)),
"data": (str, type(None)),
}
allowed_fields = set(required_fields) | set(optional_fields)

if set(payload) != allowed_fields:
return False

for field, expected_type in required_fields.items():
if not isinstance(payload.get(field), expected_type):
return False

for field, expected_type in optional_fields.items():
if not isinstance(payload.get(field), expected_type):
return False

if payload["amount"] <= 0:
return False

receiver = payload.get("receiver")
if receiver is not None and not is_valid_receiver(receiver):
tx = Transaction.from_dict(payload)
if not tx:
return False

return True
return tx.is_valid()

def _validate_sync_payload(self, payload):
if not isinstance(payload, dict) or set(payload) != {"accounts"}:
Expand Down Expand Up @@ -176,36 +147,11 @@ def _validate_sync_payload(self, payload):
return True

def _validate_block_payload(self, payload):
from .block import Block
if not isinstance(payload, dict):
return False

required_fields = {
"index": int,
"previous_hash": str,
"merkle_root": (str, type(None)),
"transactions": list,
"timestamp": int,
"difficulty": (int, type(None)),
"nonce": int,
"hash": str,
}
optional_fields = {"miner": str}
allowed_fields = set(required_fields) | set(optional_fields)

if not set(payload).issubset(allowed_fields):
return False

for field, expected_type in required_fields.items():
if not isinstance(payload.get(field), expected_type):
return False

if "miner" in payload and not isinstance(payload["miner"], str):
return False

return all(
self._validate_transaction_payload(tx_payload)
for tx_payload in payload["transactions"]
)
block = Block.from_dict(payload)
return block is not None and block.is_valid()

def _validate_message(self, message):
if not isinstance(message, dict):
Expand Down
17 changes: 3 additions & 14 deletions minichain/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def get_account(self, address):
return self.accounts[address]

def verify_transaction_logic(self, tx):
if not tx.verify():
logger.error(f"Error: Invalid signature for tx from {tx.sender[:8]}...")
if not tx.is_valid():
logger.error("Error: Invalid transaction schema or signature")
return False

sender_acc = self.get_account(tx.sender)
Expand All @@ -50,18 +50,7 @@ def copy(self):
new_state.contract_machine = ContractMachine(new_state) # Reinitialize contract_machine
return new_state

def validate_and_apply(self, tx):
"""
Validate and apply a transaction.
Returns the same success/failure shape as apply_transaction().
NOTE: Delegates to apply_transaction. Callers should use this for
semantic validation entry points.
"""
# Semantic validation: amount must be an integer and non-negative
if not isinstance(tx.amount, int) or tx.amount < 0:
return False
# Further checks can be added here
return self.apply_transaction(tx)


def apply_transaction(self, tx):
"""
Expand Down
44 changes: 35 additions & 9 deletions minichain/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,43 @@ def to_signing_dict(self):
"timestamp": self.timestamp,
}

@staticmethod
def is_valid_address(address):
import re
return bool(re.fullmatch(r"[0-9a-fA-F]{40}|[0-9a-fA-F]{64}", address))
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@classmethod
def from_dict(cls, payload: dict):
return cls(
sender=payload["sender"],
receiver=payload.get("receiver"),
amount=payload["amount"],
nonce=payload["nonce"],
data=payload.get("data"),
signature=payload.get("signature"),
timestamp=payload.get("timestamp"),
)
try:
return cls(
sender=payload["sender"],
receiver=payload.get("receiver"),
amount=payload["amount"],
nonce=payload["nonce"],
data=payload.get("data"),
signature=payload.get("signature"),
timestamp=payload.get("timestamp"),
)
except (KeyError, TypeError):
return None

def is_valid(self):
"""Unified, stateless validation (Types, Schema, Signatures)"""
if not isinstance(self.amount, int) or self.amount < 0:
return False
if not isinstance(self.nonce, int) or self.nonce < 0:
return False
if not isinstance(self.sender, str) or not self.is_valid_address(self.sender):
return False
if self.receiver is not None:
if not isinstance(self.receiver, str) or not self.is_valid_address(self.receiver):
return False
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if self.data is not None and not isinstance(self.data, str):
return False
if not isinstance(self.timestamp, int) or self.timestamp <= 0:
return False

return self.verify()

@property
def hash_payload(self):
Expand Down
5 changes: 0 additions & 5 deletions minichain/validators.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/test_protocol_hardening.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ async def test_block_schema_accepts_current_block_wire_format(self):
sender_pk = sender_sk.verify_key.encode(encoder=HexEncoder).decode()
receiver_pk = SigningKey.generate().verify_key.encode(encoder=HexEncoder).decode()

tx = Transaction(sender_pk, receiver_pk, 1, 0, timestamp=123)
tx = Transaction(sender_pk, receiver_pk, 1, 0, timestamp=1600000000000)
tx.sign(sender_sk)

block = Block(index=1, previous_hash="0" * 64, transactions=[tx], timestamp=456, difficulty=2)
Expand Down
Loading