UNPKG

@metamask/ocap-kernel

Version:
323 lines 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getVatMethods = getVatMethods; const errors_1 = require("@endo/errors"); const base_ts_1 = require("./base.cjs"); const clist_ts_1 = require("./clist.cjs"); const object_ts_1 = require("./object.cjs"); const promise_ts_1 = require("./promise.cjs"); const reachable_ts_1 = require("./reachable.cjs"); const refcount_ts_1 = require("./refcount.cjs"); const types_ts_1 = require("../../types.cjs"); const parse_ref_ts_1 = require("../utils/parse-ref.cjs"); const reachable_ts_2 = require("../utils/reachable.cjs"); const VAT_CONFIG_BASE = 'vatConfig.'; const VAT_CONFIG_BASE_LEN = VAT_CONFIG_BASE.length; /** * Get a vat store object that provides functionality for managing vat records. * * @param ctx - The store context. * @returns A vat store object that maps various persistent kernel data */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function getVatMethods(ctx) { const { kv } = ctx; const { getPrefixedKeys, getSlotKey, getOwnerKey } = (0, base_ts_1.getBaseMethods)(ctx.kv); const { deleteCListEntry } = (0, clist_ts_1.getCListMethods)(ctx); const { getReachableAndVatSlot } = (0, reachable_ts_1.getReachableMethods)(ctx); const { initKernelPromise, setPromiseDecider, getKernelPromise } = (0, promise_ts_1.getPromiseMethods)(ctx); const { initKernelObject } = (0, object_ts_1.getObjectMethods)(ctx); const { addCListEntry } = (0, clist_ts_1.getCListMethods)(ctx); const { incrementRefCount, decrementRefCount } = (0, refcount_ts_1.getRefCountMethods)(ctx); /** * Delete all persistent state associated with an endpoint. * * @param endpointId - The endpoint whose state is to be deleted. */ function deleteEndpoint(endpointId) { for (const key of getPrefixedKeys(`cle.${endpointId}.`)) { kv.delete(key); } for (const key of getPrefixedKeys(`clk.${endpointId}.`)) { kv.delete(key); } kv.delete(`e.nextObjectId.${endpointId}`); kv.delete(`e.nextPromiseId.${endpointId}`); } /** * Generator that yields the configurations of running vats. * * @yields a series of vat records for all configured vats. */ function* getAllVatRecords() { for (const vatKey of getPrefixedKeys(VAT_CONFIG_BASE)) { const vatID = vatKey.slice(VAT_CONFIG_BASE_LEN); const vatConfig = getVatConfig(vatID); yield { vatID, vatConfig }; } } /** * Get all vat IDs from the store. * * @returns an array of vat IDs. */ function getVatIDs() { return Array.from(getPrefixedKeys(VAT_CONFIG_BASE)).map((vatKey) => vatKey.slice(VAT_CONFIG_BASE_LEN)); } /** * Fetch the stored configuration for a vat. * * @param vatID - The vat whose configuration is sought. * * @returns the configuration for the given vat. */ function getVatConfig(vatID) { return JSON.parse(kv.getRequired(`${VAT_CONFIG_BASE}${vatID}`)); } /** * Check if a vat is active. * * @param vatID - The ID of the vat to check. * @returns True if the vat is active, false otherwise. */ function isVatActive(vatID) { return kv.get(`${VAT_CONFIG_BASE}${vatID}`) !== undefined; } /** * Store the configuration for a vat. * * @param vatID - The vat whose configuration is to be set. * @param vatConfig - The configuration to write. */ function setVatConfig(vatID, vatConfig) { kv.set(`${VAT_CONFIG_BASE}${vatID}`, JSON.stringify(vatConfig)); } /** * Delete the stored configuration for a vat. * * @param vatID - The vat whose configuration is to be deleted. */ function deleteVatConfig(vatID) { kv.delete(`${VAT_CONFIG_BASE}${vatID}`); } /** * Checks if a vat imports the specified kernel slot. * * @param vatID - The ID of the vat to check. * @param kernelSlot - The kernel slot reference. * @returns True if the vat imports the kernel slot, false otherwise. */ function importsKernelSlot(vatID, kernelSlot) { const data = ctx.kv.get(getSlotKey(vatID, kernelSlot)); if (data) { const { vatSlot } = (0, reachable_ts_2.parseReachableAndVatSlot)(data); const { direction } = (0, parse_ref_ts_1.parseRef)(vatSlot); if (direction === 'import') { return true; } } return false; } /** * Gets all vats that import a specific kernel object. * * @param koid - The kernel object ID. * @returns An array of vat IDs that import the kernel object. */ function getImporters(koid) { const importers = []; importers.push(...getVatIDs().filter((vatID) => importsKernelSlot(vatID, koid))); importers.sort(); return importers; } /** * Get the list of terminated vats. * * @returns an array of terminated vat IDs. */ function getTerminatedVats() { return JSON.parse(ctx.terminatedVats.get() ?? '[]'); } /** * Check if a vat is terminated. * * @param vatID - The ID of the vat to check. * @returns True if the vat is terminated, false otherwise. */ function isVatTerminated(vatID) { return getTerminatedVats().includes(vatID); } /** * Add a vat to the list of terminated vats. * * @param vatID - The ID of the vat to add. */ function markVatAsTerminated(vatID) { const terminatedVats = getTerminatedVats(); if (!terminatedVats.includes(vatID)) { terminatedVats.push(vatID); ctx.terminatedVats.set(JSON.stringify(terminatedVats)); } } /** * Remove a vat from the list of terminated vats. * * @param vatID - The ID of the vat to remove. */ function forgetTerminatedVat(vatID) { const terminatedVats = getTerminatedVats().filter((id) => id !== vatID); ctx.terminatedVats.set(JSON.stringify(terminatedVats)); } /** * Cleanup a terminated vat. * * @param vatID - The ID of the vat to cleanup. * @returns The work done during the cleanup. */ function cleanupTerminatedVat(vatID) { const work = { exports: 0, imports: 0, promises: 0, kv: 0, }; if (!isVatTerminated(vatID)) { return work; } const clistPrefix = `${vatID}.c.`; const exportPrefix = `${clistPrefix}o+`; const importPrefix = `${clistPrefix}o-`; const promisePrefix = `${clistPrefix}p`; // Note: ASCII order is "+,-./", and we rely upon this to split the // keyspace into the various o+NN/o-NN/etc spaces. If we were using a // more sophisticated database, we'd keep each section in a separate // table. // The current store semantics ensure this iteration is lexicographic. // Any changes to the creation of the list of promises to be rejected (and // thus to the order in which they *get* rejected) need to preserve this // ordering in order to preserve determinism. // first, scan for exported objects, which must be orphaned for (const key of getPrefixedKeys(exportPrefix)) { // The void for an object exported by a vat will always be of the form // `o+NN`. The '+' means that the vat exported the object (rather than // importing it) and therefore the object is owned by (i.e., within) the // vat. The corresponding void->koid c-list entry will thus always // begin with `vMM.c.o+`. In addition to deleting the c-list entry, we // must also delete the corresponding kernel owner entry for the object, // since the object will no longer be accessible. assert(key.startsWith(clistPrefix), key); const vref = key.slice(clistPrefix.length); assert(vref.startsWith('o+'), vref); const kref = ctx.kv.get(key); assert(kref, key); // deletes c-list and .owner, adds to maybeFreeKrefs const ownerKey = getOwnerKey(kref); const ownerVat = ctx.kv.get(ownerKey); ownerVat === vatID || (0, errors_1.Fail) `export ${kref} not owned by old vat`; ctx.kv.delete(ownerKey); const { vatSlot } = getReachableAndVatSlot(vatID, kref); ctx.kv.delete(getSlotKey(vatID, kref)); ctx.kv.delete(getSlotKey(vatID, vatSlot)); // Decrease refcounts that belonged to the terminating vat decrementRefCount(kref, 'cleanup|export|baseline'); ctx.maybeFreeKrefs.add(kref); work.exports += 1; } // then scan for imported objects, which must be decrefed for (const key of getPrefixedKeys(importPrefix)) { // abandoned imports: delete the clist entry as if the vat did a // drop+retire const kref = ctx.kv.get(key) ?? (0, errors_1.Fail) `getNextKey ensures get`; assert(key.startsWith(clistPrefix), key); const vref = key.slice(clistPrefix.length); deleteCListEntry(vatID, kref, vref); // that will also delete both db keys work.imports += 1; } // The caller used enumeratePromisesByDecider() before calling us, // so they have already rejected the orphan promises, but those // kpids are still present in the dead vat's c-list. Clean those up now. for (const key of getPrefixedKeys(promisePrefix)) { const kref = ctx.kv.get(key) ?? (0, errors_1.Fail) `getNextKey ensures get`; assert(key.startsWith(clistPrefix), key); const vref = key.slice(clistPrefix.length); // the following will also delete both db keys deleteCListEntry(vatID, kref, vref); // If the dead vat was still the decider, drop the decider’s refcount, too. const kp = getKernelPromise(kref); if (kp.decider === vatID) { decrementRefCount(kref, 'cleanup|promise|decider'); } work.promises += 1; } // Finally, clean up any remaining KV entries for this vat for (const key of getPrefixedKeys(`${vatID}.`)) { ctx.kv.delete(key); work.kv += 1; } // Clean up any remaining c-list entries and vat-specific counters deleteEndpoint(vatID); // Remove the vat from the terminated vats list forgetTerminatedVat(vatID); // Log the cleanup work done console.debug(`Cleaned up terminated vat ${vatID}:`, work); return work; } /** * Get the next terminated vat to cleanup. * * @returns The work done during the cleanup. */ function nextTerminatedVatCleanup() { const vatID = getTerminatedVats()?.[0]; vatID && cleanupTerminatedVat(vatID); return getTerminatedVats().length > 0; } /** * Create the kernel's representation of an export from a vat. * * @param vatId - The vat doing the exporting. * @param vref - The vat's ref for the entity in question. * * @returns the kref corresponding to the export of `vref` from `vatId`. */ function exportFromVat(vatId, vref) { (0, types_ts_1.insistVatId)(vatId); const { isPromise, context, direction } = (0, parse_ref_ts_1.parseRef)(vref); assert(context === 'vat', `${vref} is not a VRef`); assert(direction === 'export', `${vref} is not an export reference`); let kref; if (isPromise) { kref = initKernelPromise()[0]; setPromiseDecider(kref, vatId); } else { kref = initKernelObject(vatId); } addCListEntry(vatId, kref, vref); incrementRefCount(kref, 'export', { isExport: true, onlyRecognizable: true, }); return kref; } return { deleteEndpoint, getAllVatRecords, getVatConfig, setVatConfig, deleteVatConfig, getVatIDs, importsKernelSlot, getImporters, getTerminatedVats, markVatAsTerminated, forgetTerminatedVat, isVatTerminated, cleanupTerminatedVat, nextTerminatedVatCleanup, exportFromVat, isVatActive, }; } //# sourceMappingURL=vat.cjs.map