fleetmesh-node 0.2.0__py3-none-any.whl

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 (338) hide show
  1. fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
  2. fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
  3. fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
  4. fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
  5. fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
  6. fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
  7. node/RELEASE +1 -0
  8. node/__init__.py +35 -0
  9. node/app/__init__.py +49 -0
  10. node/app/commands.py +265 -0
  11. node/app/config.py +131 -0
  12. node/app/control.py +670 -0
  13. node/app/core.py +867 -0
  14. node/app/plugins.py +76 -0
  15. node/app/router.py +83 -0
  16. node/app/socket.py +431 -0
  17. node/app/systemd.py +36 -0
  18. node/app/views.py +204 -0
  19. node/channels/__init__.py +378 -0
  20. node/channels/bus.py +331 -0
  21. node/channels/journal.py +46 -0
  22. node/commands/__init__.py +140 -0
  23. node/commands/adapter.py +210 -0
  24. node/commands/base.py +278 -0
  25. node/commands/cluster_cmds.py +183 -0
  26. node/commands/compute_cmds.py +401 -0
  27. node/commands/core.py +474 -0
  28. node/commands/gossip_cmds.py +106 -0
  29. node/commands/nsite_cmds.py +141 -0
  30. node/commands/peering.py +402 -0
  31. node/commands/plugin_cmds.py +135 -0
  32. node/commands/sidecar_cmds.py +120 -0
  33. node/commands/sync.py +388 -0
  34. node/commands/transport_cmds.py +451 -0
  35. node/commands/video_cmds.py +99 -0
  36. node/compartment/__init__.py +124 -0
  37. node/compartment/base.py +868 -0
  38. node/compartment/command.py +458 -0
  39. node/compartment/context.py +163 -0
  40. node/compartment/every.py +108 -0
  41. node/compartment/handler.py +131 -0
  42. node/compartment/lifecycle.py +460 -0
  43. node/compartment/lint.py +388 -0
  44. node/compartment/need.py +158 -0
  45. node/compartment/problem.py +521 -0
  46. node/compartment/refusals.py +120 -0
  47. node/compartment/registry.py +790 -0
  48. node/compartment/schema.py +290 -0
  49. node/compartment/sidecar.py +499 -0
  50. node/compartment/stores.py +662 -0
  51. node/compartment/wire.py +225 -0
  52. node/compartment/worker.py +61 -0
  53. node/config/__init__.py +43 -0
  54. node/config/base.py +116 -0
  55. node/config/change.py +441 -0
  56. node/config/changestore.py +201 -0
  57. node/config/compartments.py +1144 -0
  58. node/config/node.py +318 -0
  59. node/config/parameter.py +563 -0
  60. node/config/protocols.py +274 -0
  61. node/config/records.py +171 -0
  62. node/examples/__init__.py +1 -0
  63. node/examples/demo.py +226 -0
  64. node/examples/follower.py +46 -0
  65. node/interfaces.py +235 -0
  66. node/keystore/__init__.py +27 -0
  67. node/keystore/core.py +103 -0
  68. node/keystore/enclave.py +688 -0
  69. node/meshnode.py +111 -0
  70. node/metrics/__init__.py +396 -0
  71. node/metrics/adapters.py +102 -0
  72. node/metrics/face.py +110 -0
  73. node/planes/__init__.py +21 -0
  74. node/planes/identity/__init__.py +20 -0
  75. node/planes/identity/announce/__init__.py +9 -0
  76. node/planes/identity/announce/compartment.py +447 -0
  77. node/planes/identity/announce/types.py +26 -0
  78. node/planes/identity/discovery/__init__.py +9 -0
  79. node/planes/identity/discovery/compartment.py +522 -0
  80. node/planes/identity/discovery/types.py +13 -0
  81. node/planes/identity/linked/__init__.py +1 -0
  82. node/planes/identity/linked/compartment.py +270 -0
  83. node/planes/identity/linked/types.py +25 -0
  84. node/planes/identity/presence/__init__.py +7 -0
  85. node/planes/identity/presence/compartment.py +409 -0
  86. node/planes/identity/presence/types.py +14 -0
  87. node/planes/operations/__init__.py +13 -0
  88. node/planes/operations/scheduler/__init__.py +9 -0
  89. node/planes/operations/scheduler/compartment.py +353 -0
  90. node/planes/operations/scheduler/types.py +21 -0
  91. node/planes/operations/updates/__init__.py +17 -0
  92. node/planes/operations/updates/core.py +310 -0
  93. node/planes/operations/updates/store.py +79 -0
  94. node/planes/operations/updates/types.py +20 -0
  95. node/planes/peering/__init__.py +28 -0
  96. node/planes/peering/budget/__init__.py +43 -0
  97. node/planes/peering/budget/chain.py +43 -0
  98. node/planes/peering/budget/core.py +537 -0
  99. node/planes/peering/budget/evaluation.py +80 -0
  100. node/planes/peering/budget/meter.py +100 -0
  101. node/planes/peering/budget/receipts.py +439 -0
  102. node/planes/peering/budget/shape.py +101 -0
  103. node/planes/peering/budget/tick.py +110 -0
  104. node/planes/peering/budget/types.py +67 -0
  105. node/planes/peering/budget/windows.py +346 -0
  106. node/planes/peering/capacity/__init__.py +7 -0
  107. node/planes/peering/capacity/compartment.py +342 -0
  108. node/planes/peering/capacity/types.py +24 -0
  109. node/planes/peering/peering/__init__.py +25 -0
  110. node/planes/peering/peering/core.py +464 -0
  111. node/planes/peering/peering/envelope.py +142 -0
  112. node/planes/peering/peering/handshake.py +198 -0
  113. node/planes/peering/peering/shape.py +80 -0
  114. node/planes/peering/peering/types.py +39 -0
  115. node/planes/peering/reporter/__init__.py +42 -0
  116. node/planes/peering/reporter/constants.py +29 -0
  117. node/planes/peering/reporter/core.py +299 -0
  118. node/planes/peering/reporter/finding.py +74 -0
  119. node/planes/peering/reporter/findings.py +171 -0
  120. node/planes/peering/reporter/publish.py +285 -0
  121. node/planes/peering/reporter/reading.py +193 -0
  122. node/planes/peering/reporter/shape.py +86 -0
  123. node/planes/peering/reporter/types.py +50 -0
  124. node/planes/peering/strain/__init__.py +9 -0
  125. node/planes/peering/strain/compartment.py +204 -0
  126. node/planes/peering/strain/types.py +21 -0
  127. node/planes/peering/trust/__init__.py +9 -0
  128. node/planes/peering/trust/compartment.py +239 -0
  129. node/planes/peering/trust/types.py +30 -0
  130. node/planes/services/__init__.py +33 -0
  131. node/planes/services/compute/__init__.py +160 -0
  132. node/planes/services/compute/common.py +134 -0
  133. node/planes/services/compute/compartment.py +681 -0
  134. node/planes/services/compute/executors/__init__.py +121 -0
  135. node/planes/services/compute/executors/agent.py +840 -0
  136. node/planes/services/compute/executors/bacalhau.py +605 -0
  137. node/planes/services/compute/executors/base.py +127 -0
  138. node/planes/services/compute/executors/harness.py +439 -0
  139. node/planes/services/compute/executors/mcp.py +454 -0
  140. node/planes/services/compute/executors/media.py +898 -0
  141. node/planes/services/compute/executors/nondet.py +283 -0
  142. node/planes/services/compute/executors/providers.py +499 -0
  143. node/planes/services/compute/executors/registry.py +140 -0
  144. node/planes/services/compute/executors/wasm.py +244 -0
  145. node/planes/services/compute/executors/zk.py +604 -0
  146. node/planes/services/compute/jobs.py +227 -0
  147. node/planes/services/compute/load.py +180 -0
  148. node/planes/services/compute/market.py +433 -0
  149. node/planes/services/compute/sandbox.py +282 -0
  150. node/planes/services/compute/types.py +84 -0
  151. node/planes/services/compute_wire/__init__.py +58 -0
  152. node/planes/services/compute_wire/compartment.py +256 -0
  153. node/planes/services/compute_wire/messages.py +320 -0
  154. node/planes/services/compute_wire/sealing.py +147 -0
  155. node/planes/services/compute_wire/session.py +572 -0
  156. node/planes/services/compute_wire/types.py +49 -0
  157. node/planes/services/compute_wire/verify.py +331 -0
  158. node/planes/services/dvm/__init__.py +24 -0
  159. node/planes/services/dvm/gateway.py +526 -0
  160. node/planes/services/dvm/protocol.py +35 -0
  161. node/planes/services/dvm/translate.py +15 -0
  162. node/planes/services/dvm/types.py +25 -0
  163. node/planes/services/gateway/__init__.py +40 -0
  164. node/planes/services/gateway/settle.py +279 -0
  165. node/planes/services/gateway/translate.py +644 -0
  166. node/planes/services/gossip/__init__.py +22 -0
  167. node/planes/services/gossip/core.py +929 -0
  168. node/planes/services/gossip/envelope.py +154 -0
  169. node/planes/services/gossip/mesh.py +127 -0
  170. node/planes/services/gossip/types.py +50 -0
  171. node/planes/services/nsite/__init__.py +7 -0
  172. node/planes/services/nsite/client.py +62 -0
  173. node/planes/services/nsite/compartment.py +752 -0
  174. node/planes/services/nsite/types.py +44 -0
  175. node/planes/services/paidfetch/__init__.py +7 -0
  176. node/planes/services/paidfetch/compartment.py +862 -0
  177. node/planes/services/paidfetch/types.py +99 -0
  178. node/planes/services/settle/__init__.py +27 -0
  179. node/planes/services/settle/compartment.py +384 -0
  180. node/planes/services/settle/engine.py +456 -0
  181. node/planes/services/settle/gateways.py +541 -0
  182. node/planes/services/settle/payment.py +512 -0
  183. node/planes/services/settle/store.py +74 -0
  184. node/planes/services/settle/types.py +58 -0
  185. node/planes/services/sidecars/__init__.py +8 -0
  186. node/planes/services/sidecars/compartment.py +461 -0
  187. node/planes/services/sidecars/manifest.py +304 -0
  188. node/planes/services/sidecars/manifests/10-grasp.json +37 -0
  189. node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
  190. node/planes/services/sidecars/manifests/30-magpie.json +28 -0
  191. node/planes/services/sidecars/types.py +43 -0
  192. node/planes/services/video/__init__.py +106 -0
  193. node/planes/services/video/barter.py +407 -0
  194. node/planes/services/video/cli.py +158 -0
  195. node/planes/services/video/compartment.py +932 -0
  196. node/planes/services/video/ingest.py +524 -0
  197. node/planes/services/video/ledger.py +705 -0
  198. node/planes/services/video/packager.py +847 -0
  199. node/planes/services/video/player.py +456 -0
  200. node/planes/services/video/timed_segments.py +124 -0
  201. node/planes/services/video/transit.py +478 -0
  202. node/planes/store/__init__.py +24 -0
  203. node/planes/store/blob_replication/__init__.py +7 -0
  204. node/planes/store/blob_replication/compartment.py +496 -0
  205. node/planes/store/blob_replication/types.py +35 -0
  206. node/planes/store/blob_wire/__init__.py +48 -0
  207. node/planes/store/blob_wire/client.py +57 -0
  208. node/planes/store/blob_wire/core.py +359 -0
  209. node/planes/store/blob_wire/intake.py +74 -0
  210. node/planes/store/blob_wire/messages.py +85 -0
  211. node/planes/store/projected/__init__.py +7 -0
  212. node/planes/store/projected/compartment.py +258 -0
  213. node/planes/store/projected/types.py +29 -0
  214. node/planes/store/retention/__init__.py +7 -0
  215. node/planes/store/retention/compartment.py +214 -0
  216. node/planes/store/retention/types.py +12 -0
  217. node/planes/store/sync/__init__.py +35 -0
  218. node/planes/store/sync/core.py +311 -0
  219. node/planes/store/sync/envelope.py +106 -0
  220. node/planes/store/sync/handover.py +206 -0
  221. node/planes/store/sync/initiator.py +128 -0
  222. node/planes/store/sync/responder.py +114 -0
  223. node/planes/store/sync/rules.py +179 -0
  224. node/planes/store/sync/shape.py +80 -0
  225. node/planes/store/sync/types.py +51 -0
  226. node/planes/transport/__init__.py +23 -0
  227. node/planes/transport/bridge/__init__.py +13 -0
  228. node/planes/transport/bridge/compartment.py +785 -0
  229. node/planes/transport/bridge/ratelimit.py +44 -0
  230. node/planes/transport/bridge/types.py +43 -0
  231. node/planes/transport/lan/__init__.py +9 -0
  232. node/planes/transport/lan/compartment.py +446 -0
  233. node/planes/transport/lan/types.py +25 -0
  234. node/planes/transport/link/__init__.py +7 -0
  235. node/planes/transport/link/compartment.py +257 -0
  236. node/planes/transport/link/types.py +27 -0
  237. node/planes/transport/mediation/__init__.py +45 -0
  238. node/planes/transport/mediation/compartment.py +238 -0
  239. node/planes/transport/mediation/leaf.py +107 -0
  240. node/planes/transport/mediation/messages.py +302 -0
  241. node/planes/transport/mediation/service.py +233 -0
  242. node/planes/transport/mediation/store.py +37 -0
  243. node/planes/transport/mediation/types.py +20 -0
  244. node/planes/transport/p2p/__init__.py +7 -0
  245. node/planes/transport/p2p/compartment.py +349 -0
  246. node/planes/transport/p2p/types.py +22 -0
  247. node/protocols/__init__.py +277 -0
  248. node/protocols/activitypub/__init__.py +56 -0
  249. node/protocols/activitypub/actor.py +230 -0
  250. node/protocols/activitypub/federation.py +490 -0
  251. node/protocols/activitypub/media.py +145 -0
  252. node/protocols/activitypub/models.py +286 -0
  253. node/protocols/activitypub/package.py +271 -0
  254. node/protocols/activitypub/store.py +564 -0
  255. node/protocols/arcade/__init__.py +26 -0
  256. node/protocols/atproto/__init__.py +13 -0
  257. node/protocols/atproto/firehose.py +126 -0
  258. node/protocols/atproto/metering.py +165 -0
  259. node/protocols/atproto/package.py +155 -0
  260. node/protocols/atproto/repo.py +250 -0
  261. node/protocols/atproto/xrpc.py +285 -0
  262. node/protocols/base.py +109 -0
  263. node/protocols/circles/__init__.py +29 -0
  264. node/protocols/dm/__init__.py +50 -0
  265. node/protocols/grasp/__init__.py +22 -0
  266. node/protocols/isotime.py +15 -0
  267. node/protocols/nostr/__init__.py +51 -0
  268. node/protocols/nostr/blobstore.py +383 -0
  269. node/protocols/nostr/blossom.py +627 -0
  270. node/protocols/nostr/client.py +377 -0
  271. node/protocols/nostr/descriptor.py +211 -0
  272. node/protocols/nostr/eventstore.py +304 -0
  273. node/protocols/nostr/package.py +182 -0
  274. node/protocols/nostr/peers.py +109 -0
  275. node/protocols/nostr/relayserver.py +656 -0
  276. node/protocols/p2panda/__init__.py +22 -0
  277. node/protocols/p2panda/identity.py +19 -0
  278. node/protocols/p2panda/package.py +236 -0
  279. node/protocols/p2panda/rung.py +152 -0
  280. node/protocols/p2panda/store.py +273 -0
  281. node/protocols/rhio/__init__.py +33 -0
  282. node/protocols/rhio/package.py +275 -0
  283. node/protocols/rhio/rung.py +136 -0
  284. node/protocols/rhio/store.py +1423 -0
  285. node/protocols/ssb/muxrpc.py +256 -0
  286. node/protocols/ssb/package.py +267 -0
  287. node/protocols/textile/__init__.py +111 -0
  288. node/protocols/textile/transducers.py +259 -0
  289. node/release.py +119 -0
  290. node/storage/__init__.py +88 -0
  291. node/storage/accounts.py +305 -0
  292. node/storage/adapter.py +161 -0
  293. node/storage/base.py +385 -0
  294. node/storage/dialect.py +209 -0
  295. node/storage/grants.py +317 -0
  296. node/storage/locator.py +150 -0
  297. node/storage/postgres.py +294 -0
  298. node/storage/projected.py +953 -0
  299. node/storage/sharding.py +448 -0
  300. node/storage/standing.py +568 -0
  301. node/testnet/__init__.py +9 -0
  302. node/testnet/cluster.py +731 -0
  303. node/testnet/fips_daemon.py +230 -0
  304. node/transport/__init__.py +94 -0
  305. node/transport/const.py +110 -0
  306. node/transport/didcomm_inbound.py +251 -0
  307. node/transport/dnstxt.py +169 -0
  308. node/transport/fips_rung/__init__.py +67 -0
  309. node/transport/fips_rung/core.py +666 -0
  310. node/transport/fips_rung/listener.py +96 -0
  311. node/transport/fips_rung/types.py +12 -0
  312. node/transport/host.py +334 -0
  313. node/transport/iroh_rung/__init__.py +29 -0
  314. node/transport/iroh_rung/core.py +477 -0
  315. node/transport/iroh_rung/quic.py +942 -0
  316. node/transport/iroh_rung/types.py +22 -0
  317. node/transport/ladder.py +145 -0
  318. node/transport/lineproto.py +49 -0
  319. node/transport/mdns.py +303 -0
  320. node/transport/netfetch.py +283 -0
  321. node/transport/relay_rung/__init__.py +23 -0
  322. node/transport/relay_rung/cooldown.py +87 -0
  323. node/transport/relay_rung/core.py +138 -0
  324. node/transport/relay_rung/inbound.py +161 -0
  325. node/transport/relay_rung/ledger.py +104 -0
  326. node/transport/relay_rung/listen.py +121 -0
  327. node/transport/relay_rung/outbound.py +181 -0
  328. node/transport/relay_rung/shape.py +71 -0
  329. node/transport/relay_rung/types.py +58 -0
  330. node/transport/relay_rung/wire.py +63 -0
  331. node/transport/router.py +761 -0
  332. node/transport/rung.py +276 -0
  333. node/transport/ssb_rung.py +166 -0
  334. node/transport/ws.py +502 -0
  335. sidecar/fleetmesh_announce.py +338 -0
  336. sidecar/fleetmesh_p2p.py +373 -0
  337. sidecar/fleetmesh_rhio.py +352 -0
  338. sidecar/fleetmesh_ssb.py +603 -0
@@ -0,0 +1,244 @@
1
+ """fleetmesh-wasi-v1: the WASI preview1 runner on wasmtime-py, and WasmExecutor.
2
+
3
+ wasmtime is optional. Everything the deterministic profile fixes (clock, random,
4
+ NaN canonicalisation, no filesystem, no network) is set up in run_wasi_module.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ import os
10
+ from typing import Any, Mapping, Sequence
11
+
12
+ from fleetmesh_core.crypto import crypto
13
+ from ..common import ComputeError, resolve_blob
14
+ from ..sandbox import ComputeLimits, NODE_MAX_MEMORY_BYTES
15
+ from .base import ComputeExecutionResult, ComputeExecutor, ComputeQuote
16
+
17
+ # -------------------------------------------------------------------------
18
+ # fleetmesh-wasi-v1 runtime (wasmtime-py)
19
+ # -------------------------------------------------------------------------
20
+ #
21
+ # Profile: WASI preview1, stdin = job input, stdout = result, no preopened
22
+ # directories (no filesystem), no environment, no network (preview1 has no
23
+ # sockets), clock_time_get / clock_res_get fixed at WASM_VIRTUAL_EPOCH_NS,
24
+ # random_get a SHA-256 stream seeded from the job nonce, NaNs canonicalised,
25
+ # threads/SIMD-relaxed off. Two nodes running the same module on the same
26
+ # input and nonce must produce byte-identical output and fuel.
27
+ #
28
+ # wasmtime is optional: a node without it refuses WASM jobs with a clear
29
+ # error instead of returning a fake result.
30
+
31
+ WASM_DEFAULT_MEMORY_BYTES: int = 256 * 1024 * 1024
32
+ WASM_MAX_OUTPUT_BYTES: int = 16 * 1024 * 1024
33
+ WASM_VIRTUAL_EPOCH_NS: int = 1_700_000_000 * 1_000_000_000
34
+
35
+ _WASM_ENGINE: Any = None
36
+
37
+
38
+ def wasmtime_available() -> bool:
39
+ # importlib.util.find_spec rather than a bare import: the module object
40
+ # is not needed here, and pyflakes cannot see through a noqa.
41
+ import importlib.util
42
+ return importlib.util.find_spec('wasmtime') is not None
43
+
44
+
45
+ def _wasm_engine() -> Any:
46
+ global _WASM_ENGINE
47
+ if _WASM_ENGINE is None:
48
+ import wasmtime
49
+ cfg = wasmtime.Config()
50
+ cfg.consume_fuel = True
51
+ cfg.cranelift_nan_canonicalization = True
52
+ cfg.wasm_threads = False
53
+ cfg.wasm_relaxed_simd = False
54
+ cfg.parallel_compilation = False
55
+ # Default wasmtime reserves 4 GiB+guard of address space per memory,
56
+ # which cannot be mapped under the sandbox's RLIMIT_AS. Reserve
57
+ # nothing up front and let memories move on growth instead.
58
+ cfg.memory_reservation = 0
59
+ cfg.memory_guard_size = 64 * 1024
60
+ cfg.memory_reservation_for_growth = 1024 * 1024
61
+ cfg.memory_may_move = True
62
+ if hasattr(type(cfg), 'macos_use_mach_ports'):
63
+ try:
64
+ cfg.macos_use_mach_ports = False
65
+ except Exception:
66
+ pass
67
+ _WASM_ENGINE = wasmtime.Engine(cfg)
68
+ return _WASM_ENGINE
69
+
70
+
71
+ def run_wasi_module(
72
+ module_bytes: bytes,
73
+ input_bytes: bytes,
74
+ fuel_cap: int,
75
+ memory_bytes: int = WASM_DEFAULT_MEMORY_BYTES,
76
+ seed: str = '',
77
+ args: Sequence[str] = (),
78
+ ) -> tuple[bytes, int]:
79
+ """Run a WASI command module under fleetmesh-wasi-v1. Returns (stdout, fuel)."""
80
+ try:
81
+ import wasmtime
82
+ except ImportError as e:
83
+ raise ComputeError('WASM executor unavailable: install wasmtime (pip install wasmtime) [%s]' % e)
84
+ import tempfile
85
+
86
+ engine = _wasm_engine()
87
+ try:
88
+ module = wasmtime.Module(engine, module_bytes)
89
+ except wasmtime.WasmtimeError as e:
90
+ raise ComputeError('invalid WASM module: %s' % str(e).splitlines()[0])
91
+
92
+ store = wasmtime.Store(engine)
93
+ store.set_fuel(int(fuel_cap))
94
+ store.set_limits(memory_size=int(memory_bytes), instances=1, tables=4, memories=1)
95
+
96
+ out = bytearray()
97
+ overflow = [False]
98
+
99
+ def _stdout(chunk: bytes) -> None:
100
+ if len(out) + len(chunk) > WASM_MAX_OUTPUT_BYTES:
101
+ overflow[0] = True
102
+ return None
103
+ out.extend(chunk)
104
+ return None
105
+
106
+ with tempfile.TemporaryDirectory(prefix='fleetmesh-wasi-') as tmp:
107
+ in_path = os.path.join(tmp, 'stdin')
108
+ with open(in_path, 'wb') as f:
109
+ f.write(input_bytes)
110
+ wc = wasmtime.WasiConfig()
111
+ wc.argv = ['job.wasm', *args]
112
+ wc.stdin_file = in_path
113
+ wc.stdout_custom = _stdout
114
+ store.set_wasi(wc)
115
+
116
+ linker = wasmtime.Linker(engine)
117
+ linker.define_wasi()
118
+ linker.allow_shadowing = True
119
+ i32, i64 = wasmtime.ValType.i32(), wasmtime.ValType.i64()
120
+
121
+ def _clock_get(caller: Any, _cid: int, _prec: int, ptr: int) -> int:
122
+ caller['memory'].write(caller, WASM_VIRTUAL_EPOCH_NS.to_bytes(8, 'little'), ptr)
123
+ return 0
124
+
125
+ def _clock_res(caller: Any, _cid: int, ptr: int) -> int:
126
+ caller['memory'].write(caller, (1).to_bytes(8, 'little'), ptr)
127
+ return 0
128
+
129
+ rng_state = {'ctr': 0}
130
+ rng_key = hashlib.sha256(b'fleetmesh-wasi-v1/random:' + seed.encode('utf-8')).digest()
131
+
132
+ def _random(caller: Any, ptr: int, n: int) -> int:
133
+ buf = bytearray()
134
+ while len(buf) < n:
135
+ buf += hashlib.sha256(rng_key + rng_state['ctr'].to_bytes(8, 'big')).digest()
136
+ rng_state['ctr'] += 1
137
+ caller['memory'].write(caller, bytes(buf[:n]), ptr)
138
+ return 0
139
+
140
+ ns = 'wasi_snapshot_preview1'
141
+ linker.define_func(ns, 'clock_time_get', wasmtime.FuncType([i32, i64, i32], [i32]), _clock_get, access_caller=True)
142
+ linker.define_func(ns, 'clock_res_get', wasmtime.FuncType([i32, i32], [i32]), _clock_res, access_caller=True)
143
+ linker.define_func(ns, 'random_get', wasmtime.FuncType([i32, i32], [i32]), _random, access_caller=True)
144
+
145
+ try:
146
+ instance = linker.instantiate(store, module)
147
+ start = instance.exports(store).get('_start')
148
+ # A module may export '_start' as a memory/table/global to confuse
149
+ # a runner; only a real function is a WASI command entry point.
150
+ if not isinstance(start, wasmtime.Func):
151
+ raise ComputeError('WASM module has no _start export (not a WASI command)')
152
+ start(store)
153
+ except wasmtime.ExitTrap as e:
154
+ if e.code != 0:
155
+ raise ComputeError('WASM module exited with status %d' % e.code)
156
+ except wasmtime.Trap as e:
157
+ if e.trap_code == wasmtime.TrapCode.OUT_OF_FUEL:
158
+ raise ComputeError('job execution exceeded fuel ceiling (%d)' % fuel_cap)
159
+ raise ComputeError('WASM trap: %s' % str(e).splitlines()[0])
160
+ except wasmtime.WasmtimeError as e:
161
+ raise ComputeError('WASM error: %s' % str(e).splitlines()[0])
162
+
163
+ if overflow[0]:
164
+ raise ComputeError('WASM output exceeded %d bytes' % WASM_MAX_OUTPUT_BYTES)
165
+ return bytes(out), int(fuel_cap) - int(store.get_fuel())
166
+
167
+
168
+ class WasmExecutor(ComputeExecutor):
169
+ """Deterministic WASI runtime driver (fleetmesh-wasi-v1 profile)."""
170
+
171
+ engine_id: str = 'wasmtime'
172
+ wasi_profile_digest: str = hashlib.sha256(
173
+ b'fleetmesh-wasi-v1:network=none:fs=memory:clock=virtual:prng=seeded'
174
+ ).hexdigest()
175
+
176
+ def is_live_runnable(self) -> bool:
177
+ """Check whether wasmtime engine is installed and available on this host."""
178
+ return wasmtime_available()
179
+
180
+ def quote(
181
+ self,
182
+ module_spec: Mapping[str, Any],
183
+ input_spec: Mapping[str, Any],
184
+ limits: ComputeLimits,
185
+ ) -> ComputeQuote:
186
+ mod_len = len(str(module_spec.get('sha256', '')))
187
+ inp_len = len(str(input_spec.get('sha256', '')))
188
+ fuel_est = min(25000 + (mod_len + inp_len) * 250, limits.max_fuel)
189
+ return ComputeQuote(
190
+ fuel_estimate=fuel_est,
191
+ rate_unit='msat_per_mfuel',
192
+ rate_amount=45,
193
+ engine_id=self.engine_id,
194
+ )
195
+
196
+ def execute(
197
+ self,
198
+ job_payload: Mapping[str, Any],
199
+ fuel_cap: int,
200
+ ) -> ComputeExecutionResult:
201
+ module_spec = job_payload.get('module', {})
202
+ input_spec = job_payload.get('input', {})
203
+
204
+ blob_store = job_payload.get('blob_store')
205
+ mod_bytes = resolve_blob(module_spec, blob_store=blob_store, blossom_servers=job_payload.get('blossom_servers'))
206
+ inp_bytes = resolve_blob(input_spec, blob_store=blob_store, blossom_servers=job_payload.get('blossom_servers'))
207
+ if mod_bytes is None:
208
+ raise ComputeError('WASM module not resolvable (sha256=%s)' % module_spec.get('sha256', '?'))
209
+ want = str(module_spec.get('sha256') or '').lower()
210
+ if want.startswith('baf'):
211
+ # A CID commits to the same digest; decode it rather than skip
212
+ # the check. A CID that does not decode is a refusal, not a pass.
213
+ from node.protocols.nostr import blobstore
214
+ try:
215
+ want = blobstore.cid_to_sha256(want)
216
+ except Exception as e:
217
+ raise ComputeError('WASM module CID does not decode: %s' % e)
218
+ if want and hashlib.sha256(mod_bytes).hexdigest() != want:
219
+ raise ComputeError('WASM module bytes do not match declared sha256')
220
+
221
+ # The requester may ask for less linear memory than the operator
222
+ # ceiling, never more: run_job passes the clamped ComputeLimits
223
+ # ceiling as '_memory_ceiling_bytes'; without it (direct execute)
224
+ # NODE_MAX_MEMORY_BYTES applies. A wasm cap above the rlimit would let
225
+ # the module get killed by the sandbox instead of trapping cleanly.
226
+ ceiling = int(job_payload.get('_memory_ceiling_bytes') or NODE_MAX_MEMORY_BYTES)
227
+ ceiling = max(1, min(ceiling, NODE_MAX_MEMORY_BYTES))
228
+ requested = int(job_payload.get('wasm_max_memory_bytes') or WASM_DEFAULT_MEMORY_BYTES)
229
+ memory_bytes = max(1, min(requested, ceiling))
230
+
231
+ raw_output, fuel_used = run_wasi_module(
232
+ mod_bytes, inp_bytes or b'', fuel_cap,
233
+ memory_bytes=memory_bytes,
234
+ seed=str(job_payload.get('nonce') or job_payload.get('ask') or ''),
235
+ args=[str(a) for a in (job_payload.get('args') or [])],
236
+ )
237
+ result_hash = crypto.sha256(raw_output)
238
+ return ComputeExecutionResult(
239
+ result_hash=result_hash,
240
+ raw_output=raw_output,
241
+ fuel_used=fuel_used,
242
+ engine_id=self.engine_id,
243
+ wasi_profile_digest=self.wasi_profile_digest,
244
+ )