From 9c9c572eab0907b168241d0dd7f4928e1c65a908 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 21 Sep 2024 11:04:13 -1000 Subject: [PATCH] refactor(gems): rewrite on durable zone + liveslots vomkit --- packages/gems/package.json | 2 + packages/gems/src/index.js | 322 ++++++++++------------------- packages/gems/src/kumavis-store.js | 77 ------- packages/gems/test/index.test.js | 294 ++++++++++---------------- packages/gems/test/util.js | 68 +++--- yarn.lock | 183 ++++++++++++++-- 6 files changed, 421 insertions(+), 525 deletions(-) delete mode 100644 packages/gems/src/kumavis-store.js diff --git a/packages/gems/package.json b/packages/gems/package.json index 5bcb730e58..ac80783cd5 100644 --- a/packages/gems/package.json +++ b/packages/gems/package.json @@ -36,6 +36,8 @@ "lint:eslint": "eslint '**/*.js'" }, "dependencies": { + "@agoric/swingset-liveslots": "0.10.3-u16.1", + "@agoric/zone": "0.3.0-u16.1", "@endo/captp": "workspace:^", "@endo/exo": "workspace:^", "@endo/far": "workspace:^", diff --git a/packages/gems/src/index.js b/packages/gems/src/index.js index 15930dc8ce..61f1030e66 100644 --- a/packages/gems/src/index.js +++ b/packages/gems/src/index.js @@ -1,18 +1,10 @@ -/* global setTimeout */ - import { makeCapTP } from '@endo/captp'; -import { makeExo } from '@endo/exo'; -import { getInterfaceMethodKeys, M } from '@endo/patterns'; +import { makeDurableZone } from '@agoric/zone/durable.js'; +import { M } from '@endo/patterns'; /** @import { Stream } from '@endo/stream' */ -const noop = (...args) => {}; -const never = new Promise(() => {}); -const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); - -export const util = { noop, never, delay }; - -const getRandomId = () => Math.random().toString(36).slice(2); +const initWithPassthrough = ({ ...args } = {}) => harden({ ...args }); /** * @template TBootstrap @@ -53,221 +45,129 @@ export const makeMessageCapTP = ( }; }; -const makePersistenceNode = () => { - let value; - return { - get() { - return value; - }, - set(newValue) { - if (typeof newValue !== 'string') { - throw new Error( - `persistence node expected string (got "${typeof newValue}")`, - ); - } - value = newValue; - }, +export const makeKernel = baggage => { + const zone = makeDurableZone(baggage); + const rootStore = zone.mapStore('rootStore'); + const rootGemZone = zone.subZone('RootGemZone'); + + /* + GemZone: + (store) 'data': { recipe } + (subzone) 'gemRegistry': SubZone + */ + + // "GemNamespaces" are created bc zones dont allow you to lookup subZones by name more than once + // so this loads the subZones and stores only once. registered child namespaces are cached. + // Additionally, you cant ask what subzones exist. so we need to keep track of them ourselves. + // So GemNamespaces serve as an abstraction for managing the gem class registration tree + const loadGemNamespaceFromGemZone = gemZone => { + const childCache = new Map(); + const data = gemZone.mapStore('data'); + const registry = gemZone.subZone('gemRegistry'); + const childKeys = gemZone.setStore('childKeys'); + let exoClass; + const namespace = { + initRecipe(gemRecipe) { + data.init('recipe', harden(gemRecipe)); + }, + getRecipe() { + return data.get('recipe'); + }, + // methods + lookupChild(name) { + if (childCache.has(name)) { + return childCache.get(name); + } + childKeys.add(name); + const childGemZone = registry.subZone(name); + const chilldNamespace = loadGemNamespaceFromGemZone(childGemZone); + childCache.set(name, chilldNamespace); + return chilldNamespace; + }, + getChildKeys() { + return Array.from(childKeys.values()); + }, + exoClass(...args) { + if (exoClass === undefined) { + exoClass = gemZone.exoClass(...args); + } + return exoClass; + }, + }; + return namespace; }; -}; - -const makeWakeController = ({ name, makeFacet }) => { - let isAwake = false; - let target; - let currentFacetId; - let controller; - - const triggerSleep = targetFacetId => { - if (currentFacetId === targetFacetId) { - console.log( - `gem:${name}/facet:${targetFacetId} being put to sleep (due to GC)`, - ); - controller.sleep(); - } + // stores a gem recipe in the registry. for each gem, called once per universe. + const registerGem = (parentGemNamespace, gemRecipe) => { + const { name } = gemRecipe; + const gemNs = parentGemNamespace.lookupChild(name); + gemNs.initRecipe(harden(gemRecipe)); }; - // is it theoretically possible for a facet to get GC'd while handling a message? - // i was unable to observe it happening - const registry = new FinalizationRegistry(facetId => { - console.log(`gem:${name}/facet:${facetId} has been garbage collected.`); - triggerSleep(facetId); - }); - - controller = { - async wake() { - await null; - // need to handle case where marked as awake but target is garbage collected - if (isAwake && target && target.deref() !== undefined) - return target.deref(); - // bug when wake is inflight - const facetId = Math.random().toString(36).slice(2); - // simulate startup process - await delay(200); - const facet = await makeFacet({ - // for debugging: - facetId, - }); - target = new WeakRef(facet); - currentFacetId = facetId; - registry.register(facet, facetId); - console.log(`gem:${name}/facet:${facetId} created`); - isAwake = true; - return facet; - }, - async sleep() { - await null; - if (!isAwake) return; - console.log(`gem:${name}/facet:${currentFacetId} being put to sleep`); - // simulate shutdown process - // bug when sleep is inflight - await delay(200); - target = undefined; - isAwake = false; - }, - isAwake() { - return isAwake; - }, - assertAwake() { - if (!isAwake) { - throw new Error('not awake'); + // used internally. defines a registered gem class. for each gem, called once per process. + const loadGem = (parentGemNamespace, name) => { + const gemNs = parentGemNamespace.lookupChild(name); + const gemRecipe = gemNs.getRecipe(); + const { code } = gemRecipe; + const defineChildGem = childGemRecipe => { + // ignore if already registered + if (gemNs.getChildKeys().includes(childGemRecipe.name)) { + return; } - }, - }; - - return controller; -}; - -const makeWrapper = (name, wakeController, methodNames) => { - return Object.fromEntries( - methodNames.map(methodName => { - return [ - methodName, - async function wrapperFn(...args) { - console.log(`gem:${name} ${methodName} called`); - const facet = await wakeController.wake(); - // load bearing codesmell against unseen enemies. - // uhhhh, use (return-await + noop) to retain a strong reference to `facet` to - // to prevent GC of facet before fully responding to a message. - // we want the GC event to imply that the facet is no longer in use. - // but we dont get many guarantees about when the GC event will fire. - // this problem has not been witnessed, - // this solution has not been verified. - const result = await facet[methodName](...args); - noop(facet); - return result; - }, - ]; - }), - ); -}; - -const makeGemFactory = ({ gemController }) => { - const makeGem = ({ name, makeFacet, interface: iface }) => { - const gemId = `gem:${getRandomId()}`; - console.log(`${gemId} created ("${name}")`); - - const gemLookup = gemController.getLookup(); - const persistenceNode = makePersistenceNode(); - const retentionSet = new Set(); - - const incarnateEvalGem = async ({ - name: childName, - interface: childIface, - code, - }) => { - const compartment = new Compartment({ M }); - const childMakeFacet = compartment.evaluate(code); - const { gemId: childGemId, exo } = await makeGem({ - name: childName, - makeFacet: childMakeFacet, - interface: childIface, - }); - return { gemId: childGemId, exo }; - }; - - // we wrap this here to avoid passing things to the wake controller - // the wake controller adds little of value as "endowments" - const makeFacetWithEndowments = async endowments => { - return makeFacet({ - ...endowments, - persistenceNode, - retentionSet, - incarnateEvalGem, - gemLookup, - }); + registerGem(gemNs, childGemRecipe); }; - const wakeController = makeWakeController({ - name, - makeFacet: makeFacetWithEndowments, + const compartment = new Compartment(); + const constructGem = compartment.evaluate(code); + const { + interface: interfaceGuards, + init, + methods, + } = constructGem({ + M, + gemName: name, + defineChildGem, + lookupChildGemClass: childName => loadGem(gemNs, childName), }); - const methodNames = getInterfaceMethodKeys(iface); - const wrapper = makeWrapper(name, wakeController, methodNames); - const exo = makeExo(`${gemId}`, iface, wrapper); - gemController.register(gemId, exo); - - return { gemId, exo, wakeController, persistenceNode, retentionSet }; + return gemNs.exoClass( + name, + interfaceGuards, + init || initWithPassthrough, + methods, + ); }; - return makeGem; -}; -const makeGemController = () => { - const gemIdToGem = new Map(); - const gemToGemId = new WeakMap(); + // reincarnate all registered gems + // TODO: should be as lazy as possible - const getGemById = gemId => { - return gemIdToGem.get(gemId); - }; - const getGemId = gem => { - // if (!gemToGemId.has(gem)) { - // throw new Error(`Gem not found in lookup ("${gem}")`); - // } - // return gemToGemId.get(gem); + const rootGemNamespace = loadGemNamespaceFromGemZone(rootGemZone); - // this is a hack to get the gem id from the remote ref - // this is prolly not safe or something - // some identity discontinuity happening with the first technique - const str = String(gem); - const startIndex = str.indexOf('Alleged: '); - const endIndex = str.indexOf(']'); - if (startIndex === -1 || endIndex === -1) { - throw new Error(`Could not find gem id in remote ref ("${str}")`); - } - const gemId = str.slice(startIndex + 9, endIndex); - if (!gemId) { - throw new Error('Gem id was empty'); + const walkGemRegistry = gemNs => { + for (const name of gemNs.getChildKeys()) { + loadGem(gemNs, name); + const childGemNs = gemNs.lookupChild(name); + walkGemRegistry(childGemNs); } - if (!gemId.startsWith('gem:')) { - throw new Error('Gem id did not start with gem:'); - } - return gemId; - }; - const register = (gemId, gem) => { - if (gemIdToGem.has(gemId)) { - throw new Error(`Gem id already registered ("${gemId}")`); - } - console.log(`${gemId} registered: ${gem}`); - gemIdToGem.set(gemId, gem); - gemToGemId.set(gem, gemId); }; + walkGemRegistry(rootGemNamespace); - return { - register, - getGemById, - getGemId, - getLookup() { - return { - getGemById, - getGemId, - }; - }, + const registerRootGem = gemRecipe => { + registerGem(rootGemNamespace, gemRecipe); + }; + const loadRootGem = name => { + return loadGem(rootGemNamespace, name); + }; + const makeRootGem = gemRecipe => { + registerRootGem(gemRecipe); + const makeGem = loadRootGem(gemRecipe.name); + return makeGem(); }; -}; -export const makeKernel = () => { - const gemController = makeGemController(); - const makeGem = makeGemFactory({ gemController }); - return { - makeGem, - gemController, + const kernel = { + registerGem: registerRootGem, + makeGem: makeRootGem, + store: rootStore, + ns: rootGemNamespace, }; + + return kernel; }; diff --git a/packages/gems/src/kumavis-store.js b/packages/gems/src/kumavis-store.js deleted file mode 100644 index a6935f9106..0000000000 --- a/packages/gems/src/kumavis-store.js +++ /dev/null @@ -1,77 +0,0 @@ -const walkJson = (obj, handler) => { - // Loop through each key in the object - for (const key in obj) { - // Check if the key belongs to the object itself (not inherited) - if (Reflect.has(obj, key)) { - // Call the handler function with the current key and value - handler(obj, key, obj[key]); - // If the value is an object (and not null), recurse into it - if (typeof obj[key] === 'object' && obj[key] !== null) { - walkJson(obj[key], handler); - } - } - } -}; - -const isRemoteRef = ref => - typeof ref === 'object' && - !Array.isArray(ref) && - String(ref).includes('Alleged:'); - -export const makeKumavisStore = async ( - { persistenceNode, retentionSet, gemLookup }, - initState, -) => { - // turns gemRefs into prefixed strings - // and escapes ordinary strings - const marshall = state => { - if (retentionSet) retentionSet.clear(); - walkJson(state, (parent, key, value) => { - if (typeof value === 'string') { - parent[key] = `string:${value}`; - } else if (isRemoteRef(value)) { - const gemId = gemLookup.getGemId(value); - parent[key] = gemId; - if (retentionSet) retentionSet.add(gemId); - } - }); - return state; - }; - // turns prefixed strings back into strings - // and looks up gemRefs by id - const unmarshall = state => { - walkJson(state, (parent, key, value) => { - if (typeof value === 'string') { - if (value.startsWith('string:')) { - parent[key] = value.slice('string:'.length); - } else if (value.startsWith('gem:')) { - parent[key] = gemLookup.getGemById(value); - } else { - throw new Error('Unexpected unescaped string value in state'); - } - } - }); - return state; - }; - const serialize = state => JSON.stringify(state); - const deserialize = string => JSON.parse(string); - const read = async () => - persistenceNode.get() - ? unmarshall(deserialize(persistenceNode.get())) - : initState; - const write = async state => persistenceNode.set(serialize(marshall(state))); - - let state = await read(); - const store = { - get: () => state, - set: async newState => { - state = newState; - await write(state); - }, - update: async partial => { - state = { ...state, ...partial }; - await write(state); - }, - }; - return store; -}; diff --git a/packages/gems/test/index.test.js b/packages/gems/test/index.test.js index 76c905df9f..aaf7a3b9e3 100644 --- a/packages/gems/test/index.test.js +++ b/packages/gems/test/index.test.js @@ -1,223 +1,143 @@ import test from '@endo/ses-ava/prepare-endo.js'; -import { E } from '@endo/far'; -import { M } from '@endo/patterns'; -import { util } from '../src/index.js'; -import { makeScenario } from './util.js'; -import { makeKumavisStore } from '../src/kumavis-store.js'; - -const { delay } = util; - -test('lifecycle - ping/gc', async t => { - const gemName = 'PingGem'; - const gemRecipe = { - name: gemName, - interface: M.interface(gemName, { - ping: M.callWhen().returns(M.string()), - }), - makeFacet: async () => { - return { - async ping() { - return 'pong'; - }, - }; - }, - }; - - const { aliceKit, bobKit } = makeScenario({ recipeForBoth: gemRecipe }); - // bob's bootstrap is alice and vice versa - const alice = await bobKit.captpKit.getBootstrap(); - - console.log('ping ->'); - console.log(' <-', await E(alice).ping()); - console.log('ping ->'); - console.log(' <-', await E(alice).ping()); - await aliceKit.gem.wakeController.sleep(); - - console.log('ping ->'); - console.log(' <-', await E(alice).ping()); - - console.log('...attempting to trigger timebased GC...'); - await delay(10e3); - - console.log('ping ->'); - console.log(' <-', await E(alice).ping()); - - // this is just an example - t.pass(); -}); +import '@agoric/swingset-liveslots/tools/setup-vat-data.js'; +import { makeVat } from './util.js'; test('persistence - simple json counter', async t => { - const gemName = 'CounterGem'; const gemRecipe = { - interface: M.interface(gemName, { - increment: M.callWhen().returns(M.number()), - getCount: M.callWhen().returns(M.number()), - }), - makeFacet: async ({ persistenceNode }) => { - const initState = { count: 0 }; - const store = await makeKumavisStore({ persistenceNode }, initState); - return { - async increment() { - let { count } = store.get(); - count += 1; - await store.update({ count }); - return count; + name: 'CounterGem', + code: `${({ M, gemName }) => ({ + interface: M.interface(gemName, { + increment: M.call().returns(M.number()), + getCount: M.call().returns(M.number()), + }), + init: (count = 0) => ({ count }), + methods: { + increment() { + this.state.count += 1; + return this.state.count; }, - async getCount() { - const { count } = store.get(); - return count; + getCount() { + return this.state.count; }, - }; - }, + }, + })}`, }; - const { aliceKit, bobKit } = makeScenario({ recipeForBoth: gemRecipe }); - // bob's bootstrap is alice and vice versa - const alice = await bobKit.captpKit.getBootstrap(); + const vat = makeVat(); + let kernel = vat.restart(); + let counter = kernel.makeGem(gemRecipe); + kernel.store.init('counter', counter); - t.deepEqual(await E(alice).getCount(), 0); - await E(alice).increment(); - t.deepEqual(await E(alice).getCount(), 1); + t.deepEqual(counter.getCount(), 0); + counter.increment(); + t.deepEqual(counter.getCount(), 1); - await aliceKit.gem.wakeController.sleep(); + kernel = vat.restart(); + counter = kernel.store.get('counter'); - t.deepEqual(await E(alice).getCount(), 1); - await Promise.all([E(alice).increment(), E(alice).increment()]); - t.deepEqual(await E(alice).getCount(), 3); + t.deepEqual(counter.getCount(), 1); + counter.increment(); + counter.increment(); + t.deepEqual(counter.getCount(), 3); }); +// TODO: need to untangle captp remote refs for persistence test('kumavis store - serialization of gem refs', async t => { - const gemName = 'FriendGem'; - const gemRecipe = { - name: gemName, - interface: M.interface(gemName, { - addFriend: M.callWhen(M.any()).returns(M.string()), - getFriends: M.callWhen().returns(M.any()), - }), - makeFacet: async ({ persistenceNode, retentionSet, gemLookup }) => { - const initState = { friends: [] }; - const store = await makeKumavisStore( - { persistenceNode, retentionSet, gemLookup }, - initState, - ); - return { - async addFriend(friend) { - const { friends } = store.get(); - friends.push(friend); - await store.update({ friends }); - return `added friend ${friend} (${friends.length} friends total)`; + const friendsListRecipe = { + name: 'FriendsList', + code: `${({ M, gemName }) => ({ + interface: M.interface(gemName, { + addFriend: M.call(M.any()).returns(M.string()), + getFriends: M.call().returns(M.any()), + }), + init: () => harden({ friends: [] }), + methods: { + addFriend(friend) { + this.state.friends = harden([...this.state.friends, friend]); + return `added friend ${friend} (${this.state.friends.length} friends total)`; }, - async getFriends() { - const { friends } = store.get(); - return friends; + getFriends() { + return this.state.friends; }, - }; - }, + }, + })}`, }; - const { aliceKit, bobKit } = makeScenario({ recipeForBoth: gemRecipe }); - // bob's bootstrap is alice and vice versa - const alice = await bobKit.captpKit.getBootstrap(); - const bob = await aliceKit.captpKit.getBootstrap(); - - t.deepEqual(aliceKit.gem.retentionSet.size, 0); - await E(alice).addFriend(bob); - t.deepEqual(aliceKit.gem.retentionSet.size, 1); - await aliceKit.gem.wakeController.sleep(); - - const aliceFriends = await E(alice).getFriends(); - t.deepEqual(aliceKit.gem.retentionSet.size, 1); - t.deepEqual(aliceFriends, [bob]); - t.notDeepEqual(aliceFriends, [alice]); -}); - -test('kumavis store - serialization + retention of gem refs', async t => { - const gemName = 'FriendGem'; - const gemRecipe = { - name: gemName, - interface: M.interface(gemName, { - addFriend: M.callWhen(M.any()).returns(M.string()), - getFriends: M.callWhen().returns(M.any()), - }), - makeFacet: async ({ persistenceNode, retentionSet, gemLookup }) => { - const initState = { friends: [] }; - const store = await makeKumavisStore( - { persistenceNode, retentionSet, gemLookup }, - initState, - ); - return { - async addFriend(friend) { - const { friends } = store.get(); - friends.push(friend); - await store.update({ friends }); - return `added friend ${friend} (${friends.length} friends total)`; - }, - async getFriends() { - const { friends } = store.get(); - return friends; - }, - }; - }, + const friendRecipe = { + name: 'Friend', + code: `${({ M, gemName }) => ({ + interface: M.interface(gemName, {}), + methods: {}, + })}`, }; - const { aliceKit, bobKit } = makeScenario({ recipeForBoth: gemRecipe }); - // bob's bootstrap is alice and vice versa - const alice = await bobKit.captpKit.getBootstrap(); - const bob = await aliceKit.captpKit.getBootstrap(); + const vat = makeVat(); + let kernel = vat.restart(); + let friendsList = kernel.makeGem(friendsListRecipe); + kernel.store.init('friendsList', friendsList); + let friend = kernel.makeGem(friendRecipe); + kernel.store.init('friend', friend); + + t.deepEqual(friendsList.getFriends(), []); + friendsList.addFriend(friend); + t.deepEqual(friendsList.getFriends(), [friend]); - t.deepEqual(aliceKit.gem.retentionSet.size, 0); - await E(alice).addFriend(bob); - t.deepEqual(aliceKit.gem.retentionSet.size, 1); - await aliceKit.gem.wakeController.sleep(); + kernel = vat.restart(); + friendsList = kernel.store.get('friendsList'); + friend = kernel.store.get('friend'); - const aliceFriends = await E(alice).getFriends(); - t.deepEqual(aliceKit.gem.retentionSet.size, 1); - t.deepEqual(aliceFriends, [bob]); - t.notDeepEqual(aliceFriends, [alice]); + t.deepEqual(friendsList.getFriends(), [friend]); }); test('makeGem - widget factory', async t => { - const gemName = 'WidgetGem'; - const gemRecipe = { - name: gemName, - interface: M.interface(gemName, { - makeWidget: M.callWhen().returns(M.any()), - }), - makeFacet: async ({ retentionSet, incarnateEvalGem }) => { + const widgetFactoryRecipe = { + name: 'WidgetFactory', + code: `${({ M, gemName, defineChildGem, lookupChildGemClass }) => { + defineChildGem({ + name: 'Widget', + code: `${({ M: M2 }) => ({ + interface: M2.interface('Widget', { + sayHi: M2.call().returns(M2.string()), + }), + methods: { + sayHi() { + return 'hi im a widget'; + }, + }, + })}`, + }); return { - async makeWidget() { - const widget = await incarnateEvalGem({ - name: 'widget', - interface: M.interface('Widget', { - sayHi: M.callWhen().returns(M.string()), - }), - code: 'async () => ({ sayHi: async () => "hi im a widget" })', - }); - // you probably wouldnt want this to - // manage the retention of the widget, - // the consumer of the widget should do that. - retentionSet.add(widget.gemId); - return widget.exo; + interface: M.interface(gemName, { + makeWidget: M.call().returns(M.any()), + }), + methods: { + makeWidget() { + const makeWidget = lookupChildGemClass('Widget'); + return makeWidget(); + }, }, }; - }, + }}`, }; - const { aliceKit, bobKit } = makeScenario({ recipeForBoth: gemRecipe }); - // bob's bootstrap is alice and vice versa - const alice = await bobKit.captpKit.getBootstrap(); + const vat = makeVat(); + let kernel = vat.restart(); + let widgetFactory = kernel.makeGem(widgetFactoryRecipe); + kernel.store.init('widgetFactory', widgetFactory); + + let widget = widgetFactory.makeWidget(); + kernel.store.init('widget', widget); - t.deepEqual(aliceKit.gem.retentionSet.size, 0); - const widget1 = await E(alice).makeWidget(); - t.deepEqual(aliceKit.gem.retentionSet.size, 1); - await aliceKit.gem.wakeController.sleep(); + t.deepEqual(widget.sayHi(), 'hi im a widget'); - t.deepEqual(aliceKit.gem.retentionSet.size, 1); - const widget2 = await E(alice).makeWidget(); - t.deepEqual(aliceKit.gem.retentionSet.size, 2); + kernel = vat.restart(); + widgetFactory = kernel.store.get('widgetFactory'); + widget = kernel.store.get('widget'); - await E(widget1).sayHi(); - await E(widget2).sayHi(); + t.deepEqual(widget.sayHi(), 'hi im a widget'); + const widget2 = widgetFactory.makeWidget(); + kernel.store.init('widget2', widget2); + t.deepEqual(widget2.sayHi(), 'hi im a widget'); t.pass(); }); diff --git a/packages/gems/test/util.js b/packages/gems/test/util.js index a96ef92eee..f91cbd1bbc 100644 --- a/packages/gems/test/util.js +++ b/packages/gems/test/util.js @@ -1,44 +1,38 @@ -import { makePipe } from '@endo/stream'; -import { makeMessageCapTP, util, makeKernel } from '../src/index.js'; +import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; +import { makeKernel } from '../src/index.js'; -const { never } = util; - -export const makeScenario = ({ - recipeForBoth, - recipeForAlice = recipeForBoth, - recipeForBob = recipeForBoth, -}) => { - const [writerA, readerB] = makePipe(); - const [writerB, readerA] = makePipe(); - - const kernel = makeKernel(); - - const gemA = kernel.makeGem({ - ...recipeForAlice, - name: `${recipeForAlice.name}-alice`, +const setupWorld = fakeStore => { + const { fakeVomKit } = reincarnate({ + relaxDurabilityRules: false, + fakeStore, }); - const captpKitA = makeMessageCapTP( - 'Alice', - writerA, - readerA, - never, - gemA.exo, - ); + const { vom, cm, vrm } = fakeVomKit; + const flush = () => { + vom.flushStateCache(); + cm.flushSchemaCache(); + vrm.flushIDCounters(); + }; + const baggage = cm.provideBaggage(); + return { baggage, flush }; +}; - const gemB = kernel.makeGem({ - ...recipeForBob, - name: `${recipeForBob.name}-bob`, - }); - const captpKitB = makeMessageCapTP('Bob', writerB, readerB, never, gemB.exo); +export const makeVat = () => { + let fakeStore; + let baggage; + let flush; + let kernel; + + const restart = () => { + if (flush) { + flush(); + } + fakeStore = new Map(fakeStore); + ({ baggage, flush } = setupWorld(fakeStore)); + kernel = makeKernel(baggage); + return kernel; + }; return { - aliceKit: { - captpKit: captpKitA, - gem: gemA, - }, - bobKit: { - captpKit: captpKitB, - gem: gemB, - }, + restart, }; }; diff --git a/yarn.lock b/yarn.lock index e329f10cce..2b77e575ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@agoric/assert@npm:0.6.1-upgrade-16a-dev-fb592e4.0+fb592e4, @agoric/assert@npm:^0.6.1-u16.0": + version: 0.6.1-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/assert@npm:0.6.1-upgrade-16a-dev-fb592e4.0" + checksum: 10c0/cb43433b33e74db3b9dbb01d3be2d737002eda0af4a387725355bcd22edcbda606e062f295a75722a7bb68705adcd329126d78c0695143967b94352dfe1d566e + languageName: node + linkType: hard + "@agoric/babel-generator@npm:^7.17.6": version: 7.17.6 resolution: "@agoric/babel-generator@npm:7.17.6" @@ -23,6 +30,138 @@ __metadata: languageName: node linkType: hard +"@agoric/base-zone@npm:0.1.1-upgrade-16a-dev-fb592e4.0+fb592e4, @agoric/base-zone@npm:^0.1.1-u16.0": + version: 0.1.1-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/base-zone@npm:0.1.1-upgrade-16a-dev-fb592e4.0" + dependencies: + "@agoric/store": "npm:0.9.3-upgrade-16a-dev-fb592e4.0+fb592e4" + "@endo/common": "npm:^1.2.2" + "@endo/exo": "npm:^1.5.0" + "@endo/far": "npm:^1.1.2" + "@endo/pass-style": "npm:^1.4.0" + "@endo/patterns": "npm:^1.4.0" + checksum: 10c0/5023ea7910fe5b855ddc1601cba6e28bfb0a48f3d063e0e73066f381a8385509ad6b0176162ce8f682b26bcebf159ffad7524acd8adcb28ec5ef62f895c38626 + languageName: node + linkType: hard + +"@agoric/internal@npm:0.4.0-upgrade-16a-dev-fb592e4.0+fb592e4, @agoric/internal@npm:^0.4.0-u16.1": + version: 0.4.0-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/internal@npm:0.4.0-upgrade-16a-dev-fb592e4.0" + dependencies: + "@agoric/assert": "npm:0.6.1-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/base-zone": "npm:0.1.1-upgrade-16a-dev-fb592e4.0+fb592e4" + "@endo/common": "npm:^1.2.2" + "@endo/far": "npm:^1.1.2" + "@endo/init": "npm:^1.1.2" + "@endo/marshal": "npm:^1.5.0" + "@endo/pass-style": "npm:^1.4.0" + "@endo/patterns": "npm:^1.4.0" + "@endo/promise-kit": "npm:^1.1.2" + "@endo/stream": "npm:^1.2.2" + anylogger: "npm:^0.21.0" + jessie.js: "npm:^0.3.4" + checksum: 10c0/c8847e17de79fdb6fec922ef860fec72c2c2a04653d139c83a7cc78d1129422a17efa560ab57f892a4516dcaa78648ca989bc0fd51d6fa44b1114396307c64a0 + languageName: node + linkType: hard + +"@agoric/store@npm:0.9.3-upgrade-16a-dev-fb592e4.0+fb592e4, @agoric/store@npm:^0.9.3-u16.0": + version: 0.9.3-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/store@npm:0.9.3-upgrade-16a-dev-fb592e4.0" + dependencies: + "@endo/exo": "npm:^1.5.0" + "@endo/marshal": "npm:^1.5.0" + "@endo/pass-style": "npm:^1.4.0" + "@endo/patterns": "npm:^1.4.0" + checksum: 10c0/83afb926f06b59f485ac14d17cbe0072a7be16250a1214eac28ac7c9c17432160a679bf3f2d2a88a1648da69603fa99426b5b54707763fd41de4551130ec4405 + languageName: node + linkType: hard + +"@agoric/swingset-liveslots@npm:0.10.3-u16.1": + version: 0.10.3-u16.1 + resolution: "@agoric/swingset-liveslots@npm:0.10.3-u16.1" + dependencies: + "@agoric/assert": "npm:^0.6.1-u16.0" + "@agoric/internal": "npm:^0.4.0-u16.1" + "@agoric/store": "npm:^0.9.3-u16.0" + "@endo/env-options": "npm:^1.1.4" + "@endo/errors": "npm:^1.2.2" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/exo": "npm:^1.5.0" + "@endo/far": "npm:^1.1.2" + "@endo/init": "npm:^1.1.2" + "@endo/marshal": "npm:^1.5.0" + "@endo/nat": "npm:^5.0.7" + "@endo/pass-style": "npm:^1.4.0" + "@endo/patterns": "npm:^1.4.0" + "@endo/promise-kit": "npm:^1.1.2" + checksum: 10c0/24f30db9deaf95d0df2e67865475992941ebae104c633166ff3b77976551678d2645517fcdf37680638590199495e300f4682e3de986f84af8d1556789e20bf0 + languageName: node + linkType: hard + +"@agoric/swingset-liveslots@npm:0.10.3-upgrade-16a-dev-fb592e4.0+fb592e4": + version: 0.10.3-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/swingset-liveslots@npm:0.10.3-upgrade-16a-dev-fb592e4.0" + dependencies: + "@agoric/assert": "npm:0.6.1-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/internal": "npm:0.4.0-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/store": "npm:0.9.3-upgrade-16a-dev-fb592e4.0+fb592e4" + "@endo/env-options": "npm:^1.1.4" + "@endo/errors": "npm:^1.2.2" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/exo": "npm:^1.5.0" + "@endo/far": "npm:^1.1.2" + "@endo/init": "npm:^1.1.2" + "@endo/marshal": "npm:^1.5.0" + "@endo/nat": "npm:^5.0.7" + "@endo/pass-style": "npm:^1.4.0" + "@endo/patterns": "npm:^1.4.0" + "@endo/promise-kit": "npm:^1.1.2" + checksum: 10c0/3d2b8ad7ade38ba3c04e9a5211029b3697a0ed81c63a6d5560638dbc22b8027d2a31264b7e26ad464c574d62382a5a24b418b46d8166e4e0988ab5a711a139f6 + languageName: node + linkType: hard + +"@agoric/vat-data@npm:^0.5.3-u16.1": + version: 0.5.3-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/vat-data@npm:0.5.3-upgrade-16a-dev-fb592e4.0" + dependencies: + "@agoric/assert": "npm:0.6.1-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/base-zone": "npm:0.1.1-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/store": "npm:0.9.3-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/swingset-liveslots": "npm:0.10.3-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/vow": "npm:0.2.0-upgrade-16a-dev-fb592e4.0+fb592e4" + "@endo/exo": "npm:^1.5.0" + "@endo/patterns": "npm:^1.4.0" + checksum: 10c0/207fe76c305b53931baa2ad9d6d144c7696bbba16d231f28e9f6d41f8e317060788d956dc58bc612ce0352edca336f7237234e21fecdb7152758e471580a787b + languageName: node + linkType: hard + +"@agoric/vow@npm:0.2.0-upgrade-16a-dev-fb592e4.0+fb592e4": + version: 0.2.0-upgrade-16a-dev-fb592e4.0 + resolution: "@agoric/vow@npm:0.2.0-upgrade-16a-dev-fb592e4.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-upgrade-16a-dev-fb592e4.0+fb592e4" + "@agoric/internal": "npm:0.4.0-upgrade-16a-dev-fb592e4.0+fb592e4" + "@endo/env-options": "npm:^1.1.4" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/pass-style": "npm:^1.4.0" + "@endo/patterns": "npm:^1.4.0" + "@endo/promise-kit": "npm:^1.1.2" + checksum: 10c0/6543cdeebdb6e5aae2a5442074c49812c86a4e132bb101bb7ec836a8919c529db9fa63ac396c5b2e3db9956bec668a01a5f5fe35887ea3ce70eea9708b7dca68 + languageName: node + linkType: hard + +"@agoric/zone@npm:0.3.0-u16.1": + version: 0.3.0-u16.1 + resolution: "@agoric/zone@npm:0.3.0-u16.1" + dependencies: + "@agoric/base-zone": "npm:^0.1.1-u16.0" + "@agoric/vat-data": "npm:^0.5.3-u16.1" + "@endo/far": "npm:^1.1.2" + "@endo/pass-style": "npm:^1.4.0" + checksum: 10c0/149cb6c1dbabd554b71e5da75973379df5fe4bb2eeba3fb73407140bc1c5aaa6f75efc3b685fdcef90ea2e49f9985a905f66135ac72f79e188bc4a5bb7753d18 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5": version: 7.23.5 resolution: "@babel/code-frame@npm:7.23.5" @@ -326,7 +465,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/common@workspace:^, @endo/common@workspace:packages/common": +"@endo/common@npm:^1.2.2, @endo/common@workspace:^, @endo/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@endo/common@workspace:packages/common" dependencies: @@ -401,7 +540,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/env-options@workspace:^, @endo/env-options@workspace:packages/env-options": +"@endo/env-options@npm:^1.1.4, @endo/env-options@workspace:^, @endo/env-options@workspace:packages/env-options": version: 0.0.0-use.local resolution: "@endo/env-options@workspace:packages/env-options" dependencies: @@ -417,7 +556,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/errors@workspace:^, @endo/errors@workspace:packages/errors": +"@endo/errors@npm:^1.2.2, @endo/errors@workspace:^, @endo/errors@workspace:packages/errors": version: 0.0.0-use.local resolution: "@endo/errors@workspace:packages/errors" dependencies: @@ -470,7 +609,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/eventual-send@workspace:^, @endo/eventual-send@workspace:packages/eventual-send": +"@endo/eventual-send@npm:^1.2.2, @endo/eventual-send@workspace:^, @endo/eventual-send@workspace:packages/eventual-send": version: 0.0.0-use.local resolution: "@endo/eventual-send@workspace:packages/eventual-send" dependencies: @@ -483,7 +622,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/exo@workspace:^, @endo/exo@workspace:packages/exo": +"@endo/exo@npm:^1.5.0, @endo/exo@workspace:^, @endo/exo@workspace:packages/exo": version: 0.0.0-use.local resolution: "@endo/exo@workspace:packages/exo" dependencies: @@ -508,7 +647,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/far@workspace:^, @endo/far@workspace:packages/far": +"@endo/far@npm:^1.0.0, @endo/far@npm:^1.1.2, @endo/far@workspace:^, @endo/far@workspace:packages/far": version: 0.0.0-use.local resolution: "@endo/far@workspace:packages/far" dependencies: @@ -527,6 +666,8 @@ __metadata: version: 0.0.0-use.local resolution: "@endo/gems@workspace:packages/gems" dependencies: + "@agoric/swingset-liveslots": "npm:0.10.3-u16.1" + "@agoric/zone": "npm:0.3.0-u16.1" "@endo/captp": "workspace:^" "@endo/exo": "workspace:^" "@endo/far": "workspace:^" @@ -570,7 +711,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/init@workspace:^, @endo/init@workspace:packages/init": +"@endo/init@npm:^1.1.2, @endo/init@workspace:^, @endo/init@workspace:packages/init": version: 0.0.0-use.local resolution: "@endo/init@workspace:packages/init" dependencies: @@ -614,7 +755,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/marshal@workspace:^, @endo/marshal@workspace:packages/marshal": +"@endo/marshal@npm:^1.5.0, @endo/marshal@workspace:^, @endo/marshal@workspace:packages/marshal": version: 0.0.0-use.local resolution: "@endo/marshal@workspace:packages/marshal" dependencies: @@ -676,7 +817,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/nat@workspace:^, @endo/nat@workspace:packages/nat": +"@endo/nat@npm:^5.0.7, @endo/nat@workspace:^, @endo/nat@workspace:packages/nat": version: 0.0.0-use.local resolution: "@endo/nat@workspace:packages/nat" dependencies: @@ -713,7 +854,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/pass-style@workspace:^, @endo/pass-style@workspace:packages/pass-style": +"@endo/pass-style@npm:^1.4.0, @endo/pass-style@workspace:^, @endo/pass-style@workspace:packages/pass-style": version: 0.0.0-use.local resolution: "@endo/pass-style@workspace:packages/pass-style" dependencies: @@ -736,7 +877,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/patterns@workspace:^, @endo/patterns@workspace:packages/patterns": +"@endo/patterns@npm:^1.4.0, @endo/patterns@workspace:^, @endo/patterns@workspace:packages/patterns": version: 0.0.0-use.local resolution: "@endo/patterns@workspace:packages/patterns" dependencies: @@ -759,7 +900,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/promise-kit@workspace:^, @endo/promise-kit@workspace:packages/promise-kit": +"@endo/promise-kit@npm:^1.1.2, @endo/promise-kit@workspace:^, @endo/promise-kit@workspace:packages/promise-kit": version: 0.0.0-use.local resolution: "@endo/promise-kit@workspace:packages/promise-kit" dependencies: @@ -852,7 +993,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/stream@workspace:^, @endo/stream@workspace:packages/stream": +"@endo/stream@npm:^1.2.2, @endo/stream@workspace:^, @endo/stream@workspace:packages/stream": version: 0.0.0-use.local resolution: "@endo/stream@workspace:packages/stream" dependencies: @@ -2664,6 +2805,13 @@ __metadata: languageName: node linkType: hard +"anylogger@npm:^0.21.0": + version: 0.21.0 + resolution: "anylogger@npm:0.21.0" + checksum: 10c0/1ca7fcf5bc2b78d1e1d9b8c8cc7ce50b5c6cc67a8da5a28c9c975b7b46fff255a04abab02de38a5139190c9d8b34b3d6c59af6724521b077f7d7dfbad9b47a9c + languageName: node + linkType: hard + "anymatch@npm:~3.1.2": version: 3.1.2 resolution: "anymatch@npm:3.1.2" @@ -6424,6 +6572,15 @@ __metadata: languageName: node linkType: hard +"jessie.js@npm:^0.3.4": + version: 0.3.4 + resolution: "jessie.js@npm:0.3.4" + dependencies: + "@endo/far": "npm:^1.0.0" + checksum: 10c0/853ab3f8a0e30df11742882f5e11479d1303033a5a203a247d8ffbf4c6f3f3d4bcbefa53084ae4632e6ab106e348f23dc988280486cbeaaf5d16487fa3d40e96 + languageName: node + linkType: hard + "jest-diff@npm:>=29.4.3 < 30, jest-diff@npm:^29.4.1": version: 29.7.0 resolution: "jest-diff@npm:29.7.0"