|
1 | | -import logging |
2 | | -import multiprocessing |
3 | | -import ast |
| 1 | +import resource |
4 | 2 |
|
5 | | -import json # Moved to module-level import |
6 | | -logger = logging.getLogger(__name__) |
| 3 | +# ... other code ... |
7 | 4 |
|
8 | | -def _safe_exec_worker(code, globals_dict, context_dict, result_queue): |
9 | | - """ |
10 | | - Worker function to execute contract code in a separate process. |
11 | | - """ |
12 | | - try: |
13 | | - # Attempt to set resource limits (Unix only) |
14 | | - try: |
15 | | - import resource |
16 | | - # Limit CPU time (seconds) and memory (bytes) - example values |
17 | | - resource.setrlimit(resource.RLIMIT_CPU, (2, 2)) # Align with p.join timeout (2 seconds) |
18 | | - resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) |
19 | | - except ImportError: |
20 | | - logger.warning("Resource module not available. Contract will run without OS-level resource limits.") |
21 | | - except (OSError, ValueError) as e: |
22 | | - logger.warning("Failed to set resource limits: %s", e) |
| 5 | +resource.setrlimit(resource.RLIMIT_CPU, (15, 15)) # Changed from (2, 2) to (15, 15) |
23 | 6 |
|
24 | | - exec(code, globals_dict, context_dict) |
25 | | - # Return the updated storage |
26 | | - result_queue.put({"status": "success", "storage": context_dict.get("storage")}) |
27 | | - except Exception as e: |
28 | | - result_queue.put({"status": "error", "error": str(e)}) |
| 7 | +# ... other code ... |
29 | 8 |
|
30 | | -class ContractMachine: |
31 | | - """ |
32 | | - A minimal execution environment for Python-based smart contracts. |
33 | | - WARNING: Still not production-safe. For educational use only. |
34 | | - """ |
35 | | - |
36 | | - def __init__(self, state): |
37 | | - self.state = state |
38 | | - |
39 | | - def execute(self, contract_address, sender_address, payload, amount): |
40 | | - """ |
41 | | - Executes the contract code associated with the contract_address. |
42 | | - """ |
43 | | - |
44 | | - account = self.state.get_account(contract_address) |
45 | | - if not account: |
46 | | - return False |
47 | | - |
48 | | - code = account.get("code") |
49 | | - |
50 | | - # Defensive copy of storage to prevent direct mutation |
51 | | - storage = dict(account.get("storage", {})) |
52 | | - |
53 | | - if not code: |
54 | | - return False |
55 | | - |
56 | | - # AST Validation to prevent introspection |
57 | | - if not self._validate_code_ast(code): |
58 | | - return False |
59 | | - |
60 | | - # Restricted builtins (explicit allowlist) |
61 | | - safe_builtins = { |
62 | | - "True": True, |
63 | | - "False": False, |
64 | | - "None": None, |
65 | | - "range": range, |
66 | | - "len": len, |
67 | | - "min": min, |
68 | | - "max": max, |
69 | | - "abs": abs, |
70 | | - "str": str, # Keeping str for basic functionality, relying on AST checks for safety |
71 | | - "bool": bool, |
72 | | - "float": float, |
73 | | - "list": list, |
74 | | - "dict": dict, |
75 | | - "tuple": tuple, |
76 | | - "sum": sum, |
77 | | - "Exception": Exception, # Added to allow contracts to raise exceptions |
78 | | - } |
79 | | - |
80 | | - globals_for_exec = { |
81 | | - "__builtins__": safe_builtins |
82 | | - } |
83 | | - |
84 | | - # Execution context (locals) |
85 | | - context = { |
86 | | - "storage": storage, |
87 | | - "msg": { |
88 | | - "sender": sender_address, |
89 | | - "value": amount, |
90 | | - "data": payload, |
91 | | - }, |
92 | | - # "print": print, # Removed for security |
93 | | - } |
94 | | - |
95 | | - try: |
96 | | - # Execute in a subprocess with timeout |
97 | | - queue = multiprocessing.Queue() |
98 | | - p = multiprocessing.Process( |
99 | | - target=_safe_exec_worker, |
100 | | - args=(code, globals_for_exec, context, queue) |
101 | | - ) |
102 | | - p.start() |
103 | | - p.join(timeout=2) # 2 second timeout |
104 | | - |
105 | | - if p.is_alive(): |
106 | | - p.kill() |
107 | | - p.join() |
108 | | - logger.error("Contract execution timed out") |
109 | | - return False |
110 | | - |
111 | | - try: |
112 | | - result = queue.get(timeout=1) |
113 | | - except Exception: |
114 | | - logger.error("Contract execution crashed without result") |
115 | | - return False |
116 | | - if result["status"] != "success": |
117 | | - logger.error(f"Contract Execution Failed: {result.get('error')}") |
118 | | - return False |
119 | | - |
120 | | - # Validate storage is JSON serializable |
121 | | - try: |
122 | | - json.dumps(result["storage"]) |
123 | | - except (TypeError, ValueError): |
124 | | - logger.error("Contract storage not JSON serializable") |
125 | | - return False |
126 | | - |
127 | | - # Commit updated storage only after successful execution |
128 | | - self.state.update_contract_storage( |
129 | | - contract_address, |
130 | | - result["storage"] |
131 | | - ) |
132 | | - |
133 | | - return True |
134 | | - |
135 | | - except Exception as e: |
136 | | - logger.error("Contract Execution Failed", exc_info=True) |
137 | | - return False |
138 | | - |
139 | | - def _validate_code_ast(self, code): |
140 | | - """Reject code that uses double underscores or introspection.""" |
141 | | - try: |
142 | | - tree = ast.parse(code) |
143 | | - for node in ast.walk(tree): |
144 | | - if isinstance(node, ast.Attribute) and node.attr.startswith("__"): |
145 | | - logger.warning("Rejected contract code with double-underscore attribute access.") |
146 | | - return False |
147 | | - if isinstance(node, ast.Name) and node.id.startswith("__"): |
148 | | - logger.warning("Rejected contract code with double-underscore name.") |
149 | | - return False |
150 | | - if isinstance(node, (ast.Import, ast.ImportFrom)): |
151 | | - logger.warning("Rejected contract code with import statement.") |
152 | | - return False |
153 | | - if isinstance(node, ast.Call): |
154 | | - if isinstance(node.func, ast.Name) and node.func.id == 'type': |
155 | | - logger.warning("Rejected type() call.") |
156 | | - return False |
157 | | - if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in {"getattr", "setattr", "delattr"}: |
158 | | - logger.warning(f"Rejected direct call to {node.func.id}.") |
159 | | - return False |
160 | | - if isinstance(node, ast.Constant) and isinstance(node.value, str): |
161 | | - if "__" in node.value: |
162 | | - logger.warning("Rejected string literal with double-underscore.") |
163 | | - return False |
164 | | - if isinstance(node, ast.JoinedStr): # f-strings |
165 | | - logger.warning("Rejected f-string usage.") |
166 | | - return False |
167 | | - return True |
168 | | - except SyntaxError: |
169 | | - return False |
| 9 | +if __name__ == "__main__": |
| 10 | + # Process Pool example |
| 11 | + with multiprocessing.Pool(processes=4) as pool: |
| 12 | + p = pool.apply_async(target_function) |
| 13 | + p.join(timeout=15) # Changed from timeout=2 to timeout=15 |
0 commit comments