@agoric/swingset-liveslots 0.10.3-dev-b282e92.0 → 0.10.3-dev-0cd32a5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-liveslots",
3
- "version": "0.10.3-dev-b282e92.0+b282e92",
3
+ "version": "0.10.3-dev-0cd32a5.0+0cd32a5",
4
4
  "description": "SwingSet ocap support layer",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -17,8 +17,8 @@
17
17
  "lint:eslint": "eslint ."
18
18
  },
19
19
  "dependencies": {
20
- "@agoric/internal": "0.3.3-dev-b282e92.0+b282e92",
21
- "@agoric/store": "0.9.3-dev-b282e92.0+b282e92",
20
+ "@agoric/internal": "0.3.3-dev-0cd32a5.0+0cd32a5",
21
+ "@agoric/store": "0.9.3-dev-0cd32a5.0+0cd32a5",
22
22
  "@endo/env-options": "^1.1.6",
23
23
  "@endo/errors": "^1.2.5",
24
24
  "@endo/eventual-send": "^1.2.5",
@@ -32,7 +32,7 @@
32
32
  "@endo/promise-kit": "^1.1.5"
33
33
  },
34
34
  "devDependencies": {
35
- "@agoric/kmarshal": "0.1.1-dev-b282e92.0+b282e92",
35
+ "@agoric/kmarshal": "0.1.1-dev-0cd32a5.0+0cd32a5",
36
36
  "ava": "^5.3.0"
37
37
  },
38
38
  "files": [
@@ -68,5 +68,5 @@
68
68
  "typeCoverage": {
69
69
  "atLeast": 75.1
70
70
  },
71
- "gitHead": "b282e923866829ba364c059429faa971fa2c9d6a"
71
+ "gitHead": "0cd32a59f24dbea365f80be5ac3880db9234e4d1"
72
72
  }
@@ -0,0 +1,70 @@
1
+ import { provideLazy as provide } from '@agoric/store';
2
+
3
+ // Partially duplicates @agoric/vat-data to avoid circular dependencies.
4
+ export const makeExoUtils = VatData => {
5
+ const { defineDurableKind, makeKindHandle, watchPromise } = VatData;
6
+
7
+ const provideKindHandle = (baggage, kindName) =>
8
+ provide(baggage, `${kindName}_kindHandle`, () => makeKindHandle(kindName));
9
+
10
+ const emptyRecord = harden({});
11
+ const initEmpty = () => emptyRecord;
12
+
13
+ const defineDurableExoClass = (
14
+ kindHandle,
15
+ interfaceGuard,
16
+ init,
17
+ methods,
18
+ options,
19
+ ) =>
20
+ defineDurableKind(kindHandle, init, methods, {
21
+ ...options,
22
+ thisfulMethods: true,
23
+ interfaceGuard,
24
+ });
25
+
26
+ const prepareExoClass = (
27
+ baggage,
28
+ kindName,
29
+ interfaceGuard,
30
+ init,
31
+ methods,
32
+ options = undefined,
33
+ ) =>
34
+ defineDurableExoClass(
35
+ provideKindHandle(baggage, kindName),
36
+ interfaceGuard,
37
+ init,
38
+ methods,
39
+ options,
40
+ );
41
+
42
+ const prepareExo = (
43
+ baggage,
44
+ kindName,
45
+ interfaceGuard,
46
+ methods,
47
+ options = undefined,
48
+ ) => {
49
+ const makeSingleton = prepareExoClass(
50
+ baggage,
51
+ kindName,
52
+ interfaceGuard,
53
+ initEmpty,
54
+ methods,
55
+ options,
56
+ );
57
+ return provide(baggage, `the_${kindName}`, () => makeSingleton());
58
+ };
59
+
60
+ return {
61
+ defineDurableKind,
62
+ makeKindHandle,
63
+ watchPromise,
64
+
65
+ provideKindHandle,
66
+ defineDurableExoClass,
67
+ prepareExoClass,
68
+ prepareExo,
69
+ };
70
+ };
@@ -2,7 +2,7 @@ import test from 'ava';
2
2
 
3
3
  import { Fail } from '@endo/errors';
4
4
  import { Far } from '@endo/marshal';
5
- import { M, provideLazy as provide } from '@agoric/store';
5
+ import { M } from '@agoric/store';
6
6
  import { makePromiseKit } from '@endo/promise-kit';
7
7
  // Disabled to avoid circular dependencies.
8
8
  // import { makeStoreUtils } from '@agoric/vat-data/src/vat-data-bindings.js';
@@ -10,79 +10,11 @@ import { makePromiseKit } from '@endo/promise-kit';
10
10
  import { kslot, kser } from '@agoric/kmarshal';
11
11
  import { setupTestLiveslots } from './liveslots-helpers.js';
12
12
  import { makeResolve, makeReject } from './util.js';
13
+ import { makeExoUtils } from './exo-utils.js';
13
14
 
14
15
  // eslint-disable-next-line no-unused-vars
15
16
  const compareEntriesByKey = ([ka], [kb]) => (ka < kb ? -1 : 1);
16
17
 
17
- // Paritally duplicates @agoric/vat-data to avoid circular dependencies.
18
- const makeExoUtils = VatData => {
19
- const { defineDurableKind, makeKindHandle, watchPromise } = VatData;
20
-
21
- const provideKindHandle = (baggage, kindName) =>
22
- provide(baggage, `${kindName}_kindHandle`, () => makeKindHandle(kindName));
23
-
24
- const emptyRecord = harden({});
25
- const initEmpty = () => emptyRecord;
26
-
27
- const defineDurableExoClass = (
28
- kindHandle,
29
- interfaceGuard,
30
- init,
31
- methods,
32
- options,
33
- ) =>
34
- defineDurableKind(kindHandle, init, methods, {
35
- ...options,
36
- thisfulMethods: true,
37
- interfaceGuard,
38
- });
39
-
40
- const prepareExoClass = (
41
- baggage,
42
- kindName,
43
- interfaceGuard,
44
- init,
45
- methods,
46
- options = undefined,
47
- ) =>
48
- defineDurableExoClass(
49
- provideKindHandle(baggage, kindName),
50
- interfaceGuard,
51
- init,
52
- methods,
53
- options,
54
- );
55
-
56
- const prepareExo = (
57
- baggage,
58
- kindName,
59
- interfaceGuard,
60
- methods,
61
- options = undefined,
62
- ) => {
63
- const makeSingleton = prepareExoClass(
64
- baggage,
65
- kindName,
66
- interfaceGuard,
67
- initEmpty,
68
- methods,
69
- options,
70
- );
71
- return provide(baggage, `the_${kindName}`, () => makeSingleton());
72
- };
73
-
74
- return {
75
- defineDurableKind,
76
- makeKindHandle,
77
- watchPromise,
78
-
79
- provideKindHandle,
80
- defineDurableExoClass,
81
- prepareExoClass,
82
- prepareExo,
83
- };
84
- };
85
-
86
18
  // cf. packages/SwingSet/test/vat-durable-promise-watcher.js
87
19
  const buildPromiseWatcherRootObject = (vatPowers, vatParameters, baggage) => {
88
20
  const { VatData } = vatPowers;
@@ -0,0 +1,94 @@
1
+ /* global globalThis */
2
+ // eslint-disable-next-line import/order
3
+ import { annihilate, startLife } from '../tools/prepare-strict-test-env.js';
4
+
5
+ import test from 'ava';
6
+
7
+ import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
8
+ import { makeExoUtils } from './exo-utils.js';
9
+
10
+ test.serial('kind redefinition enforced', async t => {
11
+ annihilate();
12
+
13
+ const { prepareExoClass } = makeExoUtils(globalThis.VatData);
14
+
15
+ await startLife(async baggage => {
16
+ const makeTestExo = prepareExoClass(
17
+ baggage,
18
+ 'TestExo',
19
+ undefined,
20
+ () => ({}),
21
+ {
22
+ foo() {
23
+ return 'bar';
24
+ },
25
+ },
26
+ );
27
+
28
+ baggage.init('testExo', makeTestExo());
29
+ });
30
+
31
+ await t.throwsAsync(
32
+ async () =>
33
+ startLife(async () => {
34
+ // Not redefining the kind here
35
+ }),
36
+ { message: 'defineDurableKind not called for tags: [TestExo]' },
37
+ );
38
+
39
+ await startLife(async baggage => {
40
+ prepareExoClass(baggage, 'TestExo', undefined, () => ({}), {
41
+ foo() {
42
+ return 'baz';
43
+ },
44
+ });
45
+
46
+ t.is(baggage.get('testExo').foo(), 'baz');
47
+ });
48
+ });
49
+
50
+ test.serial('decided promise rejected', async t => {
51
+ annihilate();
52
+
53
+ const { prepareExo } = makeExoUtils(globalThis.VatData);
54
+ const { watchPromise } = globalThis.VatData;
55
+
56
+ t.plan(1);
57
+
58
+ await startLife(async baggage => {
59
+ const watcher = prepareExo(
60
+ baggage,
61
+ 'DurablePromiseTestWatcher',
62
+ undefined,
63
+ {
64
+ onFulfilled(value) {
65
+ t.fail(
66
+ `First incarnation watcher onFulfilled triggered with value ${value}`,
67
+ );
68
+ },
69
+ onRejected(reason) {
70
+ t.fail(
71
+ `First incarnation watcher onRejected triggered with reason ${reason}`,
72
+ );
73
+ },
74
+ },
75
+ );
76
+
77
+ const never = harden(new Promise(() => {}));
78
+
79
+ watchPromise(never, watcher);
80
+ });
81
+
82
+ await startLife(async baggage => {
83
+ prepareExo(baggage, 'DurablePromiseTestWatcher', undefined, {
84
+ onFulfilled(value) {
85
+ t.fail(
86
+ `Second incarnation watcher onFulfilled triggered with value ${value}`,
87
+ );
88
+ },
89
+ onRejected(reason) {
90
+ t.deepEqual(reason, makeUpgradeDisconnection('vat upgraded', 1));
91
+ },
92
+ });
93
+ });
94
+ });
@@ -127,8 +127,8 @@ test('multifaceted virtual objects', t => {
127
127
 
128
128
  flushStateCache();
129
129
  t.deepEqual(log.splice(0), [
130
- `get kindIDID => undefined`,
131
130
  `get idCounters => undefined`,
131
+ `get kindIDID => undefined`,
132
132
  `set kindIDID 1`,
133
133
  `set vom.vkind.2.descriptor {"kindID":"2","tag":"multithing"}`,
134
134
  `set vom.${kid}/1 ${multiThingVal('foo', 1)}`,
@@ -203,8 +203,8 @@ test('virtual object operations', t => {
203
203
  // t3-0: 'thing-3' 200 0
204
204
  const thing4 = makeThing('thing-4', 300); // [t4-0* t3-0* t2-0* t1-0*]
205
205
  // t4-0: 'thing-4' 300 0
206
- t.is(log.shift(), `get kindIDID => undefined`);
207
206
  t.is(log.shift(), `get idCounters => undefined`);
207
+ t.is(log.shift(), `get kindIDID => undefined`);
208
208
  t.is(log.shift(), `set kindIDID 1`);
209
209
  t.is(log.shift(), `set vom.vkind.2.descriptor {"kindID":"2","tag":"thing"}`);
210
210
  t.is(log.shift(), `set vom.vkind.3.descriptor {"kindID":"3","tag":"zot"}`);
@@ -468,8 +468,8 @@ test('symbol named methods', t => {
468
468
  // t1-0: 'thing-1' 0 0
469
469
  const thing2 = makeThing('thing-2', 100); // [t1-0* t2-0*]
470
470
  // t2-0: 'thing-2' 100 0
471
- t.is(log.shift(), `get kindIDID => undefined`);
472
471
  t.is(log.shift(), `get idCounters => undefined`);
472
+ t.is(log.shift(), `get kindIDID => undefined`);
473
473
  t.is(log.shift(), `set kindIDID 1`);
474
474
  t.is(
475
475
  log.shift(),
@@ -649,8 +649,8 @@ test('virtual object gc', t => {
649
649
  },
650
650
  });
651
651
 
652
- t.is(log.shift(), `get kindIDID => undefined`);
653
652
  t.is(log.shift(), `get idCounters => undefined`);
653
+ t.is(log.shift(), `get kindIDID => undefined`);
654
654
  t.is(log.shift(), `set kindIDID 1`);
655
655
  const skit = [
656
656
  'storeKindIDTable',
@@ -23,6 +23,7 @@ export function makeFakeVirtualObjectManager(vrm, fakeStuff) {
23
23
  VirtualObjectAwareWeakSet,
24
24
  flushStateCache,
25
25
  canBeDurable,
26
+ insistAllDurableKindsReconnected,
26
27
  } = makeVirtualObjectManager(
27
28
  fakeStuff.syscall,
28
29
  vrm,
@@ -43,6 +44,7 @@ export function makeFakeVirtualObjectManager(vrm, fakeStuff) {
43
44
  defineDurableKindMulti,
44
45
  makeKindHandle,
45
46
  canBeDurable,
47
+ insistAllDurableKindsReconnected,
46
48
  VirtualObjectAwareWeakMap,
47
49
  VirtualObjectAwareWeakSet,
48
50
  };
@@ -28,12 +28,14 @@ class FakeFinalizationRegistry {
28
28
  }
29
29
 
30
30
  class FakeWeakRef {
31
+ #target;
32
+
31
33
  constructor(target) {
32
- this.target = target;
34
+ this.#target = target;
33
35
  }
34
36
 
35
37
  deref() {
36
- return this.target; // strong ref
38
+ return this.#target; // strong ref
37
39
  }
38
40
  }
39
41
 
@@ -41,6 +43,7 @@ export function makeFakeLiveSlotsStuff(options = {}) {
41
43
  let vrm;
42
44
  function setVrm(vrmToUse) {
43
45
  assert(!vrm, 'vrm already configured');
46
+ vrmToUse.initializeIDCounters();
44
47
  vrm = vrmToUse;
45
48
  }
46
49
 
@@ -174,6 +177,7 @@ export function makeFakeLiveSlotsStuff(options = {}) {
174
177
  // and the WeakRef may or may not contain the target value. Use
175
178
  // options={weak:true} to match that behavior, or the default weak:false to
176
179
  // keep strong references.
180
+ const WeakRefForSlot = weak ? RealWeakRef : FakeWeakRef;
177
181
  const valToSlot = new WeakMap();
178
182
  const slotToVal = new Map();
179
183
 
@@ -183,7 +187,7 @@ export function makeFakeLiveSlotsStuff(options = {}) {
183
187
 
184
188
  function getValForSlot(slot) {
185
189
  const d = slotToVal.get(slot);
186
- return d && (weak ? d.deref() : d);
190
+ return d && d.deref();
187
191
  }
188
192
 
189
193
  function requiredValForSlot(slot) {
@@ -193,7 +197,7 @@ export function makeFakeLiveSlotsStuff(options = {}) {
193
197
  }
194
198
 
195
199
  function setValForSlot(slot, val) {
196
- slotToVal.set(slot, weak ? new RealWeakRef(val) : val);
200
+ slotToVal.set(slot, new WeakRefForSlot(val));
197
201
  }
198
202
 
199
203
  function convertValToSlot(val) {
@@ -275,6 +279,7 @@ export function makeFakeLiveSlotsStuff(options = {}) {
275
279
  return {
276
280
  syscall,
277
281
  allocateExportID,
282
+ allocatePromiseID,
278
283
  allocateCollectionID,
279
284
  getSlotForVal,
280
285
  requiredValForSlot,
@@ -339,7 +344,7 @@ export function makeFakeWatchedPromiseManager(
339
344
  * @param {object} [options]
340
345
  * @param {number} [options.cacheSize]
341
346
  * @param {boolean} [options.relaxDurabilityRules]
342
- * @param {Map<any, any>} [options.fakeStore]
347
+ * @param {Map<string, string>} [options.fakeStore]
343
348
  * @param {WeakMapConstructor} [options.WeakMap]
344
349
  * @param {WeakSetConstructor} [options.WeakSet]
345
350
  * @param {boolean} [options.weak]
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Prepare Agoric SwingSet vat global strict environment for testing.
3
+ *
4
+ * Installs Hardened JS (and does lockdown), plus adds mocks for virtual objects
5
+ * and stores.
6
+ *
7
+ * Exports tools for simulating upgrades.
8
+ */
9
+
10
+ import '@agoric/internal/src/install-ses-debug.js';
11
+
12
+ // eslint-disable-next-line import/order
13
+ import { reincarnate, flushIncarnation } from './setup-vat-data.js';
14
+
15
+ import { makePromiseKit } from '@endo/promise-kit';
16
+ import { Fail } from '@endo/errors';
17
+ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
18
+ import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
19
+
20
+ export { flushIncarnation };
21
+ export { eventLoopIteration as nextCrank };
22
+
23
+ /**
24
+ * @import { ReincarnateOptions } from './setup-vat-data.js'
25
+ */
26
+
27
+ /** @type {ReincarnateOptions} */
28
+ let incarnation;
29
+ let incarnationNumber = 0;
30
+
31
+ /** @param {Omit<ReincarnateOptions, 'fakeVomKit' | 'fakeStore'>} [options] */
32
+ export const annihilate = (options = {}) => {
33
+ // @ts-expect-error fakeStore and fakeVomKit don't exist on type, but drop them if they do at runtime
34
+ const { fakeStore: _fs, fakeVomKit: _fvk, ...opts } = options;
35
+ incarnation = reincarnate({ relaxDurabilityRules: false, ...opts });
36
+ incarnationNumber = 0;
37
+ return incarnation;
38
+ };
39
+
40
+ export const getBaggage = () => {
41
+ return incarnation.fakeVomKit.cm.provideBaggage();
42
+ };
43
+
44
+ /**
45
+ * @param {ReincarnateOptions} [fromIncarnation]
46
+ */
47
+ export const nextLife = (fromIncarnation = incarnation) => {
48
+ incarnation = reincarnate(fromIncarnation);
49
+ incarnationNumber += 1;
50
+ return incarnation;
51
+ };
52
+
53
+ /**
54
+ * @template {(baggage: import('@agoric/swingset-liveslots').Baggage) => Promise<any> | any} B
55
+ * @param {B} build
56
+ * @param {(tools: Awaited<ReturnType<B>>) => Promise<void> | void} [run]
57
+ * @param {object} [options]
58
+ * @param {ReincarnateOptions} [options.fromIncarnation]
59
+ * @param {boolean} [options.cleanStart]
60
+ */
61
+ export const startLife = async (
62
+ build,
63
+ run,
64
+ { fromIncarnation, cleanStart } = {},
65
+ ) => {
66
+ await eventLoopIteration();
67
+ if (cleanStart) annihilate();
68
+ const oldIncarnationNumber = incarnationNumber;
69
+ const oldIncarnation = incarnation;
70
+ const disconnectionObject = makeUpgradeDisconnection(
71
+ 'vat upgraded',
72
+ oldIncarnationNumber,
73
+ );
74
+ const { fakeVomKit } = nextLife(fromIncarnation);
75
+ /** @type {Map<string, import('@endo/promise-kit').PromiseKit<any>>} */
76
+ const previouslyWatchedPromises = new Map();
77
+ let buildTools;
78
+ try {
79
+ buildTools = await build(getBaggage());
80
+ fakeVomKit.wpm.loadWatchedPromiseTable(vref => {
81
+ // See revivePromise in liveslots.js
82
+ const { getValForSlot, valToSlot, setValForSlot } = fakeVomKit.fakeStuff;
83
+ // Assume all promises were decided by the previous incarnation
84
+ !getValForSlot(vref) || Fail`Attempting to revive known promise ${vref}`;
85
+ const pk = makePromiseKit();
86
+ previouslyWatchedPromises.set(vref, pk);
87
+ const val = pk.promise;
88
+ valToSlot.set(val, vref);
89
+ setValForSlot(vref, val);
90
+ return val;
91
+ });
92
+
93
+ fakeVomKit.vom.insistAllDurableKindsReconnected();
94
+
95
+ await eventLoopIteration();
96
+ // End of start crank
97
+ } catch (err) {
98
+ // Rollback upgrade
99
+ incarnation = oldIncarnation;
100
+ incarnationNumber = oldIncarnationNumber;
101
+ throw err;
102
+ }
103
+
104
+ // Simulate a dispatch of previously decided promise rejections
105
+ // In real swingset this could happen after some deliveries
106
+ for (const { reject } of previouslyWatchedPromises.values()) {
107
+ reject(disconnectionObject);
108
+ }
109
+ await eventLoopIteration();
110
+ // End of resolution dispatch crank
111
+
112
+ if (run) {
113
+ await run(buildTools);
114
+ await eventLoopIteration();
115
+ }
116
+
117
+ return incarnation;
118
+ };
119
+
120
+ // Setup the initial incarnation
121
+ annihilate();
@@ -12,7 +12,9 @@ import { makeFakeVirtualStuff } from './fakeVirtualSupport.js';
12
12
 
13
13
  const { WeakMap, WeakSet } = globalThis;
14
14
 
15
- /** @type {ReturnType<makeFakeVirtualStuff>} */
15
+ /** @typedef {ReturnType<typeof makeFakeVirtualStuff>} FakeVomKit */
16
+
17
+ /** @type {FakeVomKit} */
16
18
  let fakeVomKit;
17
19
 
18
20
  globalThis.VatData = harden({
@@ -44,18 +46,43 @@ globalThis.VatData = harden({
44
46
 
45
47
  globalThis[PassStyleOfEndowmentSymbol] = passStyleOf;
46
48
 
47
- export const reincarnate = (options = {}) => {
48
- const { fakeStore = new Map(), fakeVomKit: fvk } = options;
49
+ /**
50
+ * @typedef {import("@agoric/internal").Simplify<
51
+ * Omit<NonNullable<Parameters<typeof makeFakeVirtualStuff>[0]>, 'WeakMap' | 'WeakSet'> &
52
+ * { fakeVomKit: FakeVomKit; fakeStore: Map<string, string> }
53
+ * >} ReincarnateOptions
54
+ */
49
55
 
50
- if (options.fakeVomKit) {
56
+ /**
57
+ *
58
+ * @param {Partial<ReincarnateOptions>} options
59
+ * @returns {Omit<ReincarnateOptions, 'fakeVomKit'>}
60
+ */
61
+ export const flushIncarnation = (options = {}) => {
62
+ const { fakeVomKit: fvk = fakeVomKit, ...fakeStuffOptions } = options;
63
+
64
+ if (fvk) {
51
65
  fvk.vom.flushStateCache();
52
66
  fvk.cm.flushSchemaCache();
53
67
  fvk.vrm.flushIDCounters();
54
68
  }
55
69
 
70
+ // Clone previous fakeStore (if any) to avoid mutations from previous incarnation
71
+ const fakeStore = new Map(options.fakeStore);
72
+
73
+ return { ...fakeStuffOptions, fakeStore };
74
+ };
75
+
76
+ /**
77
+ *
78
+ * @param {Partial<ReincarnateOptions>} options
79
+ * @returns {ReincarnateOptions}
80
+ */
81
+ export const reincarnate = (options = {}) => {
82
+ const clonedIncarnation = flushIncarnation(options);
83
+
56
84
  fakeVomKit = makeFakeVirtualStuff({
57
- ...options,
58
- fakeStore,
85
+ ...clonedIncarnation,
59
86
  WeakMap,
60
87
  WeakSet,
61
88
  });
@@ -65,5 +92,5 @@ export const reincarnate = (options = {}) => {
65
92
  // @ts-expect-error ditto
66
93
  globalThis.WeakSet = fakeVomKit.vom.VirtualObjectAwareWeakSet;
67
94
 
68
- return { ...options, fakeStore, fakeVomKit };
95
+ return { ...clonedIncarnation, fakeVomKit };
69
96
  };