Skip to content

Commit ff27889

Browse files
cristiam86claude
andcommitted
fix: disable 'latest' and 'test' runner versions with friendly error message
- Remove --debug-mode flag from GenVM calls to enforce version restrictions - Add INVALID_RUNNER error code for contracts using disallowed versions - Add user-friendly error messages for common GenVM errors - Extract and display helpful message when 'latest' or 'test' versions are used When a contract uses py-genlayer:latest or py-genlayer:test, users now see: "Invalid runner version. The 'latest' and 'test' versions are not allowed. Please use a fixed version hash in your contract header." Resolves DXP-742 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent aa41e86 commit ff27889

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

backend/node/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ async def get_contract_schema(self, code: bytes) -> str:
694694
),
695695
message=message,
696696
permissions="rw",
697-
extra_args=["--debug-mode"],
697+
extra_args=[],
698698
host_data='{"node_address":"0x", "tx_id":"0x"}',
699699
capture_output=True,
700700
is_sync=False,
@@ -818,7 +818,7 @@ async def _run_genvm(
818818
permissions=perms,
819819
capture_output=True,
820820
host_data=json.dumps(host_data),
821-
extra_args=["--debug-mode"],
821+
extra_args=[],
822822
is_sync=False,
823823
manager_uri=self.manager.url,
824824
timeout=timeout,

backend/node/genvm/base.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
extract_error_code_from_timeout,
4747
parse_module_error_string,
4848
GenVMInternalError,
49+
get_user_friendly_message,
4950
)
5051

5152

@@ -180,17 +181,27 @@ def provide_result(
180181
# Preserve raw error structure (causes, fatal, ctx) excluding message
181182
raw_error = {k: v for k, v in result_decoded.items() if k != "message"}
182183

184+
# Get user-friendly message if available
185+
original_message = result_decoded["message"]
186+
friendly_message = get_user_friendly_message(
187+
error_code, original_message, raw_error
188+
)
189+
183190
result = ExecutionError(
184-
result_decoded["message"],
191+
friendly_message,
185192
res.result_kind,
186193
error_code=error_code,
187194
raw_error=raw_error if raw_error else None,
188195
)
189196
else:
190197
# String error - try to extract error code from message
191198
error_code = extract_error_code(str(result_decoded), res.stderr)
199+
original_message = str(result_decoded)
200+
friendly_message = get_user_friendly_message(
201+
error_code, original_message
202+
)
192203
result = ExecutionError(
193-
str(result_decoded),
204+
friendly_message,
194205
res.result_kind,
195206
error_code=error_code,
196207
)

backend/node/genvm/error_codes.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"RATE_LIMIT_STATUSES",
2020
"extract_error_code",
2121
"parse_module_error_string",
22+
"get_user_friendly_message",
2223
)
2324

2425

@@ -73,12 +74,61 @@ class GenVMErrorCode(StrEnum):
7374
WEB_REQUEST_FAILED = "WEB_REQUEST_FAILED"
7475
WEB_TLD_FORBIDDEN = "WEB_TLD_FORBIDDEN"
7576

77+
# Contract/runner errors
78+
INVALID_RUNNER = "INVALID_RUNNER"
79+
7680
# Execution errors
7781
GENVM_TIMEOUT = "GENVM_TIMEOUT"
7882
CONTRACT_ERROR = "CONTRACT_ERROR"
7983
INTERNAL_ERROR = "INTERNAL_ERROR"
8084

8185

86+
# User-friendly error messages for each error code
87+
ERROR_CODE_MESSAGES: dict[str, str] = {
88+
GenVMErrorCode.INVALID_RUNNER: (
89+
"Invalid runner version. The 'latest' and 'test' versions are not allowed. "
90+
"Please use a fixed version hash in your contract header, e.g.: "
91+
'# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }'
92+
),
93+
GenVMErrorCode.LLM_RATE_LIMITED: "LLM provider rate limit exceeded. Please try again later.",
94+
GenVMErrorCode.LLM_NO_PROVIDER: "No LLM provider available for the requested operation.",
95+
GenVMErrorCode.LLM_PROVIDER_ERROR: "LLM provider returned an error.",
96+
GenVMErrorCode.LLM_INVALID_API_KEY: "Invalid API key for the LLM provider.",
97+
GenVMErrorCode.LLM_TIMEOUT: "LLM request timed out.",
98+
GenVMErrorCode.WEB_REQUEST_FAILED: "Web request failed.",
99+
GenVMErrorCode.WEB_TLD_FORBIDDEN: "Access to the requested domain is forbidden.",
100+
GenVMErrorCode.GENVM_TIMEOUT: "Contract execution timed out.",
101+
GenVMErrorCode.CONTRACT_ERROR: "Contract execution error.",
102+
GenVMErrorCode.INTERNAL_ERROR: "Internal GenVM error.",
103+
}
104+
105+
106+
def get_user_friendly_message(
107+
error_code: str | None, original_message: str, raw_error: dict | None = None
108+
) -> str:
109+
"""
110+
Get a user-friendly error message based on the error code.
111+
112+
Args:
113+
error_code: The standardized error code (e.g., INVALID_RUNNER)
114+
original_message: The original error message from GenVM
115+
raw_error: Optional raw error structure with causes
116+
117+
Returns:
118+
A user-friendly error message
119+
"""
120+
if error_code and error_code in ERROR_CODE_MESSAGES:
121+
return ERROR_CODE_MESSAGES[error_code]
122+
123+
# Fallback: check raw_error causes for invalid runner pattern
124+
if raw_error and isinstance(raw_error.get("causes"), list):
125+
for cause in raw_error["causes"]:
126+
if isinstance(cause, str) and "invalid runner id" in cause.lower():
127+
return ERROR_CODE_MESSAGES[GenVMErrorCode.INVALID_RUNNER]
128+
129+
return original_message
130+
131+
82132
# Mapping from Lua error causes to standardized error codes
83133
LUA_CAUSE_TO_CODE: dict[str, GenVMErrorCode] = {
84134
"NO_PROVIDER_FOR_PROMPT": GenVMErrorCode.LLM_NO_PROVIDER,
@@ -214,6 +264,9 @@ def extract_error_code(
214264
# Map Lua causes to error codes
215265
if isinstance(causes, list):
216266
for cause in causes:
267+
# Check for invalid runner error (e.g., "invalid runner id: py-genlayer:latest")
268+
if isinstance(cause, str) and "invalid runner id" in cause.lower():
269+
return GenVMErrorCode.INVALID_RUNNER
217270
if cause in LUA_CAUSE_TO_CODE:
218271
# Special case: STATUS_NOT_OK might be rate limiting based on ctx
219272
if cause == "STATUS_NOT_OK":
@@ -230,6 +283,10 @@ def _extract_from_message(message: str, stderr: str = "") -> Optional[str]:
230283
"""Extract error code from message string or stderr."""
231284
combined = f"{message} {stderr}".lower()
232285

286+
# Check for invalid runner (e.g., using :latest or :test in non-debug mode)
287+
if "invalid runner" in combined:
288+
return GenVMErrorCode.INVALID_RUNNER
289+
233290
# Check for specific error patterns
234291
if "rate limit" in combined or "429" in combined:
235292
return GenVMErrorCode.LLM_RATE_LIMITED

0 commit comments

Comments
 (0)