@metamask/ocap-kernel
Version:
OCap kernel core components
526 lines • 24.5 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 _Kernel_instances, _Kernel_commandStream, _Kernel_rpcService, _Kernel_vats, _Kernel_vatWorkerService, _Kernel_kernelStore, _Kernel_logger, _Kernel_kernelQueue, _Kernel_kernelRouter, _Kernel_init, _Kernel_handleCommandMessage, _Kernel_launchVat, _Kernel_runVat, _Kernel_launchVatsForSubcluster, _Kernel_stopVat, _Kernel_getVat, _Kernel_resetKernelState;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Kernel = void 0;
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 logger_1 = require("@metamask/logger");
const rpc_errors_1 = require("@metamask/rpc-errors");
const utils_1 = require("@metamask/utils");
const KernelQueue_ts_1 = require("./KernelQueue.cjs");
const KernelRouter_ts_1 = require("./KernelRouter.cjs");
const index_ts_1 = require("./rpc/index.cjs");
const kernel_marshal_ts_1 = require("./services/kernel-marshal.cjs");
const index_ts_2 = require("./store/index.cjs");
const types_ts_1 = require("./types.cjs");
const assert_ts_1 = require("./utils/assert.cjs");
const VatHandle_ts_1 = require("./VatHandle.cjs");
class Kernel {
/**
* Construct a new kernel instance.
*
* @param commandStream - Command channel from whatever external software is driving the kernel.
* @param vatWorkerService - Service to create a worker in which a new vat can run.
* @param kernelDatabase - Database holding the kernel's persistent state.
* @param options - Options for the kernel constructor.
* @param options.resetStorage - If true, the storage will be cleared.
* @param options.logger - Optional logger for error and diagnostic output.
*/
// eslint-disable-next-line no-restricted-syntax
constructor(commandStream, vatWorkerService, kernelDatabase, options = {}) {
_Kernel_instances.add(this);
/** Command channel from the controlling console/browser extension/test driver */
_Kernel_commandStream.set(this, void 0);
_Kernel_rpcService.set(this, void 0);
/** Currently running vats, by ID */
_Kernel_vats.set(this, void 0);
/** Service to spawn workers (in iframes) for vats to run in */
_Kernel_vatWorkerService.set(this, void 0);
/** Storage holding the kernel's own persistent state */
_Kernel_kernelStore.set(this, void 0);
/** Logger for outputting messages (such as errors) to the console */
_Kernel_logger.set(this, void 0);
/** The kernel's run queue */
_Kernel_kernelQueue.set(this, void 0);
/** The kernel's router */
_Kernel_kernelRouter.set(this, void 0);
__classPrivateFieldSet(this, _Kernel_commandStream, commandStream, "f");
__classPrivateFieldSet(this, _Kernel_rpcService, new kernel_rpc_methods_1.RpcService(index_ts_1.kernelHandlers, {}), "f");
__classPrivateFieldSet(this, _Kernel_vats, new Map(), "f");
__classPrivateFieldSet(this, _Kernel_vatWorkerService, vatWorkerService, "f");
__classPrivateFieldSet(this, _Kernel_logger, options.logger ?? new logger_1.Logger('ocap-kernel'), "f");
__classPrivateFieldSet(this, _Kernel_kernelStore, (0, index_ts_2.makeKernelStore)(kernelDatabase), "f");
if (options.resetStorage) {
__classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_resetKernelState).call(this);
}
__classPrivateFieldSet(this, _Kernel_kernelQueue, new KernelQueue_ts_1.KernelQueue(__classPrivateFieldGet(this, _Kernel_kernelStore, "f"), this.terminateVat.bind(this)), "f");
__classPrivateFieldSet(this, _Kernel_kernelRouter, new KernelRouter_ts_1.KernelRouter(__classPrivateFieldGet(this, _Kernel_kernelStore, "f"), __classPrivateFieldGet(this, _Kernel_kernelQueue, "f"), __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_getVat).bind(this)), "f");
harden(this);
}
/**
* Create a new kernel instance.
*
* @param commandStream - Command channel from whatever external software is driving the kernel.
* @param vatWorkerService - Service to create a worker in which a new vat can run.
* @param kernelDatabase - Database holding the kernel's persistent state.
* @param options - Options for the kernel constructor.
* @param options.resetStorage - If true, the storage will be cleared.
* @param options.logger - Optional logger for error and diagnostic output.
* @returns A promise for the new kernel instance.
*/
static async make(commandStream, vatWorkerService, kernelDatabase, options = {}) {
const kernel = new Kernel(commandStream, vatWorkerService, kernelDatabase, options);
await __classPrivateFieldGet(kernel, _Kernel_instances, "m", _Kernel_init).call(kernel);
return kernel;
}
/**
* Send a message from the kernel to an object in a vat.
*
* @param target - The object to which the message is directed.
* @param method - The method to be invoked.
* @param args - Message arguments.
*
* @returns a promise for the (CapData encoded) result of the message invocation.
*/
async queueMessage(target, method, args) {
return __classPrivateFieldGet(this, _Kernel_kernelQueue, "f").enqueueMessage(target, method, args);
}
/**
* Launches a sub-cluster of vats.
*
* @param config - Configuration object for sub-cluster.
* @returns a promise for the (CapData encoded) result of the bootstrap message.
*/
async launchSubcluster(config) {
(0, types_ts_1.isClusterConfig)(config) || (0, assert_ts_1.Fail) `invalid cluster config`;
if (!config.vats[config.bootstrap]) {
(0, assert_ts_1.Fail) `invalid bootstrap vat name ${config.bootstrap}`;
}
const subclusterId = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").addSubcluster(config);
return __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_launchVatsForSubcluster).call(this, subclusterId, config);
}
/**
* Terminates a named sub-cluster of vats.
*
* @param subclusterId - The id of the subcluster to terminate.
* @returns A promise that resolves when termination is complete.
*/
async terminateSubcluster(subclusterId) {
if (!__classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubcluster(subclusterId)) {
throw new kernel_errors_1.SubclusterNotFoundError(subclusterId);
}
const vatIdsToTerminate = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubclusterVats(subclusterId);
for (const vatId of vatIdsToTerminate.reverse()) {
await this.terminateVat(vatId);
this.collectGarbage();
}
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").deleteSubcluster(subclusterId);
}
/**
* Reloads a named subcluster by restarting all its vats.
* This terminates and restarts all vats in the subcluster.
*
* @param subclusterId - The id of the subcluster to reload.
* @returns A promise for an object containing the subcluster.
* @throws If the subcluster is not found.
*/
async reloadSubcluster(subclusterId) {
const subcluster = this.getSubcluster(subclusterId);
if (!subcluster) {
throw new kernel_errors_1.SubclusterNotFoundError(subclusterId);
}
for (const vatId of subcluster.vats.reverse()) {
await this.terminateVat(vatId);
this.collectGarbage();
}
const newId = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").addSubcluster(subcluster.config);
await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_launchVatsForSubcluster).call(this, newId, subcluster.config);
const newSubcluster = this.getSubcluster(newId);
if (!newSubcluster) {
throw new kernel_errors_1.SubclusterNotFoundError(newId);
}
return newSubcluster;
}
/**
* Retrieves a subcluster by its ID.
*
* @param subclusterId - The id of the subcluster.
* @returns The subcluster, or undefined if not found.
*/
getSubcluster(subclusterId) {
return __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubcluster(subclusterId);
}
/**
* Gets all subclusters.
*
* @returns An array of subcluster information records.
*/
getSubclusters() {
return __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubclusters();
}
/**
* Checks if a vat belongs to a specific subcluster.
*
* @param vatId - The ID of the vat to check.
* @param subclusterId - The ID of the subcluster to check against.
* @returns True if the vat belongs to the specified subcluster, false otherwise.
*/
isVatInSubcluster(vatId, subclusterId) {
return __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getVatSubcluster(vatId) === subclusterId;
}
/**
* Gets all vat IDs that belong to a specific subcluster.
*
* @param subclusterId - The ID of the subcluster to get vats for.
* @returns An array of vat IDs that belong to the specified subcluster.
*/
getSubclusterVats(subclusterId) {
return __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubclusterVats(subclusterId);
}
/**
* Restarts a vat.
*
* @param vatId - The ID of the vat.
* @returns A promise for the restarted vat.
*/
async restartVat(vatId) {
const vat = __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_getVat).call(this, vatId);
if (!vat) {
throw new kernel_errors_1.VatNotFoundError(vatId);
}
const { config } = vat;
await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_stopVat).call(this, vatId, false);
await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_runVat).call(this, vatId, config);
return vat;
}
/**
* Terminate a vat with extreme prejudice.
*
* @param vatId - The ID of the vat.
* @param reason - If the vat is being terminated, the reason for the termination.
*/
async terminateVat(vatId, reason) {
await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_stopVat).call(this, vatId, true, reason);
// Mark for deletion (which will happen later, in vat-cleanup events)
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").markVatAsTerminated(vatId);
}
/**
* Clear the database.
*/
async clearStorage() {
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").clear();
}
/**
* Gets a list of the IDs of all running vats.
*
* @returns An array of vat IDs.
*/
getVatIds() {
return Array.from(__classPrivateFieldGet(this, _Kernel_vats, "f").keys());
}
/**
* Gets a list of information about all running vats.
*
* @returns An array of vat information records.
*/
getVats() {
return Array.from(__classPrivateFieldGet(this, _Kernel_vats, "f").values()).map((vat) => {
const subclusterId = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getVatSubcluster(vat.vatId);
return {
id: vat.vatId,
config: vat.config,
subclusterId,
};
});
}
/**
* Revoke an exported object. Idempotent. Revoking promises is not supported.
*
* @param kref - The KRef of the object to revoke.
* @throws If the object is a promise.
*/
revoke(kref) {
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").revoke(kref);
}
/**
* Check if an object is revoked.
*
* @param kref - The KRef of the object to check.
* @returns True if the object is revoked, false otherwise.
*/
isRevoked(kref) {
return __classPrivateFieldGet(this, _Kernel_kernelStore, "f").isRevoked(kref);
}
/**
* Get the current kernel status, defined as the current cluster configuration
* and a list of all running vats.
*
* @returns The current kernel status containing vats and subclusters information.
*/
getStatus() {
return {
vats: this.getVats(),
subclusters: __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubclusters(),
};
}
/**
* Reap vats that match the filter.
*
* @param filter - A function that returns true if the vat should be reaped.
*/
reapVats(filter = () => true) {
for (const vatID of this.getVatIds()) {
if (filter(vatID)) {
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").scheduleReap(vatID);
}
}
}
/**
* Pin a vat root.
*
* @param vatId - The ID of the vat.
* @returns The KRef of the vat root.
*/
pinVatRoot(vatId) {
const kref = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getRootObject(vatId);
if (!kref) {
throw new kernel_errors_1.VatNotFoundError(vatId);
}
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").pinObject(kref);
return kref;
}
/**
* Unpin a vat root.
*
* @param vatId - The ID of the vat.
*/
unpinVatRoot(vatId) {
const kref = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getRootObject(vatId);
if (!kref) {
throw new kernel_errors_1.VatNotFoundError(vatId);
}
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").unpinObject(kref);
}
/**
* Ping a vat.
*
* @param vatId - The ID of the vat.
* @returns A promise that resolves to the result of the ping.
*/
async pingVat(vatId) {
const vat = __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_getVat).call(this, vatId);
return vat.ping();
}
/**
* Stop all running vats and reset the kernel state.
* This is for debugging purposes only.
*/
async reset() {
await this.terminateAllVats();
__classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_resetKernelState).call(this);
}
/**
* Terminate all vats and collect garbage.
* This is for debugging purposes only.
*/
async terminateAllVats() {
for (const id of this.getVatIds().reverse()) {
await this.terminateVat(id);
this.collectGarbage();
}
}
/**
* Terminate all running vats and reload them.
* This is for debugging purposes only.
*/
async reload() {
const subclusters = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getSubclusters();
await this.terminateAllVats();
for (const subcluster of subclusters) {
const newId = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").addSubcluster(subcluster.config);
await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_launchVatsForSubcluster).call(this, newId, subcluster.config);
// Wait for run queue to be empty before proceeding to next subcluster
await (0, kernel_utils_1.delay)(100);
}
}
/**
* Collect garbage.
* This is for debugging purposes only.
*/
collectGarbage() {
while (__classPrivateFieldGet(this, _Kernel_kernelStore, "f").nextTerminatedVatCleanup()) {
// wait for all vats to be cleaned up
}
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").collectGarbage();
}
}
exports.Kernel = Kernel;
_Kernel_commandStream = new WeakMap(), _Kernel_rpcService = new WeakMap(), _Kernel_vats = new WeakMap(), _Kernel_vatWorkerService = new WeakMap(), _Kernel_kernelStore = new WeakMap(), _Kernel_logger = new WeakMap(), _Kernel_kernelQueue = new WeakMap(), _Kernel_kernelRouter = new WeakMap(), _Kernel_instances = new WeakSet(), _Kernel_init =
/**
* Start the kernel running. Sets it up to actually receive command messages
* and then begin processing the run queue.
*/
async function _Kernel_init() {
__classPrivateFieldGet(this, _Kernel_commandStream, "f")
.drain(__classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_handleCommandMessage).bind(this))
.catch((error) => {
__classPrivateFieldGet(this, _Kernel_logger, "f").error('Stream read error:', error);
throw new kernel_errors_1.StreamReadError({ kernelId: 'kernel' }, error);
});
const starts = [];
for (const { vatID, vatConfig } of __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getAllVatRecords()) {
starts.push(__classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_runVat).call(this, vatID, vatConfig));
}
await Promise.all(starts);
__classPrivateFieldGet(this, _Kernel_kernelQueue, "f")
.run(__classPrivateFieldGet(this, _Kernel_kernelRouter, "f").deliver.bind(__classPrivateFieldGet(this, _Kernel_kernelRouter, "f")))
.catch((error) => {
__classPrivateFieldGet(this, _Kernel_logger, "f").error('Run loop error:', error);
throw error;
});
}, _Kernel_handleCommandMessage =
/**
* Handle messages received over the command channel.
*
* @param message - The message to handle.
*/
async function _Kernel_handleCommandMessage(message) {
try {
__classPrivateFieldGet(this, _Kernel_rpcService, "f").assertHasMethod(message.method);
const result = await __classPrivateFieldGet(this, _Kernel_rpcService, "f").execute(message.method, message.params);
if ((0, utils_1.hasProperty)(message, 'id') && typeof message.id === 'string') {
await __classPrivateFieldGet(this, _Kernel_commandStream, "f").write({
id: message.id,
jsonrpc: '2.0',
result,
});
}
}
catch (error) {
__classPrivateFieldGet(this, _Kernel_logger, "f").error('Error executing command', error);
if ((0, utils_1.hasProperty)(message, 'id') && typeof message.id === 'string') {
await __classPrivateFieldGet(this, _Kernel_commandStream, "f").write({
id: message.id,
jsonrpc: '2.0',
error: (0, rpc_errors_1.serializeError)(error),
});
}
}
}, _Kernel_launchVat =
/**
* Launches a new vat.
*
* @param vatConfig - Configuration for the new vat.
* @param subclusterId - The ID of the subcluster to launch the vat in. Optional.
* @returns a promise for the KRef of the new vat's root object.
*/
async function _Kernel_launchVat(vatConfig, subclusterId) {
const vatId = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").getNextVatId();
await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_runVat).call(this, vatId, vatConfig);
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").initEndpoint(vatId);
const rootRef = __classPrivateFieldGet(this, _Kernel_kernelStore, "f").exportFromVat(vatId, types_ts_1.ROOT_OBJECT_VREF);
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").setVatConfig(vatId, vatConfig);
if (subclusterId) {
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").addSubclusterVat(subclusterId, vatId);
}
return rootRef;
}, _Kernel_runVat =
/**
* Start a new or resurrected vat running.
*
* @param vatId - The ID of the vat to start.
* @param vatConfig - Its configuration.
*/
async function _Kernel_runVat(vatId, vatConfig) {
if (__classPrivateFieldGet(this, _Kernel_vats, "f").has(vatId)) {
throw new kernel_errors_1.VatAlreadyExistsError(vatId);
}
const stream = await __classPrivateFieldGet(this, _Kernel_vatWorkerService, "f").launch(vatId, vatConfig);
const { kernelStream: vatStream, loggerStream } = (0, logger_1.splitLoggerStream)(stream);
const vatLogger = __classPrivateFieldGet(this, _Kernel_logger, "f").subLogger({ tags: [vatId] });
vatLogger.injectStream(loggerStream, (error) => __classPrivateFieldGet(this, _Kernel_logger, "f").error(`Vat ${vatId} error: ${(0, kernel_utils_1.stringify)(error)}`));
const vat = await VatHandle_ts_1.VatHandle.make({
vatId,
vatConfig,
vatStream,
kernelStore: __classPrivateFieldGet(this, _Kernel_kernelStore, "f"),
kernelQueue: __classPrivateFieldGet(this, _Kernel_kernelQueue, "f"),
logger: vatLogger,
});
__classPrivateFieldGet(this, _Kernel_vats, "f").set(vatId, vat);
}, _Kernel_launchVatsForSubcluster =
/**
* Launches all vats for a subcluster and sets up their bootstrap connections.
*
* @param subclusterId - The ID of the subcluster to launch vats for.
* @param config - The configuration for the subcluster.
* @returns A promise for the (CapData encoded) result of the bootstrap message, if any.
*/
async function _Kernel_launchVatsForSubcluster(subclusterId, config) {
const rootIds = {};
const roots = {};
for (const [vatName, vatConfig] of Object.entries(config.vats)) {
const rootRef = await __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_launchVat).call(this, vatConfig, subclusterId);
rootIds[vatName] = rootRef;
roots[vatName] = (0, kernel_marshal_ts_1.kslot)(rootRef, 'vatRoot');
}
const bootstrapRoot = rootIds[config.bootstrap];
if (bootstrapRoot) {
return this.queueMessage(bootstrapRoot, 'bootstrap', [roots]);
}
return undefined;
}, _Kernel_stopVat =
/**
* Stop a vat from running.
*
* Note that after this operation, the vat will be in a weird twilight zone
* between existence and nonexistence, so this operation should only be used
* as a component of vat restart (which will push it back into existence) or
* vat termination (which will push it all the way into nonexistence).
*
* @param vatId - The ID of the vat.
* @param terminating - If true, the vat is being killed, if false, it's being
* restarted.
* @param reason - If the vat is being terminated, the reason for the termination.
*/
async function _Kernel_stopVat(vatId, terminating, reason) {
const vat = __classPrivateFieldGet(this, _Kernel_instances, "m", _Kernel_getVat).call(this, vatId);
if (!vat) {
throw new kernel_errors_1.VatNotFoundError(vatId);
}
let terminationError;
if (reason) {
terminationError = new Error(`Vat termination: ${reason.body}`);
}
else if (terminating) {
terminationError = new kernel_errors_1.VatDeletedError(vatId);
}
await __classPrivateFieldGet(this, _Kernel_vatWorkerService, "f")
.terminate(vatId, terminationError)
.catch(__classPrivateFieldGet(this, _Kernel_logger, "f").error);
await vat.terminate(terminating, terminationError);
__classPrivateFieldGet(this, _Kernel_vats, "f").delete(vatId);
}, _Kernel_getVat = function _Kernel_getVat(vatId) {
const vat = __classPrivateFieldGet(this, _Kernel_vats, "f").get(vatId);
if (vat === undefined) {
throw new kernel_errors_1.VatNotFoundError(vatId);
}
return vat;
}, _Kernel_resetKernelState = function _Kernel_resetKernelState() {
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").clear();
__classPrivateFieldGet(this, _Kernel_kernelStore, "f").reset();
};
harden(Kernel);
//# sourceMappingURL=Kernel.cjs.map