-
-
Notifications
You must be signed in to change notification settings - Fork 14
refactor: Conceptual Minimality (File reduction, Deterministic execut… #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,84 +1,49 @@ | ||||||||||||||||||||||||||
| 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() | ||||||||||||||||||||||||||
| self._lock = threading.RLock() | ||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||
|
Comment on lines
43
to
45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider logging transaction validation failures for observability. Block validation failures are logged (line 39), but transaction validation failures return ♻️ Proposed fix for tx in block.transactions:
if not temp_state.validate_and_apply(tx):
+ logger.warning("Block %s rejected: transaction validation failed", block.index)
return False📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # All transactions valid → commit state and append block | ||||||||||||||||||||||||||
| self.state = temp_state | ||||||||||||||||||||||||||
| self.chain.append(block) | ||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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) | ||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you explain why you removed the setting of resource limits, @KDiamantidis ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely! The resource module is Unix-specific. Including it completely breaks cross-platform execution (e.g., if someone tries to run the node on Windows, it throws an ImportError and crashes instantly). Since we already enforce an absolute time boundary via p.join(timeout=2) and immediately terminate the process, I thought removing it helps conceptual minimality and portability. |
||||||||||||||||||||||||||||||||||||||||||
| 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. | ||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep this warning.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! I was aggressively cleaning up dead code and accidentally removed the docstring warning along with it. I will restore it right away in the next commit. |
||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring warning should be restored per the previous discussion. The author acknowledged in the past review that the docstring warning was accidentally removed and agreed to restore it. The current comment "No floats" is brief but the original warning provided important context about the sandbox limitations. Additionally, float literals are now properly rejected at the AST level (line 80) and JSON serialization enforces 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
46
to
59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Process.join(timeout=...) only limits how long the parent process waits for the child to terminate; it does not constrain the child process's memory or CPU usage. The child continues running unaffected until it finishes or is explicitly terminated (e.g., via p.terminate after timeout). If the timeout expires, join returns and the parent can check p.is_alive and act accordingly. On POSIX systems, resource.setrlimit(RLIMIT_AS, limits) or resource.setrlimit(RLIMIT_CPU, limits) called in the parent process sets limits that are inherited by child processes created via fork (including multiprocessing's 'fork' start method), as fork copies the parent's limits and they persist across exec. However, with 'spawn' or 'forkserver' start methods (now defaults on many platforms), the child starts a fresh Python interpreter, inheriting limits from the parent but potentially modified by multiprocessing setup. Limits apply per-process (not tree-wide), so each child has its own independent limits for AS (virtual memory) and CPU time (SIGXCPU on exceed). To set limits specifically for multiprocessing children, call setrlimit in the target function (runs in child) or use 'fork' start method after setting in parent. Note: RLIMIT_AS may not be fully supported on all POSIX (e.g., BSD/macOS lacks it), and requires appropriate privileges to raise hard limits. Citations:
Restore hard resource caps via The current timeout mechanism only limits how long the parent waits for the child process; it does not constrain the child's memory or CPU usage. An untrusted contract can still allocate unbounded memory and OOM the host before the 2-second timeout expires. Add 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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"]) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject non-dict storage before persisting it. Serializability alone does not protect the storage invariant. A contract can rebind Minimal guard- json.dumps(result["storage"], allow_nan=False) # Validate JSON serializability
- self.state.update_contract_storage(contract_address, result["storage"])
+ if not isinstance(result["storage"], dict):
+ logger.error("Contract returned invalid storage type")
+ return False
+ json.dumps(result["storage"], allow_nan=False) # Validate JSON serializability
+ self.state.update_contract_storage(contract_address, result["storage"])🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||
| logger.error("Contract Execution Failed", exc_info=True) | ||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+69
to
71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider using The static analysis suggests using ♻️ Proposed fix except Exception:
- logger.error("Contract Execution Failed", exc_info=True)
+ logger.exception("Contract Execution Failed")
return False🧰 Tools🪛 Ruff (0.15.7)[warning] 69-69: Logging (G201) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+75
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find the contract.py file and verify its location
fd -t f "contract.py" | head -20Repository: StabilityNexus/MiniChain Length of output: 112 🏁 Script executed: # Read the specific lines mentioned in the review (71-77)
cat -n minichain/contract.py | sed -n '65,80p'Repository: StabilityNexus/MiniChain Length of output: 1068 🏁 Script executed: # Check if Ruff is configured and what rules are enabled
find . -maxdepth 3 -type f \( -name "ruff.toml" -o -name "pyproject.toml" -o -name ".ruff.toml" \) -exec cat {} \;Repository: StabilityNexus/MiniChain Length of output: 2494 Split these one-line Lines 72–77 each have the pattern Example fix pattern- if isinstance(node, (ast.Import, ast.ImportFrom)): return False
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
+ return False📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.7)[error] 72-72: Multiple statements on one line (colon) (E701) [error] 73-73: Multiple statements on one line (colon) (E701) [error] 74-74: Multiple statements on one line (colon) (E701) [error] 75-75: Multiple statements on one line (colon) (E701) [error] 76-76: Multiple statements on one line (colon) (E701) [error] 77-77: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||
| except SyntaxError: | ||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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)) | ||||||||||||||
|
Comment on lines
+17
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider adding defensive type checking for robustness.
♻️ Optional defensive improvement def is_valid_receiver(receiver):
+ if not isinstance(receiver, str):
+ return False
return bool(re.fullmatch(r"[0-9a-fA-F]{40}|[0-9a-fA-F]{64}", receiver))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| TOPIC = "minichain-global" | ||||||||||||||
| SUPPORTED_MESSAGE_TYPES = {"sync", "tx", "block"} | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Prefer parameterized logging over f-strings.
F-strings are evaluated eagerly even when the log level would suppress the message, incurring unnecessary overhead. Use
%-style formatting which defers string interpolation until the message is actually emitted.♻️ Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 Ruff (0.15.7)
[warning] 39-39: Logging statement uses f-string
(G004)
🤖 Prompt for AI Agents