Skip to content

Commit a4e3fd6

Browse files
committed
Update resource limits and join timeout in contract.py
1 parent 1d602c8 commit a4e3fd6

1 file changed

Lines changed: 9 additions & 165 deletions

File tree

minichain/contract.py

Lines changed: 9 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,13 @@
1-
import logging
2-
import multiprocessing
3-
import ast
1+
import resource
42

5-
import json # Moved to module-level import
6-
logger = logging.getLogger(__name__)
3+
# ... other code ...
74

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)
236

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 ...
298

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

Comments
 (0)