UNPKG

@metamask/ocap-kernel

Version:
1 lines 16 kB
{"version":3,"file":"VatSupervisor.mjs","sourceRoot":"","sources":["../src/VatSupervisor.ts"],"names":[],"mappings":"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","sourcesContent":["import { makeLiveSlots as localMakeLiveSlots } from '@agoric/swingset-liveslots';\nimport type {\n VatDeliveryObject,\n VatSyscallObject,\n VatSyscallResult,\n} from '@agoric/swingset-liveslots';\nimport { importBundle } from '@endo/import-bundle';\nimport { makeMarshal } from '@endo/marshal';\nimport type { CapData } from '@endo/marshal';\nimport { StreamReadError } from '@metamask/kernel-errors';\nimport { RpcClient, RpcService } from '@metamask/kernel-rpc-methods';\nimport type { VatKVStore } from '@metamask/kernel-store';\nimport { waitUntilQuiescent } from '@metamask/kernel-utils';\nimport type { JsonRpcMessage } from '@metamask/kernel-utils';\nimport type { Logger } from '@metamask/logger';\nimport { serializeError } from '@metamask/rpc-errors';\nimport type { DuplexStream } from '@metamask/streams';\nimport { isJsonRpcRequest, isJsonRpcResponse } from '@metamask/utils';\n\nimport { vatSyscallMethodSpecs, vatHandlers } from './rpc/index.ts';\nimport { makeGCAndFinalize } from './services/gc-finalize.ts';\nimport { makeDummyMeterControl } from './services/meter-control.ts';\nimport { makeSupervisorSyscall } from './services/syscall.ts';\nimport type { DispatchFn, MakeLiveSlotsFn, GCTools } from './services/types.ts';\nimport { makeVatKVStore } from './store/vat-kv-store.ts';\nimport type { VatConfig, VatDeliveryResult, VatId } from './types.ts';\nimport { isVatConfig, coerceVatSyscallObject } from './types.ts';\n\nconst makeLiveSlots: MakeLiveSlotsFn = localMakeLiveSlots;\n\n// eslint-disable-next-line n/no-unsupported-features/node-builtins\nexport type FetchBlob = (bundleURL: string) => Promise<Response>;\n\ntype SupervisorRpcClient = Pick<\n RpcClient<typeof vatSyscallMethodSpecs>,\n 'notify' | 'handleResponse'\n>;\n\ntype SupervisorConstructorProps = {\n id: VatId;\n kernelStream: DuplexStream<JsonRpcMessage, JsonRpcMessage>;\n logger: Logger;\n vatPowers?: Record<string, unknown> | undefined;\n fetchBlob?: FetchBlob;\n};\n\nconst marshal = makeMarshal(undefined, undefined, {\n serializeBodyFormat: 'smallcaps',\n});\n\nexport class VatSupervisor {\n /** The id of the vat being supervised */\n readonly id: VatId;\n\n /** Communications channel between this vat and the kernel */\n readonly #kernelStream: DuplexStream<JsonRpcMessage, JsonRpcMessage>;\n\n /** The logger for this vat */\n readonly #logger: Logger;\n\n /** RPC client for sending syscall requests to the kernel */\n readonly #rpcClient: SupervisorRpcClient;\n\n /** RPC service for handling requests from the kernel */\n readonly #rpcService: RpcService<typeof vatHandlers>;\n\n /** Flag that the user code has been loaded */\n #loaded: boolean = false;\n\n /** Function to dispatch deliveries into liveslots */\n #dispatch: DispatchFn | null;\n\n /** In-memory KVStore cache for this vat. */\n #vatKVStore: VatKVStore | undefined;\n\n /** External capabilities for this vat. */\n readonly #vatPowers: Record<string, unknown>;\n\n /** Capability to fetch the bundle of code to run in this vat. */\n readonly #fetchBlob: FetchBlob;\n\n /**\n * Construct a new VatSupervisor instance.\n *\n * @param params - Named constructor parameters.\n * @param params.id - The id of the vat being supervised.\n * @param params.kernelStream - Communications channel connected to the kernel.\n * @param params.logger - The logger for this vat.\n * @param params.vatPowers - The external capabilities for this vat.\n * @param params.fetchBlob - Function to fetch the user code bundle for this vat.\n */\n constructor({\n id,\n kernelStream,\n logger,\n vatPowers,\n fetchBlob,\n }: SupervisorConstructorProps) {\n this.id = id;\n this.#kernelStream = kernelStream;\n this.#logger = logger;\n this.#vatPowers = vatPowers ?? {};\n this.#dispatch = null;\n const defaultFetchBlob: FetchBlob = async (bundleURL: string) =>\n await fetch(bundleURL);\n this.#fetchBlob = fetchBlob ?? defaultFetchBlob;\n\n this.#rpcClient = new RpcClient(\n vatSyscallMethodSpecs,\n async (request) => {\n await this.#kernelStream.write(request);\n },\n `${this.id}:`,\n this.#logger.subLogger({ tags: ['rpc-client'] }),\n );\n\n this.#rpcService = new RpcService(vatHandlers, {\n initVat: this.#initVat.bind(this),\n handleDelivery: this.#deliver.bind(this),\n });\n\n Promise.all([\n this.#kernelStream.drain(this.#handleMessage.bind(this)),\n ]).catch(async (error) => {\n this.#logger.error(\n `Unexpected read error from VatSupervisor \"${this.id}\"`,\n error,\n );\n await this.terminate(new StreamReadError({ vatId: this.id }, error));\n });\n }\n\n /**\n * Terminate the VatSupervisor.\n *\n * @param error - The error to terminate the VatSupervisor with.\n */\n async terminate(error?: Error): Promise<void> {\n await this.#kernelStream.end(error);\n }\n\n /**\n * Handle a message from the kernel.\n *\n * @param message - The vat message to handle.\n */\n async #handleMessage(message: JsonRpcMessage): Promise<void> {\n if (isJsonRpcResponse(message)) {\n this.#rpcClient.handleResponse(message.id as string, message);\n } else if (isJsonRpcRequest(message)) {\n try {\n this.#rpcService.assertHasMethod(message.method);\n const result = await this.#rpcService.execute(\n message.method,\n message.params,\n );\n await this.#kernelStream.write({\n id: message.id,\n result,\n jsonrpc: '2.0',\n });\n } catch (error) {\n await this.#kernelStream.write({\n id: message.id,\n error: serializeError(error),\n jsonrpc: '2.0',\n });\n }\n }\n }\n\n /**\n * Execute a syscall by sending it to the kernel. To support the synchronous\n * requirements of the liveslots interface, it optimistically assumes success;\n * errors will be dealt with at the end of the crank.\n *\n * @param vso - Descriptor of the syscall to be issued.\n *\n * @returns a syscall success result.\n */\n executeSyscall(vso: VatSyscallObject): VatSyscallResult {\n // IMPORTANT: Syscall architecture design explanation:\n // - Vats operate on an \"optimistic execution\" model - they send syscalls and continue execution\n // without waiting for responses, assuming success.\n // - The Kernel processes syscalls synchronously on receipt and failures are caught in VatHandle.\n // - The vat is terminated and the crank is rolled back if a syscall fails.\n this.#rpcClient\n .notify('syscall', coerceVatSyscallObject(vso))\n // Just to please the linter (notifications never reject)\n .catch(() => undefined);\n return ['ok', null];\n }\n\n async #deliver(params: VatDeliveryObject): Promise<VatDeliveryResult> {\n if (!this.#dispatch) {\n throw new Error(`cannot deliver before vat is loaded`);\n }\n\n let deliveryError: string | null = null;\n\n try {\n await this.#dispatch(harden(params));\n } catch (error) {\n // Handle delivery errors\n deliveryError = error instanceof Error ? error.message : String(error);\n this.#logger.error(`Delivery error in vat ${this.id}:`, deliveryError);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return [this.#vatKVStore!.checkpoint(), deliveryError];\n }\n\n /**\n * Initialize the vat by loading its user code bundle and creating a liveslots\n * instance to manage it.\n *\n * @param vatConfig - Configuration object describing the vat to be intialized.\n * @param state - A Map representing the current persistent state of the vat.\n *\n * @returns a promise for a checkpoint of the new vat.\n */\n async #initVat(\n vatConfig: VatConfig,\n state: Map<string, string>,\n ): Promise<VatDeliveryResult> {\n if (this.#loaded) {\n throw Error(\n 'VatSupervisor received initVat after user code already loaded',\n );\n }\n if (!isVatConfig(vatConfig)) {\n throw Error('VatSupervisor received initVat with bad config parameter');\n }\n // XXX TODO: this check can and should go away once we can handle `bundleName` and `sourceSpec` too\n if (!('bundleSpec' in vatConfig)) {\n throw Error(\n 'for now, only sourceSpec is support in vatConfig specifications',\n );\n }\n this.#loaded = true;\n\n this.#vatKVStore = makeVatKVStore(state);\n const syscall = makeSupervisorSyscall(this, this.#vatKVStore);\n const liveSlotsOptions = {}; // XXX should be something more real\n\n const gcTools: GCTools = harden({\n WeakRef,\n FinalizationRegistry,\n waitUntilQuiescent,\n gcAndFinalize: makeGCAndFinalize(),\n meterControl: makeDummyMeterControl(),\n });\n\n const workerEndowments = {\n console: this.#logger.subLogger({ tags: ['console'] }),\n assert: globalThis.assert,\n };\n\n const { bundleSpec, parameters } = vatConfig;\n\n const fetched = await this.#fetchBlob(bundleSpec);\n if (!fetched.ok) {\n throw Error(`fetch of user code ${bundleSpec} failed: ${fetched.status}`);\n }\n const bundle = await fetched.json();\n const buildVatNamespace = async (\n lsEndowments: object,\n inescapableGlobalProperties: object,\n ): Promise<Record<string, unknown>> => {\n const vatNS = await importBundle(bundle, {\n filePrefix: `vat-${this.id}/...`,\n endowments: { ...workerEndowments, ...lsEndowments },\n inescapableGlobalProperties,\n });\n return vatNS;\n };\n\n const liveslots = makeLiveSlots(\n syscall,\n this.id,\n this.#vatPowers,\n liveSlotsOptions,\n gcTools,\n this.#logger.subLogger({ tags: ['liveslots'] }),\n buildVatNamespace,\n );\n\n this.#dispatch = liveslots.dispatch;\n const serParam = marshal.toCapData(harden(parameters)) as CapData<string>;\n\n return await this.#deliver(harden(['startVat', serParam]));\n }\n}\n"]}