@metamask/ocap-kernel
Version:
OCap kernel core components
215 lines • 12.9 kB
JavaScript
"use strict";
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;
Object.defineProperty(exports, "__esModule", { value: true });
exports.VatSupervisor = void 0;
const swingset_liveslots_1 = require("@agoric/swingset-liveslots");
const import_bundle_1 = require("@endo/import-bundle");
const marshal_1 = require("@endo/marshal");
const kernel_errors_1 = require("@metamask/kernel-errors");
const kernel_rpc_methods_1 = require("@metamask/kernel-rpc-methods");
const kernel_utils_1 = require("@metamask/kernel-utils");
const rpc_errors_1 = require("@metamask/rpc-errors");
const utils_1 = require("@metamask/utils");
const index_ts_1 = require("./rpc/index.cjs");
const gc_finalize_ts_1 = require("./services/gc-finalize.cjs");
const meter_control_ts_1 = require("./services/meter-control.cjs");
const syscall_ts_1 = require("./services/syscall.cjs");
const vat_kv_store_ts_1 = require("./store/vat-kv-store.cjs");
const types_ts_1 = require("./types.cjs");
const makeLiveSlots = swingset_liveslots_1.makeLiveSlots;
const marshal = (0, marshal_1.makeMarshal)(undefined, undefined, {
serializeBodyFormat: 'smallcaps',
});
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 kernel_rpc_methods_1.RpcClient(index_ts_1.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 kernel_rpc_methods_1.RpcService(index_ts_1.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 kernel_errors_1.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', (0, types_ts_1.coerceVatSyscallObject)(vso))
// Just to please the linter (notifications never reject)
.catch(() => undefined);
return ['ok', null];
}
}
exports.VatSupervisor = VatSupervisor;
_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 ((0, utils_1.isJsonRpcResponse)(message)) {
__classPrivateFieldGet(this, _VatSupervisor_rpcClient, "f").handleResponse(message.id, message);
}
else if ((0, utils_1.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: (0, rpc_errors_1.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 (!(0, types_ts_1.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, (0, vat_kv_store_ts_1.makeVatKVStore)(state), "f");
const syscall = (0, syscall_ts_1.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: kernel_utils_1.waitUntilQuiescent,
gcAndFinalize: (0, gc_finalize_ts_1.makeGCAndFinalize)(__classPrivateFieldGet(this, _VatSupervisor_logger, "f").subLogger({ tags: ['gc'] })),
meterControl: (0, meter_control_ts_1.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 (0, import_bundle_1.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.cjs.map