diff --git a/main.py b/main.py index e1edc51..2878717 100644 --- a/main.py +++ b/main.py @@ -27,8 +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 - +from minichain.p2p import is_valid_receiver logger = logging.getLogger(__name__) @@ -327,7 +326,6 @@ async def on_peer_connected(writer): logger.info("🔄 Sent state sync to new peer") network.register_on_peer_connected(on_peer_connected) - await network.start(port=port, host=host) # Fund this node's wallet so it can transact in the demo diff --git a/minichain/chain.py b/minichain/chain.py index b65d575..aa56532 100644 --- a/minichain/chain.py +++ b/minichain/chain.py @@ -1,33 +1,20 @@ +import logging +import threading from .block import Block from .state import State from .pow import calculate_hash -import logging -import threading logger = logging.getLogger(__name__) - def validate_block_link_and_hash(previous_block, block): if block.previous_hash != previous_block.hash: - raise ValueError( - f"invalid previous hash {block.previous_hash} != {previous_block.hash}" - ) - + raise ValueError("Invalid previous hash") if block.index != previous_block.index + 1: - raise ValueError( - f"invalid index {block.index} != {previous_block.index + 1}" - ) - - expected_hash = calculate_hash(block.to_header_dict()) - if block.hash != expected_hash: - raise ValueError(f"invalid hash {block.hash}") - + raise ValueError("Invalid index") + if block.hash != calculate_hash(block.to_header_dict()): + raise ValueError("Invalid hash") class Blockchain: - """ - Manages the blockchain, validates blocks, and commits state transitions. - """ - def __init__(self): self.chain = [] self.state = State() @@ -35,50 +22,28 @@ def __init__(self): self._create_genesis_block() def _create_genesis_block(self): - """ - Creates the genesis block with a fixed hash. - """ - genesis_block = Block( - index=0, - previous_hash="0", - transactions=[] - ) - genesis_block.hash = "0" * 64 - self.chain.append(genesis_block) + genesis = Block(index=0, previous_hash="0", transactions=[]) + genesis.hash = "0" * 64 + self.chain.append(genesis) @property def last_block(self): - """ - Returns the most recent block in the chain. - """ - with self._lock: # Acquire lock for thread-safe access + with self._lock: return self.chain[-1] def add_block(self, block): - """ - Validates and adds a block to the chain if all transactions succeed. - Uses a copied State to ensure atomic validation. - """ - with self._lock: try: validate_block_link_and_hash(self.last_block, block) except ValueError as exc: - logger.warning("Block %s rejected: %s", block.index, exc) + logger.warning(f"Block {block.index} rejected: {exc}") return False - # Validate transactions on a temporary state copy temp_state = self.state.copy() - for tx in block.transactions: - result = temp_state.validate_and_apply(tx) - - # Reject block if any transaction fails - if not result: - logger.warning("Block %s rejected: Transaction failed validation", block.index) + if not temp_state.validate_and_apply(tx): return False - # All transactions valid → commit state and append block self.state = temp_state self.chain.append(block) - return True + return True \ No newline at end of file diff --git a/minichain/contract.py b/minichain/contract.py index c88a20f..656b63d 100644 --- a/minichain/contract.py +++ b/minichain/contract.py @@ -1,28 +1,13 @@ import logging import multiprocessing import ast +import json -import json # Moved to module-level import logger = logging.getLogger(__name__) def _safe_exec_worker(code, globals_dict, context_dict, result_queue): - """ - Worker function to execute contract code in a separate process. - """ try: - # Attempt to set resource limits (Unix only) - try: - import resource - # Limit CPU time (seconds) and memory (bytes) - example values - resource.setrlimit(resource.RLIMIT_CPU, (2, 2)) # Align with p.join timeout (2 seconds) - resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) - except ImportError: - logger.warning("Resource module not available. Contract will run without OS-level resource limits.") - except (OSError, ValueError) as e: - logger.warning("Failed to set resource limits: %s", e) - exec(code, globals_dict, context_dict) - # Return the updated storage result_queue.put({"status": "success", "storage": context_dict.get("storage")}) except Exception as e: result_queue.put({"status": "error", "error": str(e)}) @@ -32,138 +17,69 @@ class ContractMachine: A minimal execution environment for Python-based smart contracts. WARNING: Still not production-safe. For educational use only. """ - def __init__(self, state): self.state = state def execute(self, contract_address, sender_address, payload, amount): - """ - Executes the contract code associated with the contract_address. - """ - account = self.state.get_account(contract_address) - if not account: + if not account or not account.get("code"): return False - code = account.get("code") - - # Defensive copy of storage to prevent direct mutation + code = account["code"] storage = dict(account.get("storage", {})) - if not code: - return False - - # AST Validation to prevent introspection if not self._validate_code_ast(code): return False - # Restricted builtins (explicit allowlist) + # Minimal & Deterministic builtins (No floats) safe_builtins = { - "True": True, - "False": False, - "None": None, - "range": range, - "len": len, - "min": min, - "max": max, - "abs": abs, - "str": str, # Keeping str for basic functionality, relying on AST checks for safety - "bool": bool, - "float": float, - "list": list, - "dict": dict, - "tuple": tuple, - "sum": sum, - "Exception": Exception, # Added to allow contracts to raise exceptions + "True": True, "False": False, "None": None, + "range": range, "len": len, "str": str, "int": int, + "bool": bool, "list": list, "dict": dict, "Exception": Exception } - globals_for_exec = { - "__builtins__": safe_builtins - } - - # Execution context (locals) context = { "storage": storage, - "msg": { - "sender": sender_address, - "value": amount, - "data": payload, - }, - # "print": print, # Removed for security + "msg": {"sender": sender_address, "value": amount, "data": payload} } try: - # Execute in a subprocess with timeout queue = multiprocessing.Queue() p = multiprocessing.Process( target=_safe_exec_worker, - args=(code, globals_for_exec, context, queue) + args=(code, {"__builtins__": safe_builtins}, context, queue) ) p.start() - p.join(timeout=2) # 2 second timeout + p.join(timeout=2) if p.is_alive(): p.kill() p.join() - logger.error("Contract execution timed out") + logger.error("Contract timeout") return False - try: - result = queue.get(timeout=1) - except Exception: - logger.error("Contract execution crashed without result") - return False + result = queue.get(timeout=1) if result["status"] != "success": - logger.error(f"Contract Execution Failed: {result.get('error')}") - return False - - # Validate storage is JSON serializable - try: - json.dumps(result["storage"]) - except (TypeError, ValueError): - logger.error("Contract storage not JSON serializable") return False - # Commit updated storage only after successful execution - self.state.update_contract_storage( - contract_address, - result["storage"] - ) - + json.dumps(result["storage"], allow_nan=False) # Validate JSON serializability + self.state.update_contract_storage(contract_address, result["storage"]) return True - except Exception as e: + except Exception: logger.error("Contract Execution Failed", exc_info=True) return False def _validate_code_ast(self, code): - """Reject code that uses double underscores or introspection.""" try: - tree = ast.parse(code) - for node in ast.walk(tree): - if isinstance(node, ast.Attribute) and node.attr.startswith("__"): - logger.warning("Rejected contract code with double-underscore attribute access.") - return False - if isinstance(node, ast.Name) and node.id.startswith("__"): - logger.warning("Rejected contract code with double-underscore name.") - return False - if isinstance(node, (ast.Import, ast.ImportFrom)): - logger.warning("Rejected contract code with import statement.") - return False - if isinstance(node, ast.Call): - if isinstance(node.func, ast.Name) and node.func.id == 'type': - logger.warning("Rejected type() call.") - return False - if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in {"getattr", "setattr", "delattr"}: - logger.warning(f"Rejected direct call to {node.func.id}.") - return False - if isinstance(node, ast.Constant) and isinstance(node.value, str): - if "__" in node.value: - logger.warning("Rejected string literal with double-underscore.") - return False - if isinstance(node, ast.JoinedStr): # f-strings - logger.warning("Rejected f-string usage.") - return False + for node in ast.walk(ast.parse(code)): + if isinstance(node, (ast.Import, ast.ImportFrom)): return False + if isinstance(node, ast.Attribute) and node.attr.startswith("__"): return False + if isinstance(node, ast.Name) and node.id.startswith("__"): return False + if isinstance(node, ast.Call) and getattr(node.func, "id", "") in {"type", "getattr", "setattr", "delattr"}: return False + if isinstance(node, ast.Constant) and isinstance(node.value, str) and "__" in node.value: return False + if isinstance(node, ast.Constant) and isinstance(node.value, (float, complex)): return False + if isinstance(node, ast.JoinedStr): return False return True except SyntaxError: - return False + return False \ No newline at end of file diff --git a/minichain/p2p.py b/minichain/p2p.py index 3271598..0769770 100644 --- a/minichain/p2p.py +++ b/minichain/p2p.py @@ -8,12 +8,15 @@ import asyncio import json import logging +import re from .serialization import canonical_json_hash -from .validators import is_valid_receiver logger = logging.getLogger(__name__) +def is_valid_receiver(receiver): + return bool(re.fullmatch(r"[0-9a-fA-F]{40}|[0-9a-fA-F]{64}", receiver)) + TOPIC = "minichain-global" SUPPORTED_MESSAGE_TYPES = {"sync", "tx", "block"} diff --git a/minichain/validators.py b/minichain/validators.py deleted file mode 100644 index b813df4..0000000 --- a/minichain/validators.py +++ /dev/null @@ -1,5 +0,0 @@ -import re - - -def is_valid_receiver(receiver): - return bool(re.fullmatch(r"[0-9a-fA-F]{40}|[0-9a-fA-F]{64}", receiver))