@ethernity-cloud/sdk-js 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +86 -48
  2. package/cas/config.js +61 -3
  3. package/info.js +14 -5
  4. package/init.js +24 -6
  5. package/network.js +51 -0
  6. package/nodenithy/build/securelock/Dockerfile.tmpl +34 -32
  7. package/nodenithy/build/securelock/scripts/binary-fs-build.sh +7 -6
  8. package/nodenithy/build.mjs +80 -68
  9. package/nodenithy/run/docker-compose-final.yml.tmpl +36 -31
  10. package/nodenithy/run/docker-compose.yml.tmpl +34 -45
  11. package/nodenithy/run/image_registry.js +17 -4
  12. package/nodenithy/run.js +42 -28
  13. package/nodenithy/src/ec_helloworld_example.js +46 -42
  14. package/nodenithy/src/preStart.mjs +41 -51
  15. package/package.json +6 -3
  16. package/publish.js +4 -2
  17. package/pynithy/build/securelock/Dockerfile.base.tpl +13 -0
  18. package/pynithy/build/securelock/Dockerfile.tpl +131 -0
  19. package/pynithy/build/securelock/scripts/binary-fs-build.sh +25 -34
  20. package/pynithy/build/securelock/scripts/pyfreeze.sh +32 -0
  21. package/pynithy/build/securelock/src/{pox.abi → abi/etnyImplementationV2.abi} +1489 -1489
  22. package/pynithy/build/securelock/src/abi/image_registry.abi +1 -0
  23. package/pynithy/build/securelock/src/ecld_state.py +906 -0
  24. package/pynithy/build/securelock/src/esr.abi +1 -0
  25. package/pynithy/build/securelock/src/esr_local.py +267 -0
  26. package/pynithy/build/securelock/src/esr_wallet.py +150 -0
  27. package/pynithy/build/securelock/src/etny_crypto.py +208 -167
  28. package/pynithy/build/securelock/src/etny_exec.py +385 -79
  29. package/pynithy/build/securelock/src/etny_exec_flask.py +535 -0
  30. package/pynithy/build/securelock/src/etny_exec_serv.py +353 -0
  31. package/pynithy/build/securelock/src/etny_session_exec.py +245 -0
  32. package/pynithy/build/securelock/src/get_sgx_report.so +0 -0
  33. package/pynithy/build/securelock/src/image_registry.abi +1 -1
  34. package/pynithy/build/securelock/src/key_generation.py +17 -17
  35. package/pynithy/build/securelock/src/local_api.py +189 -0
  36. package/pynithy/build/securelock/src/models.py +200 -176
  37. package/pynithy/build/securelock/src/securelock.py.tmpl +1037 -731
  38. package/pynithy/build/securelock/src/swift_stream_service.py +206 -186
  39. package/pynithy/build.mjs +139 -89
  40. package/pynithy/run/docker-compose-final.yml.tmpl +58 -51
  41. package/pynithy/run/docker-compose.yml.tmpl +62 -48
  42. package/pynithy/run/etny-securelock-test.yaml.tpl +4 -1
  43. package/pynithy/run/image_registry.js +17 -4
  44. package/pynithy/run.js +44 -28
  45. package/pynithy/src/ec_helloworld_example.js +46 -42
  46. package/pynithy/src/preStart.mjs +41 -51
  47. package/pynithy/src/serverless/Dockerfile.serverless +8 -0
  48. package/pynithy/src/serverless/requirements.txt +6 -0
  49. package/run.js +37 -34
  50. package/nodenithy/build/las/Dockerfile +0 -5
  51. package/nodenithy/src/preStart.js +0 -45
  52. package/pynithy/build/docker-compose.yml +0 -70
  53. package/pynithy/build/las/Dockerfile +0 -11
  54. package/pynithy/build/las/src/etny_entrypoint.sh +0 -6
  55. package/pynithy/build/las/src/las_entrypoint.sh +0 -26
  56. package/pynithy/build/scripts/binary-fs-build.sh +0 -28
  57. package/pynithy/build/securelock/Dockerfile.tmpl +0 -95
@@ -1,79 +1,385 @@
1
- import os.path
2
-
3
- try:
4
- import serverless.backend as backend
5
- except ImportError:
6
- backend = None
7
- pass
8
-
9
- sdkFunctions = {}
10
- if backend is not None:
11
- for func in backend.__dict__.keys():
12
- if func not in backend.__builtins__.keys() and func not in [
13
- "__file__",
14
- "__cached__",
15
- "__builtins__",
16
- ]:
17
- sdkFunctions.update({func: backend.__dict__[func]})
18
-
19
-
20
- def ___etny_result___(data):
21
- quit([0, data])
22
-
23
-
24
- class TaskStatus:
25
- SUCCESS = 0
26
- SYSTEM_ERROR = 1
27
- KEY_ERROR = 2
28
- SYNTAX_WARNING = 3
29
- BASE_EXCEPTION = 4
30
- PAYLOAD_NOT_DEFINED = 5
31
- PAYLOAD_CHECKSUM_ERROR = 6
32
- INPUT_CHECKSUM_ERROR = 7
33
-
34
-
35
- def execute_task(payload_data, input_data):
36
- return Exec(
37
- payload_data,
38
- input_data,
39
- {"___etny_result___": ___etny_result___, **sdkFunctions},
40
- )
41
-
42
-
43
- def Exec(payload_data, input_data, globals=None, locals=None):
44
- try:
45
- if payload_data is not None:
46
- if input_data is not None:
47
- globals["___etny_data_set___"] = input_data
48
- return ___etny_result___(eval(payload_data, globals, locals))
49
- else:
50
- return ___etny_result___(eval(payload_data, globals, locals))
51
- else:
52
- return (
53
- TaskStatus.PAYLOAD_NOT_DEFINED,
54
- "Could not find the source file to execute",
55
- )
56
-
57
- return TaskStatus.SUCCESS, "TASK EXECUTED SUCCESSFULLY"
58
- except SystemError as e:
59
- return TaskStatus.SYSTEM_ERROR, e.args[0]
60
- except KeyError as e:
61
- return TaskStatus.KEY_ERROR, e.args[0]
62
- except SyntaxWarning as e:
63
- return TaskStatus.SYNTAX_WARNING, e.args[0]
64
- except BaseException as e:
65
- try:
66
- if e.args[0][0] == 0:
67
- return TaskStatus.SUCCESS, e.args[0][1]
68
- else:
69
- return TaskStatus.BASE_EXCEPTION, e.args[0]
70
- except Exception as e:
71
- return TaskStatus.BASE_EXCEPTION, e.args[0]
72
-
73
-
74
- # result = Exec('./v1/src/app/payload.py', './v1/src/app/input.txt',
75
- # {'etny_print': etny_print})
76
- # print('task result:', result)
77
-
78
- # write the task result in a file
79
- # generate task hash
1
+ import os.path
2
+ import ast
3
+ import json
4
+ import logging
5
+ import traceback
6
+
7
+ # If the serverless backend fails to import, remember WHY. Previously any
8
+ # import failure was silently swallowed (backend = None), so every task then
9
+ # died later with a misleading "name 'X' is not defined" — a missing module in
10
+ # requirements.txt or a top-level-style import inside backend.py (the enclave
11
+ # loads it as the package `serverless.backend`) took many builds to diagnose.
12
+ # Only a genuinely absent backend (no serverless module at all) stays silent:
13
+ # that is the stock-enclave configuration, where payloads are self-contained.
14
+ _backend_import_error = None
15
+ try:
16
+ import serverless.backend as backend
17
+ except ModuleNotFoundError as e:
18
+ backend = None
19
+ if e.name not in ("serverless", "serverless.backend"):
20
+ _backend_import_error = f"{type(e).__name__}: {e}"
21
+ except BaseException as e: # SyntaxError, ValueError at import time, etc.
22
+ backend = None
23
+ _backend_import_error = f"{type(e).__name__}: {e}"
24
+
25
+ sdkFunctions = {}
26
+ if backend is not None:
27
+ for func in backend.__dict__.keys():
28
+ if func not in backend.__builtins__.keys() and func not in [
29
+ "__file__",
30
+ "__cached__",
31
+ "__builtins__",
32
+ ]:
33
+ sdkFunctions.update({func: backend.__dict__[func]})
34
+
35
+
36
+ def _encode_result_data(data):
37
+ """Automatic encoding for task results (single code path for all results).
38
+
39
+ Returns (type, encoded): JSON-serializable values ride as JSON, strings as
40
+ text, bytes-like as base64. Anything else is JSON-dumped with a stringify
41
+ fallback -- a task result never fails on encoding.
42
+ """
43
+ import base64 as _b64
44
+ if isinstance(data, (bytes, bytearray, memoryview)):
45
+ return "base64", _b64.b64encode(bytes(data)).decode("ascii")
46
+ if isinstance(data, str):
47
+ return "text", data
48
+ try:
49
+ json.dumps(data)
50
+ return "json", data
51
+ except (TypeError, ValueError):
52
+ try:
53
+ return "json", json.loads(json.dumps(data, default=str))
54
+ except Exception:
55
+ return "text", str(data)
56
+
57
+
58
+ def ecld_result(data=None, *, state=True, keys=None):
59
+ """Return a task result (THE way to end a task).
60
+
61
+ Builds the structured result envelope and terminates execution with it:
62
+
63
+ {"ecld": 1, "type": "json"|"text"|"base64", "data": ..., "esr": ...}
64
+
65
+ - `data` is encoded automatically (see _encode_result_data).
66
+ - `state=True` (default) attaches the ESR state of every key this task
67
+ touched; `state="meta"` attaches {key, version, cid} without the state
68
+ payload; `state=False` attaches nothing.
69
+ - `keys=[...]` restricts the attachment to those keys, force-reading any
70
+ the task did not touch.
71
+
72
+ ESR attachment is best-effort: an ESR-disabled build (or any attachment
73
+ error) yields `esr: null`, never a failed task.
74
+ """
75
+ rtype, rdata = _encode_result_data(data)
76
+ envelope = {"ecld": 1, "type": rtype, "data": rdata, "esr": None}
77
+ if state or keys:
78
+ try:
79
+ import ecld_state
80
+ envelope["esr"] = ecld_state.ledger_snapshot(
81
+ include_state=(state is True), keys=keys)
82
+ except Exception:
83
+ envelope["esr"] = None
84
+ quit([0, json.dumps(envelope, default=str)])
85
+
86
+
87
+ def ___etny_result___(data):
88
+ """Legacy alias: same builder, ESR attachment off -- today's behavior."""
89
+ return ecld_result(data, state=False)
90
+
91
+
92
+ def esr_fetch(*keys):
93
+ """Standard state-fetch task body: no computation, just attach state.
94
+
95
+ The runner's cache-gated read submits this on a cache miss, so dApps never
96
+ write their own read function: `esr_fetch("profile-7")`.
97
+ """
98
+ return ecld_result(None, keys=list(keys) or None)
99
+
100
+
101
+ class TaskStatus:
102
+ SUCCESS = 0
103
+ SYSTEM_ERROR = 1
104
+ KEY_ERROR = 2
105
+ SYNTAX_WARNING = 3
106
+ BASE_EXCEPTION = 4
107
+ PAYLOAD_NOT_DEFINED = 5
108
+ PAYLOAD_CHECKSUM_ERROR = 6
109
+ INPUT_CHECKSUM_ERROR = 7
110
+ EXECVE = 8
111
+ # Extended diagnostics (match the trustedzone's extended enum): the
112
+ # serverless backend failed to import inside the enclave, so none of its
113
+ # functions exist. Reported eagerly with the original import error instead
114
+ # of letting every call die as "name 'X' is not defined".
115
+ IMPORT_ERROR = 28
116
+ # A required enclave config value is present but EMPTY (e.g. an ESR
117
+ # address that was never baked into the sealed image). Reported eagerly
118
+ # with the variable named, instead of a confusing downstream crash.
119
+ CONFIG_ERROR = 32
120
+ EXECUTION_TIMEOUT = 33 # Started but produced no result within the order duration.
121
+ ESR_GAS_LIMIT_EXCEEDED = 34 # ESR state commits would exceed the per-order relayed-gas budget.
122
+ ESR_NONCE_VIOLATION = 36 # A commit's idempotency nonce was already used -- duplicate
123
+ # suppressed, state unchanged (StateNonceError).
124
+ ESR_RELAY_TIMEOUT = 37 # The enclave signed state commits but they did not land on
125
+ # the registry within 5 blocks -- the node did not relay them.
126
+ # The result is NOT trustworthy as persisted state; the
127
+ # validator refunds the order.
128
+ ESR_COMMIT_LIMIT_EXCEEDED = 38 # More than 100 state commits in one run -- the per-run cap
129
+ # that bounds relayed transactions and the result size.
130
+ SECURITY_VIOLATION = 35 # A state commit was authorized under a caller other than the
131
+ # task's submitter (the in-enclave ownership check was bypassed).
132
+ # Set by the securelock when it detects a forged commit caller,
133
+ # and independently by the trustedzone re-adjudication.
134
+ CAS_ATTESTATION_FAULT = 40 # The CAS that provisioned this enclave presented an
135
+ # INVALID self-attestation quote (ECAS_CAS_QUOTE failing
136
+ # the ValidatorRegistry checks). A missing quote is a
137
+ # rollout gap and only logs; an INCORRECT one means the
138
+ # operator provisioned the task through an impostor CAS.
139
+ # Operator fault; the order terminates and is refunded.
140
+
141
+
142
+ def _empty_required_config():
143
+ """Names of *_ADDRESS-style enclave config vars that are set but empty.
144
+
145
+ The enclave is sealed: env can only come from the image, so a present-but-
146
+ empty required value is always a build/render defect, never a runtime
147
+ choice. ESR_* vars only exist when the project enabled ESR at build time.
148
+ """
149
+ required = ("ESR_CONTRACT_ADDRESS",)
150
+ return [name for name in required
151
+ if name in os.environ and not os.environ[name].strip()]
152
+
153
+
154
+ def execute_task_v3(payload_data, input_data, extra_globals=None):
155
+ missing = _empty_required_config()
156
+ if missing:
157
+ return (
158
+ TaskStatus.CONFIG_ERROR,
159
+ "ENCLAVE CONFIG ERROR: required value(s) empty inside the enclave: "
160
+ + ", ".join(missing)
161
+ + " | The enclave is sealed, so this value had to be baked at build"
162
+ + " time and was not. Re-run ecld-build (it validates ESR config)"
163
+ + " and republish.",
164
+ )
165
+ if _backend_import_error is not None:
166
+ return (
167
+ TaskStatus.IMPORT_ERROR,
168
+ "BACKEND IMPORT ERROR: " + _backend_import_error
169
+ + " | The serverless backend failed to import inside the enclave, so"
170
+ + " none of its functions are available. Common causes: a module"
171
+ + " missing from src/serverless/requirements.txt, or a top-level"
172
+ + " import inside backend.py (the enclave loads it as the package"
173
+ + " 'serverless.backend' - use relative imports for sibling modules).",
174
+ )
175
+ base_globals = session_base_globals()
176
+ if extra_globals:
177
+ base_globals.update(extra_globals)
178
+ return Exec(payload_data, input_data, globals=base_globals)
179
+
180
+
181
+ class _Ecld:
182
+ """The single dApp-facing handle injected into every payload as `ecld`.
183
+
184
+ A namespaced front door over the same primitives a payload used to reach
185
+ by bare magic names, chosen to read like a contract object to Web3 devs:
186
+
187
+ ecld.result(value) # end the task with a result (+ESR state)
188
+ ecld.input # the request payload (was ___etny_data_set___)
189
+ ecld.caller # the data owner's wallet (msg.sender-style)
190
+ ecld.on_input = fn # session input handler (was ___etny_on_input___)
191
+ ecld.state.get(k) / .commit(k, v) / .grant(...) / ... # ESR
192
+ ecld.fetch("k1", "k2") # standard state-fetch task body
193
+
194
+ Every legacy name (___etny_result___, ___etny_data_set___,
195
+ ___etny_on_input___, task_caller, esr_*, ...) still works unchanged; this
196
+ object adds a coherent surface without removing anything. `input` and the
197
+ session handler are populated per-run by the executor.
198
+ """
199
+
200
+ def __init__(self):
201
+ self.result = ecld_result
202
+ self.fetch = esr_fetch
203
+ self.input = None # set by Exec() when input_data is present
204
+ self.on_input = None # session handler slot; payload assigns it
205
+ self.state = None # StateRegistry-style handle if ESR present
206
+ self._caller_fn = None # bound to ecld_state.task_caller if present
207
+
208
+ @property
209
+ def caller(self):
210
+ """The verified wallet that submitted this task (msg.sender-style),
211
+ or None in a non-ESR build / local test. Live: reads the attested
212
+ caller at access time, not at scope-build time."""
213
+ fn = self._caller_fn
214
+ try:
215
+ return fn() if callable(fn) else None
216
+ except Exception:
217
+ return None
218
+
219
+
220
+ def session_base_globals():
221
+ """The exec globals every payload run receives. Public so the session
222
+ executor (etny_session_exec) builds ONE persistent dict and keeps payload
223
+ state alive across streamed inputs."""
224
+ ecld = _Ecld()
225
+ base_globals = {
226
+ "___etny_result___": ___etny_result___, # legacy alias (no ESR)
227
+ "ecld_result": ecld_result, # the result API (bare)
228
+ "esr_fetch": esr_fetch, # standard state-fetch task
229
+ "ecld": ecld, # the namespaced handle
230
+ **sdkFunctions,
231
+ }
232
+ # State ownership / ACL API (present only in ESR-enabled builds). Enforced
233
+ # inside ecld_state against the trustedzone-attested caller.
234
+ try:
235
+ import ecld_state as _ecld_state
236
+ base_globals.setdefault("task_caller", _ecld_state.task_caller)
237
+ for _name in ("esr_grant", "esr_revoke", "esr_set_public_read",
238
+ "esr_transfer", "esr_owner", "esr_acl", "esr_nonce"):
239
+ base_globals.setdefault(_name, getattr(_ecld_state, _name))
240
+ # Mirror the ESR surface onto the namespaced handle. `caller` is a live
241
+ # property that calls task_caller() at access time (see _Ecld.caller).
242
+ ecld._caller_fn = _ecld_state.task_caller
243
+ try:
244
+ ecld.state = _ecld_state.StateRegistry()
245
+ except Exception:
246
+ ecld.state = None
247
+ except Exception:
248
+ pass
249
+ return base_globals
250
+
251
+ def Exec(payload_data, input_data, globals=None, locals=None):
252
+ try:
253
+ if globals is None:
254
+ globals = {}
255
+ if locals is None:
256
+ locals = globals
257
+
258
+ print("Globals keys:", list(globals.keys()))
259
+ print("Locals keys:", list(locals.keys()))
260
+ print("execve in globals:", "execve" in globals)
261
+ print("execve in locals:", "execve" in locals)
262
+
263
+ if payload_data is not None:
264
+ if input_data is not None:
265
+ globals["___etny_data_set___"] = input_data # legacy name
266
+ _ecld = globals.get("ecld")
267
+ if _ecld is not None:
268
+ _ecld.input = input_data # ecld.input
269
+ module = ast.parse(payload_data)
270
+ outputs = []
271
+ for node in module.body:
272
+ if isinstance(node, ast.Expr):
273
+ expr_code = compile(
274
+ ast.Expression(node.value), filename="<ast>", mode="eval"
275
+ )
276
+ result = eval(expr_code, globals, locals)
277
+ outputs.append(result)
278
+ else:
279
+ # Handle statements if needed
280
+ exec(
281
+ compile(ast.Module([node], type_ignores=[]), filename="<ast>", mode="exec"),
282
+ globals,
283
+ locals,
284
+ )
285
+
286
+ # Function results are arbitrary Python values -- the shipped
287
+ # esr-counter example returns dicts -- so serialize anything that
288
+ # is not already a str instead of letting join() raise. None (an
289
+ # expression statement with no value, e.g. a bare print()) is
290
+ # dropped, matching REPL semantics. NOTE: this is the executor the
291
+ # serverless securelock actually runs (local_api.py imports
292
+ # etny_exec); the 0.8.3 fix landed only in etny_exec_serv.py,
293
+ # which nothing imports.
294
+ rendered = [
295
+ out if isinstance(out, str) else json.dumps(out, default=str)
296
+ for out in outputs
297
+ if out is not None
298
+ ]
299
+ return ___etny_result___("\n".join(rendered))
300
+ else:
301
+ return (
302
+ TaskStatus.PAYLOAD_NOT_DEFINED,
303
+ "Could not find the source file to execute",
304
+ )
305
+
306
+ return TaskStatus.SUCCESS, "TASK EXECUTED SUCCESSFULLY"
307
+ except SystemExit as e:
308
+ # A task returns its result ONLY through ___etny_result___(data) ->
309
+ # quit([code, data]), which raises SystemExit carrying [code, data].
310
+ # Decode and honor that embedded code -- it is the authoritative outcome.
311
+ #
312
+ # Handled BEFORE `except SystemError` below on purpose. The read path runs
313
+ # web3/crypto C-extensions before returning; those intermittently raise a
314
+ # spurious SystemError ("error return without exception set") during the
315
+ # SystemExit unwind. With SystemError caught first, that spurious error
316
+ # stamped SYSTEM_ERROR onto a task that had already computed the correct
317
+ # value -- the ~50% false failure seen only on read tasks. Honoring the
318
+ # real result first fixes it.
319
+ try:
320
+ code, data = e.args[0][0], e.args[0][1]
321
+ except Exception:
322
+ tb = traceback.format_exc()
323
+ logging.error(
324
+ "SystemExit without a [code, data] result payload: %r (args=%r)"
325
+ " -- full traceback follows:\n%s", e, getattr(e, "args", None), tb)
326
+ return TaskStatus.SYSTEM_ERROR, f"SYSTEM_ERROR: {e!r}\n{tb}"
327
+ if code == 0:
328
+ return TaskStatus.SUCCESS, data
329
+ return int(code), data
330
+ except SystemError as e:
331
+ # A spurious native/interpreter SystemError can surface DURING the
332
+ # SystemExit unwind and REPLACE it, so the exception reaching here is a
333
+ # SystemError even though the task already produced its result via
334
+ # quit([code, data]). The real SystemExit is still on the chain as
335
+ # e.__context__ / e.__cause__ -- recover the result from there before
336
+ # treating this as a failure.
337
+ for chained in (getattr(e, "__context__", None), getattr(e, "__cause__", None)):
338
+ if isinstance(chained, SystemExit):
339
+ try:
340
+ code, data = chained.args[0][0], chained.args[0][1]
341
+ except Exception:
342
+ continue
343
+ logging.warning(
344
+ "SystemError raised during result unwind; recovered task "
345
+ "result from chained SystemExit (code=%r). Native error was: "
346
+ "%r", code, e)
347
+ if code == 0:
348
+ return TaskStatus.SUCCESS, data
349
+ return int(code), data
350
+ # No recoverable result -> genuine internal error. Deliver the FULL
351
+ # traceback to the data owner AS the result (visible in their runner
352
+ # output) and never crash the enclave.
353
+ tb = traceback.format_exc()
354
+ logging.error(
355
+ "SystemError in payload execution: %r (args=%r) -- full traceback "
356
+ "follows:\n%s", e, getattr(e, "args", None), tb)
357
+ return TaskStatus.SYSTEM_ERROR, f"SYSTEM_ERROR: {e!r}\n{tb}"
358
+ except KeyError as e:
359
+ return TaskStatus.KEY_ERROR, e.args[0]
360
+ except SyntaxWarning as e:
361
+ return TaskStatus.SYNTAX_WARNING, e.args[0]
362
+ except BaseException as e:
363
+ # A duplicate-suppressed commit (StateNonceError) gets its own task
364
+ # code so the dApp can distinguish "already applied" from a failure.
365
+ # Matched by name so non-ESR builds need no import.
366
+ if type(e).__name__ == "StateNonceError":
367
+ return TaskStatus.ESR_NONCE_VIOLATION, f"ESR_NONCE_VIOLATION: {e}"
368
+ # Over the per-run commit cap (100): the over-limit commit was NOT
369
+ # applied; the earlier commits stand and will be relayed.
370
+ if type(e).__name__ == "StateLimitError":
371
+ return (TaskStatus.ESR_COMMIT_LIMIT_EXCEEDED,
372
+ f"ESR_COMMIT_LIMIT_EXCEEDED: {e}")
373
+ # Deliver the traceback to the data owner as the result and never crash;
374
+ # preserve the embedded-result SUCCESS path (code 0).
375
+ try:
376
+ if e.args[0][0] == 0:
377
+ return TaskStatus.SUCCESS, e.args[0][1]
378
+ except Exception:
379
+ pass
380
+ tb = traceback.format_exc()
381
+ logging.error(
382
+ "BaseException in payload execution: %r -- full traceback follows:\n%s",
383
+ e, tb)
384
+ return TaskStatus.BASE_EXCEPTION, f"BASE_EXCEPTION: {e!r}\n{tb}"
385
+