@agoric/swingset-liveslots 0.10.3-other-dev-8f8782b.0 → 0.10.3-other-dev-fbe72e7.0.fbe72e7

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.
Files changed (141) hide show
  1. package/README.md +2 -0
  2. package/package.json +34 -26
  3. package/src/boyd-gc.d.ts +12 -0
  4. package/src/boyd-gc.d.ts.map +1 -0
  5. package/src/boyd-gc.js +598 -0
  6. package/src/cache.d.ts +71 -0
  7. package/src/cache.d.ts.map +1 -0
  8. package/src/cache.js +3 -2
  9. package/src/capdata.d.ts +16 -0
  10. package/src/capdata.d.ts.map +1 -0
  11. package/src/capdata.js +17 -10
  12. package/src/collectionManager.d.ts +47 -0
  13. package/src/collectionManager.d.ts.map +1 -0
  14. package/src/collectionManager.js +220 -103
  15. package/src/facetiousness.d.ts +25 -0
  16. package/src/facetiousness.d.ts.map +1 -0
  17. package/src/facetiousness.js +1 -1
  18. package/src/index.d.ts +4 -0
  19. package/src/index.d.ts.map +1 -0
  20. package/src/index.js +4 -2
  21. package/src/kdebug.d.ts +7 -0
  22. package/src/kdebug.d.ts.map +1 -0
  23. package/src/liveslots.d.ts +42 -0
  24. package/src/liveslots.d.ts.map +1 -0
  25. package/src/liveslots.js +137 -305
  26. package/src/message.d.ts +49 -0
  27. package/src/message.d.ts.map +1 -0
  28. package/src/message.js +9 -5
  29. package/src/parseVatSlots.d.ts +125 -0
  30. package/src/parseVatSlots.d.ts.map +1 -0
  31. package/src/parseVatSlots.js +1 -1
  32. package/src/types-index.d.ts +4 -0
  33. package/src/types-index.js +2 -0
  34. package/src/types.d.ts +81 -0
  35. package/src/types.d.ts.map +1 -0
  36. package/src/types.js +14 -7
  37. package/src/vatDataTypes.d.ts +170 -0
  38. package/src/vatDataTypes.d.ts.map +1 -0
  39. package/src/vatDataTypes.ts +272 -0
  40. package/src/vatstore-iterators.d.ts +4 -0
  41. package/src/vatstore-iterators.d.ts.map +1 -0
  42. package/src/vatstore-iterators.js +2 -0
  43. package/src/vatstore-usage.md +198 -0
  44. package/src/virtualObjectManager.d.ts +44 -0
  45. package/src/virtualObjectManager.d.ts.map +1 -0
  46. package/src/virtualObjectManager.js +254 -84
  47. package/src/virtualReferences.d.ts +61 -0
  48. package/src/virtualReferences.d.ts.map +1 -0
  49. package/src/virtualReferences.js +135 -26
  50. package/src/vpid-tracking.md +92 -0
  51. package/src/watchedPromises.d.ts +31 -0
  52. package/src/watchedPromises.d.ts.map +1 -0
  53. package/src/watchedPromises.js +81 -24
  54. package/test/{test-baggage.js → baggage.test.js} +1 -2
  55. package/test/{test-cache.js → cache.test.js} +0 -1
  56. package/test/clear-collection.test.js +586 -0
  57. package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
  58. package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
  59. package/test/{test-collections.js → collections.test.js} +183 -18
  60. package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
  61. package/test/dropped-weakset-9939.test.js +80 -0
  62. package/test/dummyMeterControl.d.ts +2 -0
  63. package/test/dummyMeterControl.d.ts.map +1 -0
  64. package/test/dummyMeterControl.js +1 -1
  65. package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
  66. package/test/engine-gc.d.ts +3 -0
  67. package/test/engine-gc.d.ts.map +1 -0
  68. package/test/exo-utils.js +70 -0
  69. package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
  70. package/test/gc-and-finalize.d.ts +5 -0
  71. package/test/gc-and-finalize.d.ts.map +1 -0
  72. package/test/gc-and-finalize.js +30 -1
  73. package/test/gc-before-finalizer.test.js +230 -0
  74. package/test/gc-helpers.js +4 -5
  75. package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
  76. package/test/handled-promises.test.js +872 -0
  77. package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +13 -20
  78. package/test/liveslots-helpers.d.ts +64 -0
  79. package/test/liveslots-helpers.d.ts.map +1 -0
  80. package/test/liveslots-helpers.js +13 -7
  81. package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +101 -2
  82. package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +73 -46
  83. package/test/{test-liveslots.js → liveslots.test.js} +17 -18
  84. package/test/mock-gc.js +1 -0
  85. package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +15 -14
  86. package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
  87. package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
  88. package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
  89. package/test/strict-test-env-upgrade.test.js +94 -0
  90. package/test/util.d.ts +25 -0
  91. package/test/util.d.ts.map +1 -0
  92. package/test/util.js +4 -4
  93. package/test/vat-environment.test.js +65 -0
  94. package/test/vat-util.d.ts +9 -0
  95. package/test/vat-util.d.ts.map +1 -0
  96. package/test/vat-util.js +2 -2
  97. package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
  98. package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
  99. package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
  100. package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
  101. package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
  102. package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
  103. package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +3 -5
  104. package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
  105. package/test/virtual-objects/set-debug-label-instances.js +1 -1
  106. package/test/virtual-objects/state-shape.test.js +389 -0
  107. package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +39 -38
  108. package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +104 -8
  109. package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
  110. package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
  111. package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +13 -10
  112. package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
  113. package/test/waitUntilQuiescent.d.ts +3 -0
  114. package/test/waitUntilQuiescent.d.ts.map +1 -0
  115. package/test/waitUntilQuiescent.js +2 -1
  116. package/test/weakset-dropped-remotable.test.js +50 -0
  117. package/tools/fakeCollectionManager.d.ts +14 -0
  118. package/tools/fakeCollectionManager.d.ts.map +1 -0
  119. package/tools/fakeCollectionManager.js +44 -0
  120. package/tools/fakeVirtualObjectManager.d.ts +32 -0
  121. package/tools/fakeVirtualObjectManager.d.ts.map +1 -0
  122. package/tools/fakeVirtualObjectManager.js +62 -0
  123. package/tools/fakeVirtualSupport.d.ts +278 -0
  124. package/tools/fakeVirtualSupport.d.ts.map +1 -0
  125. package/tools/fakeVirtualSupport.js +389 -0
  126. package/tools/prepare-strict-test-env.d.ts +37 -0
  127. package/tools/prepare-strict-test-env.d.ts.map +1 -0
  128. package/tools/prepare-strict-test-env.js +124 -0
  129. package/tools/prepare-test-env.d.ts +2 -0
  130. package/tools/prepare-test-env.d.ts.map +1 -0
  131. package/tools/prepare-test-env.js +13 -0
  132. package/tools/setup-vat-data.d.ts +9 -0
  133. package/tools/setup-vat-data.d.ts.map +1 -0
  134. package/tools/setup-vat-data.js +95 -0
  135. package/tools/vo-test-harness.d.ts +33 -0
  136. package/tools/vo-test-harness.d.ts.map +1 -0
  137. package/tools/vo-test-harness.js +164 -0
  138. package/CHANGELOG.md +0 -61
  139. package/test/kmarshal.js +0 -79
  140. package/test/test-handled-promises.js +0 -360
  141. package/test/virtual-objects/test-state-shape.js +0 -298
@@ -0,0 +1,872 @@
1
+ import test from 'ava';
2
+
3
+ import { Fail } from '@endo/errors';
4
+ import { Far } from '@endo/marshal';
5
+ import { M } from '@agoric/store';
6
+ import { makePromiseKit } from '@endo/promise-kit';
7
+ // Disabled to avoid circular dependencies.
8
+ // import { makeStoreUtils } from '@agoric/vat-data/src/vat-data-bindings.js';
9
+ // import { makeExoUtils } from '@agoric/vat-data/src/exo-utils.js';
10
+ import { kslot, kser } from '@agoric/kmarshal';
11
+ import { setupTestLiveslots } from './liveslots-helpers.js';
12
+ import { makeResolve, makeReject } from './util.js';
13
+ import { makeExoUtils } from './exo-utils.js';
14
+
15
+ // eslint-disable-next-line no-unused-vars
16
+ const compareEntriesByKey = ([ka], [kb]) => (ka < kb ? -1 : 1);
17
+
18
+ // cf. packages/SwingSet/test/vat-durable-promise-watcher.js
19
+ const buildPromiseWatcherRootObject = (vatPowers, vatParameters, baggage) => {
20
+ const { VatData } = vatPowers;
21
+ const { watchPromise } = VatData;
22
+ const { prepareExo } = makeExoUtils(VatData);
23
+ // const { makeScalarBigMapStore } = makeStoreUtils(VatData);
24
+ const PromiseWatcherI = M.interface('ExtraArgPromiseWatcher', {
25
+ onFulfilled: M.call(M.any(), M.string()).returns(),
26
+ onRejected: M.call(M.any(), M.string()).returns(),
27
+ });
28
+ const watchResolutions = new Map();
29
+ const watcher = prepareExo(
30
+ baggage,
31
+ // No longer ignoring, but the name is set in stone in `kvStoreDataV1`
32
+ 'DurablePromiseIgnorer',
33
+ PromiseWatcherI,
34
+ {
35
+ onFulfilled(value, name) {
36
+ watchResolutions.set(name, { status: 'fulfilled', value });
37
+ },
38
+ onRejected(reason, name) {
39
+ watchResolutions.set(name, { status: 'rejected', reason });
40
+ },
41
+ },
42
+ );
43
+
44
+ const knownPromises = new Map();
45
+
46
+ const root = Far('root', {
47
+ getPromise: name => {
48
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
49
+ const { promise } = knownPromises.get(name);
50
+ return { promise };
51
+ },
52
+ importPromise: (name, promise) => {
53
+ !knownPromises.has(name) || Fail`promise already exists: ${name}`;
54
+ knownPromises.set(name, { promise });
55
+ return `imported promise: ${name}`;
56
+ },
57
+ createLocalPromise: (name, fulfillment, rejection) => {
58
+ !knownPromises.has(name) || Fail`promise already exists: ${name}`;
59
+ const { promise, resolve, reject } = makePromiseKit();
60
+ let resolvers = {};
61
+ if (fulfillment !== undefined) {
62
+ resolve(fulfillment);
63
+ } else if (rejection !== undefined) {
64
+ reject(rejection);
65
+ } else {
66
+ resolvers = { resolve, reject };
67
+ }
68
+ knownPromises.set(name, { promise, ...resolvers });
69
+ return `created local promise: ${name}`;
70
+ },
71
+ resolveLocalPromise: (name, rejection, value) => {
72
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
73
+ const { resolve, reject, promise } = knownPromises.get(name);
74
+ (resolve && reject) || Fail`promise not resolvable: ${name}`;
75
+ (rejection ? reject : resolve)(value);
76
+ knownPromises.set(name, { promise });
77
+ return `resolved promise: ${name}`;
78
+ },
79
+ watchPromise: name => {
80
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
81
+ watchPromise(knownPromises.get(name).promise, watcher, name);
82
+ return `watched promise: ${name}`;
83
+ },
84
+ getWatchResolution: name => {
85
+ return watchResolutions.get(name);
86
+ },
87
+ sendToPromise: (name, method, ...args) => {
88
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
89
+ const { promise } = knownPromises.get(name);
90
+ return HandledPromise.applyMethod(promise, method, args);
91
+ },
92
+ });
93
+
94
+ const startOperations = vatParameters?.startOperations || [];
95
+ for (const [method, ...args] of startOperations) {
96
+ root[method](...args);
97
+ }
98
+
99
+ return root;
100
+ };
101
+ const kvStoreDataV1 = Object.entries({
102
+ baggageID: 'o+d6/1',
103
+ idCounters: '{"exportID":11,"collectionID":5,"promiseID":9}',
104
+ kindIDID: '1',
105
+ storeKindIDTable:
106
+ '{"scalarMapStore":2,"scalarWeakMapStore":3,"scalarSetStore":4,"scalarWeakSetStore":5,"scalarDurableMapStore":6,"scalarDurableWeakMapStore":7,"scalarDurableSetStore":8,"scalarDurableWeakSetStore":9}',
107
+ 'vc.1.sDurablePromiseIgnorer_kindHandle':
108
+ '{"body":"#\\"$0.Alleged: kind\\"","slots":["o+d1/10"]}',
109
+ 'vc.1.sthe_DurablePromiseIgnorer':
110
+ '{"body":"#\\"$0.Alleged: DurablePromiseIgnorer\\"","slots":["o+d10/1"]}',
111
+ 'vc.1.|entryCount': '2',
112
+ 'vc.1.|nextOrdinal': '1',
113
+ 'vc.1.|schemata':
114
+ '{"label":"baggage","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}}","slots":[]}',
115
+ // non-durable
116
+ // 'vc.2.sp+6': '{"body":"#\\"&0\\"","slots":["p+6"]}',
117
+ // 'vc.2.|entryCount': '1',
118
+ // 'vc.2.|nextOrdinal': '1',
119
+ // 'vc.2.|schemata': '{"label":"promiseRegistrations","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}',
120
+ 'vc.3.|entryCount': '0',
121
+ 'vc.3.|nextOrdinal': '1',
122
+ 'vc.3.|schemata':
123
+ '{"label":"promiseWatcherByKind","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}',
124
+ 'vc.4.sp+6':
125
+ '{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"orphaned\\"]]","slots":["o+d10/1"]}',
126
+ 'vc.4.sp-8':
127
+ '{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"unresolved\\"]]","slots":["o+d10/1"]}',
128
+ 'vc.4.sp-9':
129
+ '{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"late-rejected\\"]]","slots":["o+d10/1"]}',
130
+ 'vc.4.|entryCount': '3',
131
+ 'vc.4.|nextOrdinal': '1',
132
+ 'vc.4.|schemata':
133
+ '{"label":"watchedPromises","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:and\\",\\"payload\\":[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"},{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]}}","slots":[]}',
134
+ 'vom.dkind.10.descriptor':
135
+ '{"kindID":"10","tag":"DurablePromiseIgnorer","unfaceted":true}',
136
+ 'vom.dkind.10.nextID': '2',
137
+ 'vom.o+d10/1': '{}',
138
+ 'vom.rc.o+d1/10': '1',
139
+ 'vom.rc.o+d10/1': '3',
140
+ 'vom.rc.o+d6/1': '1',
141
+ 'vom.rc.o+d6/3': '1',
142
+ 'vom.rc.o+d6/4': '1',
143
+ watchedPromiseTableID: 'o+d6/4',
144
+ watcherTableID: 'o+d6/3',
145
+ });
146
+ const kvStoreDataV1VpidsToReject = ['p+6', 'p-9'];
147
+ const kvStoreDataV1KeysToDelete = ['vc.4.sp+6', 'vc.4.sp-9'];
148
+ const kvStoreDataV1VpidsToKeep = ['p-8'];
149
+ const kvStoreDataV1KeysToKeep = ['vc.4.sp-8'];
150
+
151
+ // Ignore vatstore syscalls.
152
+ const extractDispatchLogs = vatLogs =>
153
+ vatLogs.splice(0).filter(m => !m.type.startsWith('vatstore'));
154
+
155
+ const settlementMessage = (vpid, rejected, value) => ({
156
+ type: 'resolve',
157
+ resolutions: [[vpid, rejected, kser(value)]],
158
+ });
159
+ const fulfillmentMessage = (vpid, value) =>
160
+ settlementMessage(vpid, false, value);
161
+ const rejectionMessage = (vpid, value) => settlementMessage(vpid, true, value);
162
+ const subscribeMessage = vpid => ({
163
+ type: 'subscribe',
164
+ target: vpid,
165
+ });
166
+
167
+ /** @param {`p-${number}` | `p+${number}`} p */
168
+ const extractPNum = p => {
169
+ const r = /^p[-+](\d+)$/.exec(p);
170
+ if (!r) throw Fail`Invalid promise ${p}`;
171
+ return parseInt(r[1], 10);
172
+ };
173
+
174
+ test('past-incarnation watched promises', async t => {
175
+ const S = 'settlement';
176
+ // Anchor promise counters upon which the other assertions depend.
177
+ const firstPImport = 9;
178
+ // cf. src/liveslots.js:initialIDCounters
179
+ const firstPExport = 5;
180
+
181
+ const startImportedP = `p-${firstPImport - 2}`;
182
+
183
+ const v1StartOperations = [
184
+ ['createLocalPromise', 'start orphaned'],
185
+ ['watchPromise', 'start orphaned'],
186
+ ['createLocalPromise', 'start fulfilled', S],
187
+ ['watchPromise', 'start fulfilled'],
188
+ ['importPromise', 'start imported', kslot(startImportedP)],
189
+ ['watchPromise', 'start imported'],
190
+ ];
191
+ const kvStore = new Map();
192
+ let {
193
+ v,
194
+ dispatch,
195
+ dispatchMessage: rawDispatch,
196
+ } = await setupTestLiveslots(
197
+ t,
198
+ buildPromiseWatcherRootObject,
199
+ 'durable-promise-watcher',
200
+ {
201
+ kvStore,
202
+ nextPromiseImportNumber: firstPImport,
203
+ vatParameters: { startOperations: v1StartOperations },
204
+ },
205
+ );
206
+ let vatLogs = v.log;
207
+
208
+ /** @type {`p-${number}`} */
209
+ let rp = `p-0`;
210
+ /** @type {typeof rawDispatch} */
211
+ const dispatchMessage = async (...args) => {
212
+ rp = /** @type {`p-${number}`} */ (await rawDispatch(...args));
213
+ return rp;
214
+ };
215
+ let lastPExport = firstPExport - 1;
216
+ const exportedPromises = new Map();
217
+ const recordNextExportedPromise = name => {
218
+ const p = `p+${(lastPExport += 1)}`;
219
+ exportedPromises.set(p, name);
220
+ return p;
221
+ };
222
+ const recordExportedPromiseNotification = p => {
223
+ t.true(exportedPromises.delete(p));
224
+ return p;
225
+ };
226
+
227
+ // startVat logs
228
+ const startOrphanedP = recordNextExportedPromise('start orphaned');
229
+ const startFulfilledP = recordNextExportedPromise('start fulfilled');
230
+ t.deepEqual(extractDispatchLogs(vatLogs), [
231
+ subscribeMessage(startImportedP),
232
+ subscribeMessage(startOrphanedP),
233
+ subscribeMessage(startFulfilledP),
234
+ fulfillmentMessage(startFulfilledP, S),
235
+ ]);
236
+ await dispatchMessage('createLocalPromise', 'exported', S);
237
+ t.deepEqual(extractDispatchLogs(vatLogs), [
238
+ fulfillmentMessage(rp, 'created local promise: exported'),
239
+ ]);
240
+ await dispatchMessage('getPromise', 'exported');
241
+ const exportedP = recordNextExportedPromise('exported');
242
+ t.deepEqual(extractDispatchLogs(vatLogs), [
243
+ fulfillmentMessage(rp, {
244
+ promise: kslot(exportedP),
245
+ }),
246
+ fulfillmentMessage(recordExportedPromiseNotification(exportedP), S),
247
+ ]);
248
+ const importedP = `p-${firstPImport - 1}`;
249
+ await dispatchMessage('importPromise', 'imported', kslot(importedP));
250
+ t.deepEqual(extractDispatchLogs(vatLogs), [
251
+ subscribeMessage(importedP),
252
+ fulfillmentMessage(rp, 'imported promise: imported'),
253
+ ]);
254
+ await dispatchMessage('createLocalPromise', 'orphaned');
255
+ t.deepEqual(extractDispatchLogs(vatLogs), [
256
+ fulfillmentMessage(rp, 'created local promise: orphaned'),
257
+ ]);
258
+ await dispatchMessage('createLocalPromise', 'orphaned exported');
259
+ t.deepEqual(extractDispatchLogs(vatLogs), [
260
+ fulfillmentMessage(rp, 'created local promise: orphaned exported'),
261
+ ]);
262
+ await dispatchMessage('getPromise', 'orphaned exported');
263
+ const orphanedExportedP = recordNextExportedPromise('orphaned exported');
264
+ t.deepEqual(extractDispatchLogs(vatLogs), [
265
+ fulfillmentMessage(rp, {
266
+ promise: kslot(orphanedExportedP),
267
+ }),
268
+ ]);
269
+ await dispatchMessage('createLocalPromise', 'fulfilled', S);
270
+ t.deepEqual(extractDispatchLogs(vatLogs), [
271
+ fulfillmentMessage(rp, 'created local promise: fulfilled'),
272
+ ]);
273
+ await dispatchMessage('createLocalPromise', 'rejected', undefined, S);
274
+ t.deepEqual(extractDispatchLogs(vatLogs), [
275
+ fulfillmentMessage(rp, 'created local promise: rejected'),
276
+ ]);
277
+
278
+ await dispatchMessage('watchPromise', 'orphaned');
279
+ const orphanedP = recordNextExportedPromise('orphaned');
280
+ t.deepEqual(extractDispatchLogs(vatLogs), [
281
+ subscribeMessage(orphanedP),
282
+ fulfillmentMessage(rp, 'watched promise: orphaned'),
283
+ ]);
284
+ await dispatchMessage('watchPromise', 'fulfilled');
285
+ const fulfilledP = recordNextExportedPromise('fulfilled');
286
+ t.deepEqual(extractDispatchLogs(vatLogs), [
287
+ subscribeMessage(fulfilledP),
288
+ fulfillmentMessage(rp, 'watched promise: fulfilled'),
289
+ fulfillmentMessage(recordExportedPromiseNotification(fulfilledP), S),
290
+ ]);
291
+ await dispatchMessage('watchPromise', 'rejected');
292
+ const rejectedP = recordNextExportedPromise('rejected');
293
+ t.deepEqual(extractDispatchLogs(vatLogs), [
294
+ subscribeMessage(rejectedP),
295
+ fulfillmentMessage(rp, 'watched promise: rejected'),
296
+ rejectionMessage(recordExportedPromiseNotification(rejectedP), S),
297
+ ]);
298
+ await dispatchMessage('watchPromise', 'imported');
299
+ t.deepEqual(extractDispatchLogs(vatLogs), [
300
+ // no subscribe, we already did at import
301
+ fulfillmentMessage(rp, 'watched promise: imported'),
302
+ ]);
303
+ await dispatchMessage('getWatchResolution', 'fulfilled');
304
+ t.deepEqual(extractDispatchLogs(vatLogs), [
305
+ fulfillmentMessage(rp, {
306
+ status: 'fulfilled',
307
+ value: S,
308
+ }),
309
+ ]);
310
+ await dispatchMessage('getWatchResolution', 'rejected');
311
+ t.deepEqual(extractDispatchLogs(vatLogs), [
312
+ fulfillmentMessage(rp, {
313
+ status: 'rejected',
314
+ reason: S,
315
+ }),
316
+ ]);
317
+ await dispatchMessage('getWatchResolution', 'start fulfilled');
318
+ t.deepEqual(extractDispatchLogs(vatLogs), [
319
+ fulfillmentMessage(rp, {
320
+ status: 'fulfilled',
321
+ value: S,
322
+ }),
323
+ ]);
324
+
325
+ const v2FirstPromise = extractPNum(rp) + 1;
326
+
327
+ // Simulate upgrade by starting from the non-empty kvStore.
328
+ // t.log(Object.fromEntries([...kvStore.entries()].sort(compareEntriesByKey)));
329
+ const clonedStore = new Map(kvStore);
330
+ const startImported2P = `p-${firstPImport - 3}`;
331
+ const v2StartOperations = [
332
+ ['importPromise', 'start imported 2', kslot(startImported2P)], // import of new promise
333
+ ['importPromise', 'imported', kslot(importedP)], // import previously imported and watched promise
334
+ ['importPromise', 'orphaned exported', kslot(orphanedExportedP)], // import previously exported but unwatched promise
335
+ ['watchPromise', 'orphaned exported'],
336
+ ];
337
+ ({
338
+ v,
339
+ dispatch,
340
+ dispatchMessage: rawDispatch,
341
+ } = await setupTestLiveslots(
342
+ t,
343
+ buildPromiseWatcherRootObject,
344
+ 'durable-promise-watcher-v2',
345
+ {
346
+ kvStore: clonedStore,
347
+ nextPromiseImportNumber: v2FirstPromise,
348
+ vatParameters: { startOperations: v2StartOperations },
349
+ },
350
+ ));
351
+ vatLogs = v.log;
352
+
353
+ // startVat logs
354
+ t.deepEqual(extractDispatchLogs(vatLogs), [
355
+ subscribeMessage(startImported2P),
356
+ subscribeMessage(importedP),
357
+ subscribeMessage(orphanedExportedP),
358
+ ]);
359
+ // Simulate kernel rejection of promises orphaned by termination/upgrade of their decider vat.
360
+ const expectedDeletions = [...clonedStore.entries()].filter(entry =>
361
+ entry[1].includes('orphaned'),
362
+ );
363
+ t.true(expectedDeletions.length >= 1);
364
+ for (const [orphanedPExport] of exportedPromises) {
365
+ await dispatch(makeReject(orphanedPExport, kser('tomorrow never came')));
366
+ }
367
+ exportedPromises.clear();
368
+ await dispatchMessage('getWatchResolution', 'orphaned');
369
+ t.deepEqual(extractDispatchLogs(vatLogs), [
370
+ fulfillmentMessage(rp, {
371
+ status: 'rejected',
372
+ reason: 'tomorrow never came',
373
+ }),
374
+ ]);
375
+ await dispatchMessage('getWatchResolution', 'start orphaned');
376
+ t.deepEqual(extractDispatchLogs(vatLogs), [
377
+ fulfillmentMessage(rp, {
378
+ status: 'rejected',
379
+ reason: 'tomorrow never came',
380
+ }),
381
+ ]);
382
+ await dispatchMessage('getWatchResolution', 'orphaned exported');
383
+ t.deepEqual(extractDispatchLogs(vatLogs), [
384
+ fulfillmentMessage(rp, {
385
+ status: 'rejected',
386
+ reason: 'tomorrow never came',
387
+ }),
388
+ ]);
389
+ for (const [key, value] of expectedDeletions) {
390
+ t.false(clonedStore.has(key), `entry should be removed: ${key}: ${value}`);
391
+ }
392
+ // Simulate resolution of imported promises watched in previous incarnation
393
+ await dispatch(makeResolve(importedP, kser(undefined)));
394
+ await dispatchMessage('getWatchResolution', 'imported');
395
+ t.deepEqual(extractDispatchLogs(vatLogs), [
396
+ fulfillmentMessage(rp, {
397
+ status: 'fulfilled',
398
+ value: undefined,
399
+ }),
400
+ ]);
401
+ await dispatch(makeResolve(startImportedP, kser(undefined)));
402
+ await dispatchMessage('getWatchResolution', 'start imported');
403
+ t.deepEqual(extractDispatchLogs(vatLogs), [
404
+ fulfillmentMessage(rp, {
405
+ status: 'fulfilled',
406
+ value: undefined,
407
+ }),
408
+ ]);
409
+ await dispatch(makeResolve(startImported2P, kser(undefined)));
410
+ await dispatchMessage('getWatchResolution', 'start imported 2');
411
+ t.deepEqual(extractDispatchLogs(vatLogs), [
412
+ fulfillmentMessage(rp, undefined),
413
+ ]);
414
+ // simulate resolution of imported promise watched after resolution
415
+ await dispatchMessage('watchPromise', 'start imported 2');
416
+ const startImported2ReexportedP =
417
+ recordNextExportedPromise('start imported 2');
418
+ t.deepEqual(extractDispatchLogs(vatLogs), [
419
+ // Promise was previously resolved, so it is re-exported
420
+ subscribeMessage(startImported2ReexportedP),
421
+ fulfillmentMessage(rp, 'watched promise: start imported 2'),
422
+ fulfillmentMessage(
423
+ recordExportedPromiseNotification(startImported2ReexportedP),
424
+ undefined,
425
+ ),
426
+ ]);
427
+ await dispatchMessage('getWatchResolution', 'start imported 2');
428
+ t.deepEqual(extractDispatchLogs(vatLogs), [
429
+ fulfillmentMessage(rp, {
430
+ status: 'fulfilled',
431
+ value: undefined,
432
+ }),
433
+ ]);
434
+
435
+ const finalFirstPromise = extractPNum(rp) + 1;
436
+
437
+ // Verify that the data is still in loadable condition.
438
+ const finalClonedStore = new Map(clonedStore);
439
+ ({
440
+ v,
441
+ dispatch,
442
+ dispatchMessage: rawDispatch,
443
+ } = await setupTestLiveslots(
444
+ t,
445
+ buildPromiseWatcherRootObject,
446
+ 'durable-promise-watcher-final',
447
+ { kvStore: finalClonedStore, nextPromiseImportNumber: finalFirstPromise },
448
+ ));
449
+ vatLogs = v.log;
450
+ vatLogs.length = 0;
451
+ t.deepEqual([...exportedPromises], [], 'exportedPromises is empty');
452
+ await dispatchMessage('createLocalPromise', 'final', S);
453
+ t.deepEqual(extractDispatchLogs(vatLogs), [
454
+ fulfillmentMessage(rp, 'created local promise: final'),
455
+ ]);
456
+ await dispatchMessage('watchPromise', 'final');
457
+ const finalP = recordNextExportedPromise('final');
458
+ t.deepEqual(extractDispatchLogs(vatLogs), [
459
+ subscribeMessage(finalP),
460
+ fulfillmentMessage(rp, 'watched promise: final'),
461
+ fulfillmentMessage(recordExportedPromiseNotification(finalP), S),
462
+ ]);
463
+ await dispatchMessage('getWatchResolution', 'final');
464
+ t.deepEqual(extractDispatchLogs(vatLogs), [
465
+ fulfillmentMessage(rp, {
466
+ status: 'fulfilled',
467
+ value: S,
468
+ }),
469
+ ]);
470
+ });
471
+
472
+ test('past-incarnation watched promises from original-format kvStore', async t => {
473
+ const kvStore = new Map(kvStoreDataV1);
474
+ for (const key of [
475
+ ...kvStoreDataV1KeysToDelete,
476
+ ...kvStoreDataV1KeysToKeep,
477
+ ]) {
478
+ t.true(kvStore.has(key), `key must be initially present: ${key}`);
479
+ }
480
+
481
+ let { v, dispatch, dispatchMessage } = await setupTestLiveslots(
482
+ t,
483
+ buildPromiseWatcherRootObject,
484
+ 'durable-promise-watcher',
485
+ { kvStore, nextPromiseImportNumber: 100 },
486
+ );
487
+ let vatLogs = v.log;
488
+ for (const vpid of kvStoreDataV1VpidsToReject) {
489
+ await dispatch(makeReject(vpid, kser('tomorrow never came')));
490
+ }
491
+ for (const key of kvStoreDataV1KeysToDelete) {
492
+ t.false(kvStore.has(key), `key should be removed: ${key}`);
493
+ }
494
+ for (const key of kvStoreDataV1KeysToKeep) {
495
+ t.true(kvStore.has(key), `key should remain: ${key}`);
496
+ }
497
+
498
+ // Verify that the data is still in loadable condition.
499
+ const finalClonedStore = new Map(kvStore);
500
+ // eslint-disable-next-line no-unused-vars
501
+ ({ v, dispatch, dispatchMessage } = await setupTestLiveslots(
502
+ t,
503
+ buildPromiseWatcherRootObject,
504
+ 'durable-promise-watcher-final',
505
+ { kvStore: finalClonedStore, nextPromiseImportNumber: 200 },
506
+ ));
507
+ vatLogs = v.log;
508
+ vatLogs.length = 0;
509
+ for (const vpid of kvStoreDataV1VpidsToKeep) {
510
+ await dispatch(makeResolve(vpid, kser('finally')));
511
+ }
512
+ for (const key of kvStoreDataV1KeysToKeep) {
513
+ t.false(finalClonedStore.has(key), `key should be removed: ${key}`);
514
+ }
515
+ });
516
+
517
+ test('watched local promises should not leak slotToVal entries', async t => {
518
+ const S = 'settlement';
519
+ // cf. src/liveslots.js:initialIDCounters
520
+ const firstPExport = 5;
521
+
522
+ const {
523
+ v: { log: vatLogs },
524
+ dispatchMessage,
525
+ testHooks,
526
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
527
+ const { slotToVal } = testHooks;
528
+ const initial = slotToVal.size;
529
+
530
+ let lastPExport = firstPExport - 1;
531
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
532
+
533
+ let rp;
534
+
535
+ // Watch already resolved promise
536
+ rp = await dispatchMessage('createLocalPromise', 'p1', S);
537
+ t.deepEqual(extractDispatchLogs(vatLogs), [
538
+ fulfillmentMessage(rp, 'created local promise: p1'),
539
+ ]);
540
+ rp = await dispatchMessage('watchPromise', 'p1');
541
+ const p1 = nextPExport();
542
+ t.deepEqual(extractDispatchLogs(vatLogs), [
543
+ subscribeMessage(p1),
544
+ fulfillmentMessage(rp, 'watched promise: p1'),
545
+ fulfillmentMessage(p1, S),
546
+ ]);
547
+ t.is(slotToVal.size, initial); // exported promise did not leak
548
+ rp = await dispatchMessage('getWatchResolution', 'p1');
549
+ t.deepEqual(extractDispatchLogs(vatLogs), [
550
+ fulfillmentMessage(rp, {
551
+ status: 'fulfilled',
552
+ value: S,
553
+ }),
554
+ ]);
555
+
556
+ // Watch subsequently resolved promise
557
+ rp = await dispatchMessage('createLocalPromise', 'p2');
558
+ t.deepEqual(extractDispatchLogs(vatLogs), [
559
+ fulfillmentMessage(rp, 'created local promise: p2'),
560
+ ]);
561
+ t.is(slotToVal.size, initial);
562
+
563
+ rp = await dispatchMessage('watchPromise', 'p2');
564
+ const p2 = nextPExport();
565
+ t.deepEqual(extractDispatchLogs(vatLogs), [
566
+ subscribeMessage(p2),
567
+ fulfillmentMessage(rp, 'watched promise: p2'),
568
+ ]);
569
+ t.is(slotToVal.size, initial + 1); // exported promise
570
+
571
+ rp = await dispatchMessage('resolveLocalPromise', 'p2', false, S);
572
+ t.deepEqual(extractDispatchLogs(vatLogs), [
573
+ fulfillmentMessage(p2, S),
574
+ fulfillmentMessage(rp, 'resolved promise: p2'),
575
+ ]);
576
+ t.is(slotToVal.size, initial); // exported promise did not leak
577
+
578
+ rp = await dispatchMessage('getWatchResolution', 'p2');
579
+ t.deepEqual(extractDispatchLogs(vatLogs), [
580
+ fulfillmentMessage(rp, {
581
+ status: 'fulfilled',
582
+ value: S,
583
+ }),
584
+ ]);
585
+ });
586
+
587
+ // Remaining case for https://github.com/Agoric/agoric-sdk/issues/10756
588
+ // The workaround doesn't handle this case because it learns about the
589
+ // settlement before the virtual object system
590
+ // See https://github.com/Agoric/agoric-sdk/issues/10757
591
+ test('watched imported promises should not leak slotToVal entries', async t => {
592
+ const S = 'settlement';
593
+ // cf. src/liveslots.js:initialIDCounters
594
+ const firstPExport = 5;
595
+
596
+ const {
597
+ v: { log: vatLogs },
598
+ dispatch,
599
+ dispatchMessage,
600
+ nextPImport,
601
+ testHooks,
602
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
603
+ const { slotToVal } = testHooks;
604
+ const initial = slotToVal.size;
605
+
606
+ let lastPExport = firstPExport - 1;
607
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
608
+
609
+ let rp;
610
+
611
+ // Watch already imported promise
612
+ const importedP = nextPImport();
613
+ rp = await dispatchMessage('importPromise', 'importedP', kslot(importedP));
614
+ t.deepEqual(extractDispatchLogs(vatLogs), [
615
+ subscribeMessage(importedP),
616
+ fulfillmentMessage(rp, 'imported promise: importedP'),
617
+ ]);
618
+ t.is(slotToVal.size, initial + 1); // imported promise
619
+
620
+ rp = await dispatchMessage('watchPromise', 'importedP');
621
+ t.deepEqual(extractDispatchLogs(vatLogs), [
622
+ fulfillmentMessage(rp, 'watched promise: importedP'),
623
+ ]);
624
+ t.is(slotToVal.size, initial + 1); // imported promise
625
+
626
+ await dispatch(makeResolve(importedP, kser(S)));
627
+ t.deepEqual(extractDispatchLogs(vatLogs), []);
628
+ t.is(slotToVal.size, initial); // should not leak
629
+
630
+ rp = await dispatchMessage('getPromise', 'importedP');
631
+ const reexportedP = nextPExport(); // Should allocate a new exported promise
632
+ t.deepEqual(extractDispatchLogs(vatLogs), [
633
+ fulfillmentMessage(rp, { promise: kslot(reexportedP) }),
634
+ fulfillmentMessage(reexportedP, S),
635
+ ]);
636
+ t.is(slotToVal.size, initial); // reexported promise did not leak
637
+ });
638
+
639
+ test('known imported promises in resolutions should not leak slotToVal entries', async t => {
640
+ const S = 'settlement';
641
+ // cf. src/liveslots.js:initialIDCounters
642
+ const firstPExport = 5;
643
+
644
+ const {
645
+ v: { log: vatLogs },
646
+ dispatch,
647
+ dispatchMessage,
648
+ nextPImport,
649
+ testHooks,
650
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
651
+ const { slotToVal } = testHooks;
652
+ const initial = slotToVal.size;
653
+
654
+ let lastPExport = firstPExport - 1;
655
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
656
+
657
+ let rp;
658
+
659
+ const importedP = nextPImport();
660
+ rp = await dispatchMessage('importPromise', 'importedP', kslot(importedP));
661
+ t.deepEqual(extractDispatchLogs(vatLogs), [
662
+ subscribeMessage(importedP),
663
+ fulfillmentMessage(rp, 'imported promise: importedP'),
664
+ ]);
665
+ t.is(slotToVal.size, initial + 1); // imported promise
666
+
667
+ rp = await dispatchMessage('getPromise', 'importedP');
668
+ t.deepEqual(extractDispatchLogs(vatLogs), [
669
+ fulfillmentMessage(rp, { promise: kslot(importedP) }),
670
+ ]);
671
+ t.is(slotToVal.size, initial + 1); // imported promise
672
+
673
+ await dispatch(makeResolve(importedP, kser(S)));
674
+ t.deepEqual(extractDispatchLogs(vatLogs), []);
675
+ t.is(slotToVal.size, initial); // promise no longer imported
676
+
677
+ // The first getPromise causes liveslots to learn about the resolution value
678
+ // of importedP since liveslots currently only tracks known promise
679
+ // resolutions on export.
680
+ rp = await dispatchMessage('getPromise', 'importedP');
681
+ const reexportedP = nextPExport();
682
+ t.deepEqual(extractDispatchLogs(vatLogs), [
683
+ fulfillmentMessage(rp, { promise: kslot(reexportedP) }),
684
+ fulfillmentMessage(reexportedP, S), // liveslots learns about the resolution
685
+ ]);
686
+ t.is(slotToVal.size, initial);
687
+
688
+ // The second getPromise verifies that liveslots does not leak when notifying
689
+ // a known resolved promise. It allocates a new exported promise because the
690
+ // resolution was previously notified
691
+ rp = await dispatchMessage('getPromise', 'importedP');
692
+ const reexportedP2 = nextPExport(); // allocates a new export promise
693
+ t.deepEqual(extractDispatchLogs(vatLogs), [
694
+ {
695
+ type: 'resolve',
696
+ resolutions: [
697
+ [rp, false, kser({ promise: kslot(reexportedP2) })],
698
+ [reexportedP2, false, kser(S)], // one shot resolution notification
699
+ ],
700
+ },
701
+ ]);
702
+ t.is(slotToVal.size, initial); // did not leak reexportedP2
703
+
704
+ // The 3rd getPromise ensures that the 2nd fully cleaned up, and that liveslots
705
+ // doesn't attempt to re-use a the previously exported promise
706
+ rp = await dispatchMessage('getPromise', 'importedP');
707
+ const reexportedP3 = nextPExport(); // allocates a new export promise
708
+ t.deepEqual(extractDispatchLogs(vatLogs), [
709
+ {
710
+ type: 'resolve',
711
+ resolutions: [
712
+ [rp, false, kser({ promise: kslot(reexportedP3) })],
713
+ [reexportedP3, false, kser(S)], // still one shot resolution
714
+ ],
715
+ },
716
+ ]);
717
+ t.is(slotToVal.size, initial); // did not leak reexportedP3
718
+ });
719
+
720
+ test('known exported promises in resolutions should not leak slotToVal entries', async t => {
721
+ const S = 'settlement';
722
+ // cf. src/liveslots.js:initialIDCounters
723
+ const firstPExport = 5;
724
+
725
+ const {
726
+ v: { log: vatLogs },
727
+ dispatchMessage,
728
+ testHooks,
729
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
730
+ const { slotToVal } = testHooks;
731
+ const initial = slotToVal.size;
732
+
733
+ let lastPExport = firstPExport - 1;
734
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
735
+
736
+ let rp;
737
+
738
+ rp = await dispatchMessage('createLocalPromise', 'localP');
739
+ t.deepEqual(extractDispatchLogs(vatLogs), [
740
+ fulfillmentMessage(rp, 'created local promise: localP'),
741
+ ]);
742
+ t.is(slotToVal.size, initial);
743
+
744
+ rp = await dispatchMessage('getPromise', 'localP');
745
+ const localP = nextPExport();
746
+ t.deepEqual(extractDispatchLogs(vatLogs), [
747
+ fulfillmentMessage(rp, { promise: kslot(localP) }),
748
+ ]);
749
+ t.is(slotToVal.size, initial + 1); // exported promise
750
+
751
+ rp = await dispatchMessage('resolveLocalPromise', 'localP', false, S);
752
+ t.deepEqual(extractDispatchLogs(vatLogs), [
753
+ fulfillmentMessage(localP, S),
754
+ fulfillmentMessage(rp, 'resolved promise: localP'),
755
+ ]);
756
+ t.is(slotToVal.size, initial); // promise no longer exported
757
+
758
+ // The first getPromise verifies that liveslots does not leak when notifying
759
+ // a known resolved promise. It allocates a new exported promise because the
760
+ // resolution was previously notified
761
+ rp = await dispatchMessage('getPromise', 'localP');
762
+ const reexportedP = nextPExport();
763
+ t.deepEqual(extractDispatchLogs(vatLogs), [
764
+ {
765
+ type: 'resolve',
766
+ resolutions: [
767
+ [rp, false, kser({ promise: kslot(reexportedP) })],
768
+ [reexportedP, false, kser(S)], // one shot resolution notification
769
+ ],
770
+ },
771
+ ]);
772
+ t.is(slotToVal.size, initial);
773
+
774
+ // The second getPromise ensures that previous fully cleaned up, and that
775
+ // liveslots doesn't attempt to re-use a the previously exported promise
776
+ rp = await dispatchMessage('getPromise', 'localP');
777
+ const reexportedP2 = nextPExport(); // allocates a new export promise
778
+ t.deepEqual(extractDispatchLogs(vatLogs), [
779
+ {
780
+ type: 'resolve',
781
+ resolutions: [
782
+ [rp, false, kser({ promise: kslot(reexportedP2) })],
783
+ [reexportedP2, false, kser(S)], // still one shot resolution
784
+ ],
785
+ },
786
+ ]);
787
+ t.is(slotToVal.size, initial); // did not leak reexportedP2
788
+ });
789
+
790
+ test('known promises in message sends should not leak slotToVal entries', async t => {
791
+ const S = 'settlement';
792
+ // cf. src/liveslots.js:initialIDCounters
793
+ const firstPExport = 5;
794
+
795
+ const targetO = `o-1`;
796
+
797
+ const {
798
+ v: { log: vatLogs },
799
+ dispatch,
800
+ dispatchMessage,
801
+ testHooks,
802
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA', {
803
+ vatParameters: {
804
+ startOperations: [['createLocalPromise', 'target', kslot(targetO)]],
805
+ },
806
+ });
807
+ const { slotToVal } = testHooks;
808
+ const initial = slotToVal.size;
809
+
810
+ let lastPExport = firstPExport - 1;
811
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
812
+
813
+ let rp;
814
+
815
+ rp = await dispatchMessage('createLocalPromise', 'targetP');
816
+ t.deepEqual(extractDispatchLogs(vatLogs), [
817
+ fulfillmentMessage(rp, 'created local promise: targetP'),
818
+ ]);
819
+ t.is(slotToVal.size, initial);
820
+
821
+ rp = await dispatchMessage('createLocalPromise', 'localP');
822
+ t.deepEqual(extractDispatchLogs(vatLogs), [
823
+ fulfillmentMessage(rp, 'created local promise: localP'),
824
+ ]);
825
+ t.is(slotToVal.size, initial);
826
+
827
+ rp = await dispatchMessage('getPromise', 'localP');
828
+ const localP = nextPExport();
829
+ t.deepEqual(extractDispatchLogs(vatLogs), [
830
+ fulfillmentMessage(rp, { promise: kslot(localP) }),
831
+ ]);
832
+ t.is(slotToVal.size, initial + 1); // localP promise
833
+
834
+ rp = await dispatchMessage('sendToPromise', 'targetP', 'foo', kslot(localP));
835
+ const sendToPromiseResultP = rp;
836
+ t.deepEqual(extractDispatchLogs(vatLogs), []);
837
+ t.is(slotToVal.size, initial + 2); // localP and sendToPromiseResultP promises
838
+
839
+ rp = await dispatchMessage('resolveLocalPromise', 'localP', false, S);
840
+ t.deepEqual(extractDispatchLogs(vatLogs), [
841
+ fulfillmentMessage(localP, S),
842
+ fulfillmentMessage(rp, 'resolved promise: localP'),
843
+ ]);
844
+ t.is(slotToVal.size, initial + 1); // sendToPromiseResultP promise
845
+
846
+ rp = await dispatchMessage(
847
+ 'resolveLocalPromise',
848
+ 'targetP',
849
+ false,
850
+ kslot(targetO),
851
+ );
852
+ const reexportedP = nextPExport(); // Allocate a new promise for importedP
853
+ const sendResultP = nextPExport();
854
+ t.deepEqual(extractDispatchLogs(vatLogs), [
855
+ fulfillmentMessage(rp, 'resolved promise: targetP'), // no await so first syscall
856
+ {
857
+ type: 'send',
858
+ targetSlot: targetO,
859
+ methargs: kser(['foo', [kslot(reexportedP)]]),
860
+ resultSlot: sendResultP,
861
+ },
862
+ fulfillmentMessage(reexportedP, S), // known resolutions comes before subscribe of send result
863
+ subscribeMessage(sendResultP),
864
+ ]);
865
+ t.is(slotToVal.size, initial + 2); // sendToPromiseResultP and sendResultP promises
866
+
867
+ await dispatch(makeResolve(sendResultP, kser(S)));
868
+ t.deepEqual(extractDispatchLogs(vatLogs), [
869
+ fulfillmentMessage(sendToPromiseResultP, S),
870
+ ]);
871
+ t.is(slotToVal.size, initial);
872
+ });