UNPKG

@metamask/ocap-kernel

Version:
211 lines 12.5 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _VatSupervisor_instances, _VatSupervisor_kernelStream, _VatSupervisor_logger, _VatSupervisor_rpcClient, _VatSupervisor_rpcService, _VatSupervisor_loaded, _VatSupervisor_dispatch, _VatSupervisor_vatKVStore, _VatSupervisor_vatPowers, _VatSupervisor_fetchBlob, _VatSupervisor_handleMessage, _VatSupervisor_deliver, _VatSupervisor_initVat; import { makeLiveSlots as localMakeLiveSlots } from "@agoric/swingset-liveslots"; import { importBundle } from "@endo/import-bundle"; import { makeMarshal } from "@endo/marshal"; import { StreamReadError } from "@metamask/kernel-errors"; import { RpcClient, RpcService } from "@metamask/kernel-rpc-methods"; import { waitUntilQuiescent } from "@metamask/kernel-utils"; import { serializeError } from "@metamask/rpc-errors"; import { isJsonRpcRequest, isJsonRpcResponse } from "@metamask/utils"; import { vatSyscallMethodSpecs, vatHandlers } from "./rpc/index.mjs"; import { makeGCAndFinalize } from "./services/gc-finalize.mjs"; import { makeDummyMeterControl } from "./services/meter-control.mjs"; import { makeSupervisorSyscall } from "./services/syscall.mjs"; import { makeVatKVStore } from "./store/vat-kv-store.mjs"; import { isVatConfig, coerceVatSyscallObject } from "./types.mjs"; const makeLiveSlots = localMakeLiveSlots; const marshal = makeMarshal(undefined, undefined, { serializeBodyFormat: 'smallcaps', }); export class VatSupervisor { /** * Construct a new VatSupervisor instance. * * @param params - Named constructor parameters. * @param params.id - The id of the vat being supervised. * @param params.kernelStream - Communications channel connected to the kernel. * @param params.logger - The logger for this vat. * @param params.vatPowers - The external capabilities for this vat. * @param params.fetchBlob - Function to fetch the user code bundle for this vat. */ constructor({ id, kernelStream, logger, vatPowers, fetchBlob, }) { _VatSupervisor_instances.add(this); /** Communications channel between this vat and the kernel */ _VatSupervisor_kernelStream.set(this, void 0); /** The logger for this vat */ _VatSupervisor_logger.set(this, void 0); /** RPC client for sending syscall requests to the kernel */ _VatSupervisor_rpcClient.set(this, void 0); /** RPC service for handling requests from the kernel */ _VatSupervisor_rpcService.set(this, void 0); /** Flag that the user code has been loaded */ _VatSupervisor_loaded.set(this, false); /** Function to dispatch deliveries into liveslots */ _VatSupervisor_dispatch.set(this, void 0); /** In-memory KVStore cache for this vat. */ _VatSupervisor_vatKVStore.set(this, void 0); /** External capabilities for this vat. */ _VatSupervisor_vatPowers.set(this, void 0); /** Capability to fetch the bundle of code to run in this vat. */ _VatSupervisor_fetchBlob.set(this, void 0); this.id = id; __classPrivateFieldSet(this, _VatSupervisor_kernelStream, kernelStream, "f"); __classPrivateFieldSet(this, _VatSupervisor_logger, logger, "f"); __classPrivateFieldSet(this, _VatSupervisor_vatPowers, vatPowers ?? {}, "f"); __classPrivateFieldSet(this, _VatSupervisor_dispatch, null, "f"); const defaultFetchBlob = async (bundleURL) => await fetch(bundleURL); __classPrivateFieldSet(this, _VatSupervisor_fetchBlob, fetchBlob ?? defaultFetchBlob, "f"); __classPrivateFieldSet(this, _VatSupervisor_rpcClient, new RpcClient(vatSyscallMethodSpecs, async (request) => { await __classPrivateFieldGet(this, _VatSupervisor_kernelStream, "f").write(request); }, `${this.id}:`, __classPrivateFieldGet(this, _VatSupervisor_logger, "f").subLogger({ tags: ['rpc-client'] })), "f"); __classPrivateFieldSet(this, _VatSupervisor_rpcService, new RpcService(vatHandlers, { initVat: __classPrivateFieldGet(this, _VatSupervisor_instances, "m", _VatSupervisor_initVat).bind(this), handleDelivery: __classPrivateFieldGet(this, _VatSupervisor_instances, "m", _VatSupervisor_deliver).bind(this), }), "f"); Promise.all([ __classPrivateFieldGet(this, _VatSupervisor_kernelStream, "f").drain(__classPrivateFieldGet(this, _VatSupervisor_instances, "m", _VatSupervisor_handleMessage).bind(this)), ]).catch(async (error) => { __classPrivateFieldGet(this, _VatSupervisor_logger, "f").error(`Unexpected read error from VatSupervisor "${this.id}"`, error); await this.terminate(new StreamReadError({ vatId: this.id }, error)); }); } /** * Terminate the VatSupervisor. * * @param error - The error to terminate the VatSupervisor with. */ async terminate(error) { await __classPrivateFieldGet(this, _VatSupervisor_kernelStream, "f").end(error); } /** * Execute a syscall by sending it to the kernel. To support the synchronous * requirements of the liveslots interface, it optimistically assumes success; * errors will be dealt with at the end of the crank. * * @param vso - Descriptor of the syscall to be issued. * * @returns a syscall success result. */ executeSyscall(vso) { // IMPORTANT: Syscall architecture design explanation: // - Vats operate on an "optimistic execution" model - they send syscalls and continue execution // without waiting for responses, assuming success. // - The Kernel processes syscalls synchronously on receipt and failures are caught in VatHandle. // - The vat is terminated and the crank is rolled back if a syscall fails. __classPrivateFieldGet(this, _VatSupervisor_rpcClient, "f") .notify('syscall', coerceVatSyscallObject(vso)) // Just to please the linter (notifications never reject) .catch(() => undefined); return ['ok', null]; } } _VatSupervisor_kernelStream = new WeakMap(), _VatSupervisor_logger = new WeakMap(), _VatSupervisor_rpcClient = new WeakMap(), _VatSupervisor_rpcService = new WeakMap(), _VatSupervisor_loaded = new WeakMap(), _VatSupervisor_dispatch = new WeakMap(), _VatSupervisor_vatKVStore = new WeakMap(), _VatSupervisor_vatPowers = new WeakMap(), _VatSupervisor_fetchBlob = new WeakMap(), _VatSupervisor_instances = new WeakSet(), _VatSupervisor_handleMessage = /** * Handle a message from the kernel. * * @param message - The vat message to handle. */ async function _VatSupervisor_handleMessage(message) { if (isJsonRpcResponse(message)) { __classPrivateFieldGet(this, _VatSupervisor_rpcClient, "f").handleResponse(message.id, message); } else if (isJsonRpcRequest(message)) { try { __classPrivateFieldGet(this, _VatSupervisor_rpcService, "f").assertHasMethod(message.method); const result = await __classPrivateFieldGet(this, _VatSupervisor_rpcService, "f").execute(message.method, message.params); await __classPrivateFieldGet(this, _VatSupervisor_kernelStream, "f").write({ id: message.id, result, jsonrpc: '2.0', }); } catch (error) { await __classPrivateFieldGet(this, _VatSupervisor_kernelStream, "f").write({ id: message.id, error: serializeError(error), jsonrpc: '2.0', }); } } }, _VatSupervisor_deliver = async function _VatSupervisor_deliver(params) { if (!__classPrivateFieldGet(this, _VatSupervisor_dispatch, "f")) { throw new Error(`cannot deliver before vat is loaded`); } let deliveryError = null; try { await __classPrivateFieldGet(this, _VatSupervisor_dispatch, "f").call(this, harden(params)); } catch (error) { // Handle delivery errors deliveryError = error instanceof Error ? error.message : String(error); __classPrivateFieldGet(this, _VatSupervisor_logger, "f").error(`Delivery error in vat ${this.id}:`, deliveryError); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return [__classPrivateFieldGet(this, _VatSupervisor_vatKVStore, "f").checkpoint(), deliveryError]; }, _VatSupervisor_initVat = /** * Initialize the vat by loading its user code bundle and creating a liveslots * instance to manage it. * * @param vatConfig - Configuration object describing the vat to be intialized. * @param state - A Map representing the current persistent state of the vat. * * @returns a promise for a checkpoint of the new vat. */ async function _VatSupervisor_initVat(vatConfig, state) { if (__classPrivateFieldGet(this, _VatSupervisor_loaded, "f")) { throw Error('VatSupervisor received initVat after user code already loaded'); } if (!isVatConfig(vatConfig)) { throw Error('VatSupervisor received initVat with bad config parameter'); } // XXX TODO: this check can and should go away once we can handle `bundleName` and `sourceSpec` too if (!('bundleSpec' in vatConfig)) { throw Error('for now, only sourceSpec is support in vatConfig specifications'); } __classPrivateFieldSet(this, _VatSupervisor_loaded, true, "f"); __classPrivateFieldSet(this, _VatSupervisor_vatKVStore, makeVatKVStore(state), "f"); const syscall = makeSupervisorSyscall(this, __classPrivateFieldGet(this, _VatSupervisor_vatKVStore, "f"), __classPrivateFieldGet(this, _VatSupervisor_logger, "f").subLogger({ tags: ['syscall'] })); const liveSlotsOptions = {}; // XXX should be something more real const gcTools = harden({ WeakRef, FinalizationRegistry, waitUntilQuiescent, gcAndFinalize: makeGCAndFinalize(__classPrivateFieldGet(this, _VatSupervisor_logger, "f").subLogger({ tags: ['gc'] })), meterControl: makeDummyMeterControl(), }); const workerEndowments = { console: __classPrivateFieldGet(this, _VatSupervisor_logger, "f").subLogger({ tags: ['console'] }), assert: globalThis.assert, }; const { bundleSpec, parameters } = vatConfig; const fetched = await __classPrivateFieldGet(this, _VatSupervisor_fetchBlob, "f").call(this, bundleSpec); if (!fetched.ok) { throw Error(`fetch of user code ${bundleSpec} failed: ${fetched.status}`); } const bundle = await fetched.json(); const buildVatNamespace = async (lsEndowments, inescapableGlobalProperties) => { const vatNS = await importBundle(bundle, { filePrefix: `vat-${this.id}/...`, endowments: { ...workerEndowments, ...lsEndowments }, inescapableGlobalProperties, }); return vatNS; }; const liveslots = makeLiveSlots(syscall, this.id, __classPrivateFieldGet(this, _VatSupervisor_vatPowers, "f"), liveSlotsOptions, gcTools, __classPrivateFieldGet(this, _VatSupervisor_logger, "f").subLogger({ tags: ['liveslots'] }), buildVatNamespace); __classPrivateFieldSet(this, _VatSupervisor_dispatch, liveslots.dispatch, "f"); const serParam = marshal.toCapData(harden(parameters)); return await __classPrivateFieldGet(this, _VatSupervisor_instances, "m", _VatSupervisor_deliver).call(this, harden(['startVat', serParam])); }; //# sourceMappingURL=VatSupervisor.mjs.map