@agoric/swingset-vat 0.33.0-upgrade-17-dev-ec448b0.0 → 0.33.0-upgrade-18-dev-b9b8db4.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-vat",
3
- "version": "0.33.0-upgrade-17-dev-ec448b0.0+ec448b0",
3
+ "version": "0.33.0-upgrade-18-dev-b9b8db4.0+b9b8db4",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,34 +27,34 @@
27
27
  "@types/yargs-parser": "^21.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@agoric/internal": "0.4.0-upgrade-17-dev-ec448b0.0+ec448b0",
31
- "@agoric/kmarshal": "0.1.1-upgrade-17-dev-ec448b0.0+ec448b0",
32
- "@agoric/store": "0.9.3-upgrade-17-dev-ec448b0.0+ec448b0",
33
- "@agoric/swing-store": "0.9.2-upgrade-17-dev-ec448b0.0+ec448b0",
34
- "@agoric/swingset-liveslots": "0.10.3-upgrade-17-dev-ec448b0.0+ec448b0",
35
- "@agoric/swingset-xsnap-supervisor": "0.10.3-upgrade-17-dev-ec448b0.0+ec448b0",
36
- "@agoric/time": "0.3.3-upgrade-17-dev-ec448b0.0+ec448b0",
37
- "@agoric/vat-data": "0.5.3-upgrade-17-dev-ec448b0.0+ec448b0",
38
- "@agoric/xsnap": "0.14.3-upgrade-17-dev-ec448b0.0+ec448b0",
39
- "@agoric/xsnap-lockdown": "0.14.1-upgrade-17-dev-ec448b0.0+ec448b0",
40
- "@endo/base64": "^1.0.7",
41
- "@endo/bundle-source": "^3.4.0",
42
- "@endo/captp": "^4.3.0",
43
- "@endo/check-bundle": "^1.0.9",
44
- "@endo/compartment-mapper": "^1.2.2",
45
- "@endo/errors": "^1.2.5",
46
- "@endo/eventual-send": "^1.2.5",
47
- "@endo/far": "^1.1.5",
48
- "@endo/import-bundle": "^1.2.2",
49
- "@endo/init": "^1.1.4",
50
- "@endo/marshal": "^1.5.3",
51
- "@endo/nat": "^5.0.10",
52
- "@endo/pass-style": "^1.4.3",
53
- "@endo/patterns": "^1.4.3",
54
- "@endo/promise-kit": "^1.1.5",
55
- "@endo/ses-ava": "^1.2.5",
56
- "@endo/stream": "^1.2.5",
57
- "@endo/zip": "^1.0.7",
30
+ "@agoric/internal": "0.4.0-upgrade-18-dev-b9b8db4.0+b9b8db4",
31
+ "@agoric/kmarshal": "0.1.1-upgrade-18-dev-b9b8db4.0+b9b8db4",
32
+ "@agoric/store": "0.9.3-upgrade-18-dev-b9b8db4.0+b9b8db4",
33
+ "@agoric/swing-store": "0.10.0-upgrade-18-dev-b9b8db4.0+b9b8db4",
34
+ "@agoric/swingset-liveslots": "0.10.3-upgrade-18-dev-b9b8db4.0+b9b8db4",
35
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-upgrade-18-dev-b9b8db4.0+b9b8db4",
36
+ "@agoric/time": "0.3.3-upgrade-18-dev-b9b8db4.0+b9b8db4",
37
+ "@agoric/vat-data": "0.5.3-upgrade-18-dev-b9b8db4.0+b9b8db4",
38
+ "@agoric/xsnap": "0.14.3-upgrade-18-dev-b9b8db4.0+b9b8db4",
39
+ "@agoric/xsnap-lockdown": "0.14.1-upgrade-18-dev-b9b8db4.0+b9b8db4",
40
+ "@endo/base64": "^1.0.8",
41
+ "@endo/bundle-source": "^3.4.2",
42
+ "@endo/captp": "^4.4.2",
43
+ "@endo/check-bundle": "^1.0.11",
44
+ "@endo/compartment-mapper": "^1.3.1",
45
+ "@endo/errors": "^1.2.7",
46
+ "@endo/eventual-send": "^1.2.7",
47
+ "@endo/far": "^1.1.8",
48
+ "@endo/import-bundle": "^1.3.1",
49
+ "@endo/init": "^1.1.6",
50
+ "@endo/marshal": "^1.6.1",
51
+ "@endo/nat": "^5.0.12",
52
+ "@endo/pass-style": "^1.4.6",
53
+ "@endo/patterns": "^1.4.6",
54
+ "@endo/promise-kit": "^1.1.7",
55
+ "@endo/ses-ava": "^1.2.7",
56
+ "@endo/stream": "^1.2.7",
57
+ "@endo/zip": "^1.0.8",
58
58
  "ansi-styles": "^6.2.1",
59
59
  "anylogger": "^0.21.0",
60
60
  "better-sqlite3": "^9.1.1",
@@ -101,7 +101,7 @@
101
101
  "access": "public"
102
102
  },
103
103
  "typeCoverage": {
104
- "atLeast": 75.7
104
+ "atLeast": 75.82
105
105
  },
106
- "gitHead": "ec448b081ac21cbe217f210e06f0b8f7989e73d6"
106
+ "gitHead": "b9b8db4f2cc3926bb71fb9ca516328c30863f246"
107
107
  }
@@ -366,6 +366,10 @@ export async function makeSwingsetController(
366
366
  return kref;
367
367
  },
368
368
 
369
+ kpRegisterInterest(kpid) {
370
+ return kernel.kpRegisterInterest(kpid);
371
+ },
372
+
369
373
  kpStatus(kpid) {
370
374
  return kernel.kpStatus(kpid);
371
375
  },
@@ -384,6 +388,8 @@ export async function makeSwingsetController(
384
388
  return kernel.deviceNameToID(deviceName);
385
389
  },
386
390
 
391
+ injectQueuedUpgradeEvents: () => kernel.injectQueuedUpgradeEvents(),
392
+
387
393
  /**
388
394
  * Queue a method call into the named vat
389
395
  *
@@ -1,4 +1,4 @@
1
- /* global process */
1
+ /* eslint-env node */
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
 
@@ -1,9 +1,17 @@
1
+ import { Fail } from '@endo/errors';
1
2
  import {
2
3
  DEFAULT_REAP_DIRT_THRESHOLD_KEY,
3
4
  DEFAULT_GC_KREFS_PER_BOYD,
4
5
  getAllDynamicVats,
5
6
  getAllStaticVats,
7
+ incrementReferenceCount,
8
+ readQueue,
6
9
  } from '../kernel/state/kernelKeeper.js';
10
+ import { enumeratePrefixedKeys } from '../kernel/state/storageHelper.js';
11
+
12
+ /**
13
+ * @import {RunQueueEvent} from '../types-internal.js';
14
+ */
7
15
 
8
16
  const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
9
17
  // This is called, once per vat, when upgradeSwingset migrates from
@@ -92,11 +100,13 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
92
100
  * `hostStorage.commit()` afterwards.
93
101
  *
94
102
  * @param {SwingStoreKernelStorage} kernelStorage
95
- * @returns {boolean} true if any changes were made
103
+ * @returns {{ modified: boolean }}
96
104
  */
97
105
  export const upgradeSwingset = kernelStorage => {
98
106
  const { kvStore } = kernelStorage;
99
107
  let modified = false;
108
+ /** @type {RunQueueEvent[]} */
109
+ const upgradeEvents = [];
100
110
  let vstring = kvStore.get('version');
101
111
  if (vstring === undefined) {
102
112
  vstring = '0';
@@ -204,9 +214,129 @@ export const upgradeSwingset = kernelStorage => {
204
214
  version = 2;
205
215
  }
206
216
 
217
+ if (version < 3) {
218
+ // v3 means that we've completed remediation for bug #9039
219
+ console.log(`Starting remediation of bug #9039`);
220
+
221
+ // find all terminated vats
222
+ const terminated = new Set(JSON.parse(getRequired('vats.terminated')));
223
+
224
+ // find all live vats
225
+ const allVatIDs = [];
226
+ for (const [_name, vatID] of getAllStaticVats(kvStore)) {
227
+ if (!terminated.has(vatID)) {
228
+ allVatIDs.push(vatID);
229
+ }
230
+ }
231
+ for (const vatID of getAllDynamicVats(getRequired)) {
232
+ if (!terminated.has(vatID)) {
233
+ allVatIDs.push(vatID);
234
+ }
235
+ }
236
+
237
+ // find all pending notifies
238
+ const notifies = new Map(); // .get(kpid) = [vatIDs..];
239
+ for (const name of ['runQueue', 'acceptanceQueue']) {
240
+ for (const rq of readQueue(name, getRequired)) {
241
+ if (rq.type === 'notify') {
242
+ const { vatID, kpid } = rq;
243
+ assert(vatID);
244
+ assert(kpid);
245
+ let vats = notifies.get(kpid);
246
+ if (!vats) {
247
+ vats = [];
248
+ notifies.set(kpid, vats);
249
+ }
250
+ vats.push(vatID);
251
+ }
252
+ }
253
+ }
254
+ console.log(` - pending notifies:`, notifies);
255
+
256
+ // cache of known-settled kpids: will grow to num(kpids)
257
+ const KPIDStatus = new Map();
258
+ const isSettled = kpid => {
259
+ if (KPIDStatus.has(kpid)) {
260
+ return KPIDStatus.get(kpid);
261
+ }
262
+ const state = kvStore.get(`${kpid}.state`);
263
+ // missing state means the kpid is deleted somehow, shouldn't happen
264
+ state || Fail`${kpid}.state is missing`;
265
+ const settled = state !== 'unresolved';
266
+ KPIDStatus.set(kpid, settled);
267
+ return settled;
268
+ };
269
+
270
+ // walk vNN.c.kpNN for all vats, for each one check the
271
+ // kpNN.state, for the settled ones check for a pending notify,
272
+ // record the ones without a pending notify
273
+
274
+ const buggyKPIDs = []; // [kpid, vatID]
275
+ for (const vatID of allVatIDs) {
276
+ const prefix = `${vatID}.c.`;
277
+ const len = prefix.length;
278
+ const ckpPrefix = `${vatID}.c.kp`;
279
+ for (const key of enumeratePrefixedKeys(kvStore, ckpPrefix)) {
280
+ const kpid = key.slice(len);
281
+ if (isSettled(kpid)) {
282
+ const n = notifies.get(kpid);
283
+ if (!n || !n.includes(vatID)) {
284
+ // there is no pending notify
285
+ buggyKPIDs.push([kpid, vatID]);
286
+ }
287
+ }
288
+ }
289
+ }
290
+ console.log(` - found ${buggyKPIDs.length} buggy kpids, enqueueing fixes`);
291
+
292
+ // now fix it. The bug means we failed to delete the c-list entry
293
+ // and decref it back when the promise was rejected. That decref
294
+ // would have pushed the kpid onto maybeFreeKrefs, which would
295
+ // have triggered a refcount check at end-of-crank, which might
296
+ // have deleted the promise records (if nothing else was
297
+ // referencing the promise, like arguments in messages enqueued to
298
+ // unresolved promises, or something transient on the
299
+ // run-queue). Deleting those promise records might have decreffed
300
+ // krefs in the rejection data (although in general 9039 rejects
301
+ // those promises with non-slot-bearing DisconnectionObjects).
302
+ //
303
+ // To avoid duplicating a lot of kernel code inside this upgrade
304
+ // handler, we do the simplest possible thing: enqueue a notify to
305
+ // the upgraded vat for all these leftover promises. The new vat
306
+ // incarnation will ignore it (they don't recognize the vpid), but
307
+ // the dispatch.notify() delivery will clear the c-list and decref
308
+ // the kpid, and will trigger all the usual GC work. Note that
309
+ // these notifies will be delivered before any activity the host
310
+ // app might trigger for e.g. a chain upgrade, but they should not
311
+ // cause userspace-visible behavior (non-slot-bearing rejection
312
+ // data means no other vat will even get a gc-action delivery:
313
+ // only the upgraded vat will see anything, and those deliveries
314
+ // won't make it past liveslots).
315
+
316
+ let count = 0;
317
+ for (const [kpid, vatID] of buggyKPIDs) {
318
+ // account for the reference to this kpid in upgradeEvents
319
+ incrementReferenceCount(getRequired, kvStore, kpid, `enq|notify`);
320
+ upgradeEvents.push({ type: 'notify', vatID, kpid });
321
+ count += 1;
322
+ }
323
+
324
+ console.log(` - #9039 remediation complete, ${count} notifies to inject`);
325
+ modified = true;
326
+ version = 3;
327
+ }
328
+
329
+ if (upgradeEvents.length) {
330
+ assert(modified);
331
+ // stash until host calls controller.injectQueuedUpgradeEvents()
332
+ const oldEvents = JSON.parse(kvStore.get('upgradeEvents') || '[]');
333
+ const events = [...oldEvents, ...upgradeEvents];
334
+ kvStore.set('upgradeEvents', JSON.stringify(events));
335
+ }
336
+
207
337
  if (modified) {
208
338
  kvStore.set('version', `${version}`);
209
339
  }
210
- return modified;
340
+ return harden({ modified });
211
341
  };
212
342
  harden(upgradeSwingset);
@@ -71,6 +71,23 @@ import { Nat } from '@endo/nat';
71
71
  // replace it with one that tracks which parts of the state have been
72
72
  // modified, to build more efficient Merkle proofs.
73
73
 
74
+ /**
75
+ * @typedef {object} Mailbox
76
+ * @property {bigint} ack
77
+ * @property {Map<bigint, unknown>} outbox
78
+ */
79
+
80
+ /**
81
+ * @typedef {object} MailboxExport
82
+ * @property {number} ack
83
+ * @property {Array<[number, unknown]>} outbox
84
+ */
85
+
86
+ /**
87
+ * @param {MailboxExport} data
88
+ * @param {Partial<Mailbox>} [inout]
89
+ * @returns {Mailbox}
90
+ */
74
91
  export function importMailbox(data, inout = {}) {
75
92
  const outbox = new Map();
76
93
  for (const m of data.outbox) {
@@ -78,10 +95,15 @@ export function importMailbox(data, inout = {}) {
78
95
  }
79
96
  inout.ack = Nat(data.ack);
80
97
  inout.outbox = outbox;
81
- return inout;
98
+ return /** @type {Mailbox} */ (inout);
82
99
  }
83
100
 
101
+ /**
102
+ * @param {Mailbox} inout
103
+ * @returns {MailboxExport}
104
+ */
84
105
  export function exportMailbox(inout) {
106
+ /** @type {MailboxExport['outbox']} */
85
107
  const messages = [];
86
108
  for (const [msgnum, body] of inout.outbox) {
87
109
  messages.push([Number(msgnum), body]);
@@ -93,64 +115,75 @@ export function exportMailbox(inout) {
93
115
  };
94
116
  }
95
117
 
96
- export function buildMailboxStateMap(state = harden(new Map())) {
97
- function getOrCreatePeer(peer) {
98
- if (!state.has(peer)) {
99
- const inout = {
100
- outbox: harden(new Map()),
101
- ack: 0n,
102
- };
103
- state.set(peer, inout);
104
- }
105
- return state.get(peer);
118
+ /**
119
+ * @param {Map<string, Mailbox>} state
120
+ */
121
+ export function exportMailboxData(state) {
122
+ /** @type {Record<string, {inboundAck: MailboxExport['ack'], outbox: MailboxExport['outbox']}>} */
123
+ const data = {};
124
+ for (const [peer, inout] of state.entries()) {
125
+ const exported = exportMailbox(inout);
126
+ data[peer] = {
127
+ inboundAck: exported.ack,
128
+ outbox: exported.outbox,
129
+ };
106
130
  }
131
+ return harden(data);
132
+ }
107
133
 
108
- function add(peer, msgnum, body) {
109
- getOrCreatePeer(`${peer}`).outbox.set(Nat(msgnum), `${body}`);
134
+ function getOrCreatePeer(state, peer) {
135
+ if (!state.has(peer)) {
136
+ const inout = {
137
+ outbox: harden(new Map()),
138
+ ack: 0n,
139
+ };
140
+ state.set(peer, inout);
110
141
  }
142
+ return state.get(peer);
143
+ }
111
144
 
112
- function remove(peer, msgnum) {
113
- const messages = getOrCreatePeer(`${peer}`).outbox;
114
- messages.delete(Nat(msgnum));
145
+ /**
146
+ * @param {ReturnType<exportMailboxData>} data
147
+ * @returns {Map<string, Mailbox>}
148
+ */
149
+ export function makeEphemeralMailboxStorage(data) {
150
+ const state = harden(new Map());
151
+ for (const peer of Object.getOwnPropertyNames(data)) {
152
+ const inout = getOrCreatePeer(state, peer);
153
+ const d = data[peer];
154
+ importMailbox(
155
+ {
156
+ ack: d.inboundAck,
157
+ outbox: d.outbox,
158
+ },
159
+ inout,
160
+ );
115
161
  }
162
+ return state;
163
+ }
116
164
 
117
- function setAcknum(peer, msgnum) {
118
- getOrCreatePeer(`${peer}`).ack = Nat(msgnum);
165
+ /**
166
+ * @template [T=unknown]
167
+ * @param {Pick<Map<string, T>, 'has' | 'get'> & {set: (key: string, value: T) => void}} [state]
168
+ */
169
+ export function buildMailboxStateMap(state = harden(new Map())) {
170
+ function add(peer, msgnum, body) {
171
+ getOrCreatePeer(state, `${peer}`).outbox.set(Nat(msgnum), `${body}`);
119
172
  }
120
173
 
121
- function exportToData() {
122
- const data = {};
123
- for (const [peer, inout] of state.entries()) {
124
- const exported = exportMailbox(inout);
125
- data[peer] = {
126
- inboundAck: exported.ack,
127
- outbox: exported.outbox,
128
- };
129
- }
130
- return harden(data);
174
+ function remove(peer, msgnum) {
175
+ const messages = getOrCreatePeer(state, `${peer}`).outbox;
176
+ messages.delete(Nat(msgnum));
131
177
  }
132
178
 
133
- function populateFromData(data) {
134
- !state.size || Fail`cannot populateFromData: outbox is not empty`;
135
- for (const peer of Object.getOwnPropertyNames(data)) {
136
- const inout = getOrCreatePeer(peer);
137
- const d = data[peer];
138
- importMailbox(
139
- {
140
- ack: d.inboundAck,
141
- outbox: d.outbox,
142
- },
143
- inout,
144
- );
145
- }
179
+ function setAcknum(peer, msgnum) {
180
+ getOrCreatePeer(state, `${peer}`).ack = Nat(msgnum);
146
181
  }
147
182
 
148
183
  return harden({
149
184
  add,
150
185
  remove,
151
186
  setAcknum,
152
- exportToData,
153
- populateFromData,
154
187
  });
155
188
  }
156
189
 
package/src/index.js CHANGED
@@ -13,6 +13,8 @@ export { upgradeSwingset } from './controller/upgradeSwingset.js';
13
13
  export {
14
14
  buildMailboxStateMap,
15
15
  buildMailbox,
16
+ exportMailboxData,
17
+ makeEphemeralMailboxStorage,
16
18
  } from './devices/mailbox/mailbox.js';
17
19
  export { buildTimer } from './devices/timer/timer.js';
18
20
  export { buildBridge } from './devices/bridge/bridge.js';
@@ -69,7 +69,7 @@ function makeDRTranslator(deviceID, kernelKeeper) {
69
69
  *
70
70
  * @param {string} deviceID
71
71
  * @param {string} deviceName
72
- * @param {*} kernelKeeper
72
+ * @param {KernelKeeper} kernelKeeper
73
73
  * @returns {(dsc: DeviceSyscallObject) => KernelSyscallObject}
74
74
  */
75
75
  export function makeDSTranslator(deviceID, deviceName, kernelKeeper) {
@@ -45,7 +45,7 @@ function parseAction(s) {
45
45
  }
46
46
 
47
47
  /**
48
- * @param {*} kernelKeeper
48
+ * @param {KernelKeeper} kernelKeeper
49
49
  * @returns {import('../types-internal.js').RunQueueEvent | undefined}
50
50
  */
51
51
  export function processGCActionSet(kernelKeeper) {
@@ -86,10 +86,9 @@ export function processGCActionSet(kernelKeeper) {
86
86
  const hasCList = vatKeeper.hasCListEntry(kref);
87
87
  const isReachable = hasCList ? vatKeeper.getReachableFlag(kref) : undefined;
88
88
  const exists = kernelKeeper.kernelObjectExists(kref);
89
- // @ts-expect-error xxx
90
89
  const { reachable, recognizable } = exists
91
90
  ? kernelKeeper.getObjectRefCount(kref)
92
- : {};
91
+ : { reachable: 0, recognizable: 0 };
93
92
 
94
93
  if (type === 'dropExport') {
95
94
  if (!exists) return false; // already, shouldn't happen
@@ -39,8 +39,10 @@ import { makeDeviceTranslators } from './deviceTranslator.js';
39
39
  import { notifyTermination } from './notifyTermination.js';
40
40
  import { makeVatAdminHooks } from './vat-admin-hooks.js';
41
41
 
42
- /** @import * as liveslots from '@agoric/swingset-liveslots' */
43
- /** @import {PolicyInputCleanupCounts} from '../types-external.js' */
42
+ /**
43
+ * @import {MeterConsumption, VatDeliveryObject, VatDeliveryResult, VatSyscallObject, VatSyscallResult} from '@agoric/swingset-liveslots';
44
+ * @import {PolicyInputCleanupCounts} from '../types-external.js';
45
+ */
44
46
 
45
47
  function abbreviateReplacer(_, arg) {
46
48
  if (typeof arg === 'bigint') {
@@ -56,7 +58,7 @@ function abbreviateReplacer(_, arg) {
56
58
  /**
57
59
  * Provide the kref of a vat's root object, as if it had been exported.
58
60
  *
59
- * @param {*} kernelKeeper Kernel keeper managing persistent kernel state.
61
+ * @param {KernelKeeper} kernelKeeper Kernel keeper managing persistent kernel state.
60
62
  * @param {string} vatID Vat ID of the vat whose root kref is sought.
61
63
  *
62
64
  * @returns {string} the kref of the root object of the given vat.
@@ -213,6 +215,10 @@ export default function buildKernel(
213
215
  return deviceID;
214
216
  }
215
217
 
218
+ function injectQueuedUpgradeEvents() {
219
+ kernelKeeper.injectQueuedUpgradeEvents();
220
+ }
221
+
216
222
  function addImport(forVatID, what) {
217
223
  if (!started) {
218
224
  throw Error('must do kernel.start() before addImport()');
@@ -285,15 +291,16 @@ export default function buildKernel(
285
291
  const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
286
292
  critical = vatKeeper.getOptions().critical;
287
293
 
288
- // Reject all promises decided by the vat, making sure to capture the list
289
- // of kpids before that data is deleted.
290
- const deadPromises = [...kernelKeeper.enumeratePromisesByDecider(vatID)];
291
- // remove vatID from the list of live vats, and mark for deletion
294
+ // remove vatID from the list of live vats, and mark for
295
+ // deletion (which will happen later, in vat-cleanup events)
292
296
  kernelKeeper.deleteVatID(vatID);
293
297
  kernelKeeper.markVatAsTerminated(vatID);
294
298
  deferred.push(kernelKeeper.removeVatFromSwingStoreExports(vatID));
295
- for (const kpid of deadPromises) {
296
- resolveToError(kpid, makeError('vat terminated'), vatID);
299
+
300
+ // Reject all promises decided by the vat
301
+ const errdata = makeError('vat terminated');
302
+ for (const [kpid, _p] of kernelKeeper.enumeratePromisesByDecider(vatID)) {
303
+ resolveToError(kpid, errdata, vatID);
297
304
  }
298
305
  }
299
306
  if (critical) {
@@ -380,7 +387,6 @@ export default function buildKernel(
380
387
 
381
388
  /**
382
389
  *
383
- * @typedef { import('@agoric/swingset-liveslots').MeterConsumption } MeterConsumption
384
390
  * @typedef { import('../types-internal.js').MeterID } MeterID
385
391
  * @typedef { import('../types-internal.js').Dirt } Dirt
386
392
  *
@@ -416,7 +422,7 @@ export default function buildKernel(
416
422
  *
417
423
  * @param {VatID} vatID
418
424
  * @param {KernelDeliveryObject} kd
419
- * @param {liveslots.VatDeliveryObject} vd
425
+ * @param {VatDeliveryObject} vd
420
426
  */
421
427
  async function deliverAndLogToVat(vatID, kd, vd) {
422
428
  vatRequestedTermination = undefined;
@@ -426,7 +432,7 @@ export default function buildKernel(
426
432
  const vs = kernelSlog.provideVatSlogger(vatID).vatSlog;
427
433
  await null;
428
434
  try {
429
- /** @type { liveslots.VatDeliveryResult } */
435
+ /** @type { VatDeliveryResult } */
430
436
  const deliveryResult = await vatWarehouse.deliverToVat(vatID, kd, vd, vs);
431
437
  insistVatDeliveryResult(deliveryResult);
432
438
  // const [ ok, problem, usage ] = deliveryResult;
@@ -596,7 +602,9 @@ export default function buildKernel(
596
602
  const p = kernelKeeper.getKernelPromise(kpid);
597
603
  kernelKeeper.incStat('dispatchNotify');
598
604
  const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
599
- p.state !== 'unresolved' || Fail`spurious notification ${kpid}`;
605
+ if (p.state === 'unresolved') {
606
+ throw Fail`spurious notification ${kpid}`;
607
+ }
600
608
  /** @type { KernelDeliveryOneNotify[] } */
601
609
  const resolutions = [];
602
610
  if (!vatKeeper.hasCListEntry(kpid)) {
@@ -611,7 +619,9 @@ export default function buildKernel(
611
619
  return NO_DELIVERY_CRANK_RESULTS;
612
620
  }
613
621
  for (const toResolve of targets) {
614
- const { state, data } = kernelKeeper.getKernelPromise(toResolve);
622
+ const tp = kernelKeeper.getKernelPromise(toResolve);
623
+ assert(tp.state !== 'unresolved');
624
+ const { state, data } = tp;
615
625
  resolutions.push([toResolve, { state, data }]);
616
626
  }
617
627
  /** @type { KernelDeliveryNotify } */
@@ -700,6 +710,7 @@ export default function buildKernel(
700
710
  total:
701
711
  work.exports +
702
712
  work.imports +
713
+ work.promises +
703
714
  work.kv +
704
715
  work.snapshots +
705
716
  work.transcripts,
@@ -741,6 +752,8 @@ export default function buildKernel(
741
752
  kdebug(`vat ${vatID} terminated before startVat delivered`);
742
753
  return NO_DELIVERY_CRANK_RESULTS;
743
754
  }
755
+ const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
756
+ vatKeeper.setVatParameters(vatParameters);
744
757
  const { meterID } = vatInfo;
745
758
  /** @type { KernelDeliveryStartVat } */
746
759
  const kd = harden(['startVat', vatParameters]);
@@ -994,9 +1007,30 @@ export default function buildKernel(
994
1007
  return results;
995
1008
  }
996
1009
 
997
- // reject all promises for which the vat was decider
998
- for (const kpid of kernelKeeper.enumeratePromisesByDecider(vatID)) {
999
- resolveToError(kpid, disconnectionCapData, vatID);
1010
+ // We are homesick for a future in which most promises are
1011
+ // durable, and vats do not need to subscribe to their own
1012
+ // promises to make promise-watchers work. In that world, vats
1013
+ // somehow mark the non-durable promises, which we must
1014
+ // reject/disconnect on their behalf during upgrade.
1015
+ //
1016
+ // To handle the present reality, without durable promises, we
1017
+ // pretend that all promises are so marked.
1018
+
1019
+ // reject all ephemeral promises for which the vat was decider
1020
+ for (const [kpid, p] of kernelKeeper.enumeratePromisesByDecider(vatID)) {
1021
+ const isEphemeral = true; // future vats will mark these explicitly
1022
+ const selfSubscribed =
1023
+ p.state === 'unresolved' && p.subscribers.includes(vatID);
1024
+ if (isEphemeral) {
1025
+ resolveToError(kpid, disconnectionCapData, vatID);
1026
+ if (!selfSubscribed) {
1027
+ // If the vat was subscribed to its own promise, the
1028
+ // resolveToError will enqueue a dispatch.notify, whose delivery
1029
+ // will delete the c-list entry. If it is *not* subscribed, we
1030
+ // should delete the c-list entry now, because nobody else will.
1031
+ vatKeeper.deleteCListEntriesForKernelSlots([kpid]);
1032
+ }
1033
+ }
1000
1034
  }
1001
1035
 
1002
1036
  // simulate an abandonExports syscall from the vat,
@@ -1020,6 +1054,7 @@ export default function buildKernel(
1020
1054
  });
1021
1055
  const vatOptions = harden({ ...origOptions, workerOptions });
1022
1056
  vatKeeper.setSourceAndOptions(source, vatOptions);
1057
+ vatKeeper.setVatParameters(vatParameters);
1023
1058
  // TODO: decref the bundleID once setSourceAndOptions increfs it
1024
1059
 
1025
1060
  // pause, take a deep breath, appreciate this moment of silence
@@ -1185,6 +1220,7 @@ export default function buildKernel(
1185
1220
  }
1186
1221
  }
1187
1222
  default:
1223
+ // @ts-expect-error
1188
1224
  throw Fail`unknown promise resolution '${kp.state}'`;
1189
1225
  }
1190
1226
  }
@@ -1542,8 +1578,8 @@ export default function buildKernel(
1542
1578
  // not
1543
1579
  /**
1544
1580
  *
1545
- * @param {liveslots.VatSyscallObject} vatSyscallObject
1546
- * @returns {liveslots.VatSyscallResult}
1581
+ * @param {VatSyscallObject} vatSyscallObject
1582
+ * @returns {VatSyscallResult}
1547
1583
  */
1548
1584
  function vatSyscallHandler(vatSyscallObject) {
1549
1585
  if (!vatWarehouse.lookup(vatID)) {
@@ -1558,7 +1594,7 @@ export default function buildKernel(
1558
1594
  let ksc;
1559
1595
  /** @type { KernelSyscallResult } */
1560
1596
  let kres = harden(['error', 'incomplete']);
1561
- /** @type { liveslots.VatSyscallResult } */
1597
+ /** @type { VatSyscallResult } */
1562
1598
  let vres = harden(['error', 'incomplete']);
1563
1599
 
1564
1600
  try {
@@ -1819,6 +1855,7 @@ export default function buildKernel(
1819
1855
  {
1820
1856
  exports: M.number(),
1821
1857
  imports: M.number(),
1858
+ promises: M.number(),
1822
1859
  kv: M.number(),
1823
1860
  snapshots: M.number(),
1824
1861
  transcripts: M.number(),
@@ -2080,6 +2117,7 @@ export default function buildKernel(
2080
2117
  return p.data;
2081
2118
  }
2082
2119
  default: {
2120
+ // @ts-expect-error
2083
2121
  throw Fail`invalid state for ${kpid}: ${p.state}`;
2084
2122
  }
2085
2123
  }
@@ -2178,6 +2216,7 @@ export default function buildKernel(
2178
2216
  pinObject,
2179
2217
  vatNameToID,
2180
2218
  deviceNameToID,
2219
+ injectQueuedUpgradeEvents,
2181
2220
  queueToKref,
2182
2221
  kpRegisterInterest,
2183
2222
  kpStatus,
@@ -45,14 +45,16 @@ const enableKernelGC = true;
45
45
  * @typedef { import('../../types-external.js').VatKeeper } VatKeeper
46
46
  * @typedef { import('../../types-internal.js').InternalKernelOptions } InternalKernelOptions
47
47
  * @typedef { import('../../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
48
+ * @import {PromiseRecord} from '../../types-internal.js';
48
49
  * @import {CleanupBudget, CleanupWork, PolicyOutputCleanupBudget} from '../../types-external.js';
49
50
  * @import {RunQueueEventCleanupTerminatedVat} from '../../types-internal.js';
51
+ * @import {SwingStoreKernelStorage} from '@agoric/swing-store';
50
52
  */
51
53
 
52
54
  export { DEFAULT_REAP_DIRT_THRESHOLD_KEY };
53
55
 
54
56
  // most recent DB schema version
55
- export const CURRENT_SCHEMA_VERSION = 2;
57
+ export const CURRENT_SCHEMA_VERSION = 3;
56
58
 
57
59
  // Kernel state lives in a key-value store supporting key retrieval by
58
60
  // lexicographic range. All keys and values are strings.
@@ -71,9 +73,9 @@ export const CURRENT_SCHEMA_VERSION = 2;
71
73
  // only modified by a call to upgradeSwingset(). See below for
72
74
  // deltas/upgrades from one version to the next.
73
75
  //
74
- // The current ("v2") schema keys/values are:
76
+ // The current ("v3") schema keys/values are:
75
77
  //
76
- // version = '2'
78
+ // version = '3'
77
79
  // vat.names = JSON([names..])
78
80
  // vat.dynamicIDs = JSON([vatIDs..])
79
81
  // vat.name.$NAME = $vatID = v$NN
@@ -117,6 +119,8 @@ export const CURRENT_SCHEMA_VERSION = 2;
117
119
  // old (v0): v$NN.reapCountdown = $NN or 'never'
118
120
  // v$NN.reapDirt = JSON({ deliveries, gcKrefs, computrons }) // missing keys treated as zero
119
121
  // (leave room for v$NN.snapshotDirt and options.snapshotDirtThreshold for #6786)
122
+ // v$NN.vatParameters = JSON(capdata) // missing for vats created/upgraded before #8947
123
+ //
120
124
  // exclude from consensus
121
125
  // local.*
122
126
 
@@ -141,6 +145,7 @@ export const CURRENT_SCHEMA_VERSION = 2;
141
145
  // gcActions = JSON(gcActions)
142
146
  // reapQueue = JSON([vatIDs...])
143
147
  // pinnedObjects = ko$NN[,ko$NN..]
148
+ // upgradeEvents = JSON([events..])
144
149
 
145
150
  // ko.nextID = $NN
146
151
  // ko$NN.owner = $vatID
@@ -177,7 +182,13 @@ export const CURRENT_SCHEMA_VERSION = 2;
177
182
  // v2:
178
183
  // * change `version` to `'2'`
179
184
  // * add `vats.terminated` with `[]` as initial value
185
+ // v3:
186
+ // * change `version` to `'3'`
187
+ // * perform remediation for bug #9039
188
+ // (after v3, does not get its own version)
189
+ // * `upgradeEvents` recognized, but omitted if empty
180
190
 
191
+ /** @type {(s: string) => string[]} s */
181
192
  export function commaSplit(s) {
182
193
  if (s === '') {
183
194
  return [];
@@ -211,6 +222,77 @@ export const getAllDynamicVats = getRequired => {
211
222
  return JSON.parse(getRequired('vat.dynamicIDs'));
212
223
  };
213
224
 
225
+ const getObjectReferenceCount = (kvStore, kref) => {
226
+ const data = kvStore.get(`${kref}.refCount`);
227
+ if (!data) {
228
+ return { reachable: 0, recognizable: 0 };
229
+ }
230
+ const [reachable, recognizable] = commaSplit(data).map(Number);
231
+ reachable <= recognizable ||
232
+ Fail`refmismatch(get) ${kref} ${reachable},${recognizable}`;
233
+ return { reachable, recognizable };
234
+ };
235
+
236
+ const setObjectReferenceCount = (kvStore, kref, counts) => {
237
+ const { reachable, recognizable } = counts;
238
+ assert.typeof(reachable, 'number');
239
+ assert.typeof(recognizable, 'number');
240
+ (reachable >= 0 && recognizable >= 0) ||
241
+ Fail`${kref} underflow ${reachable},${recognizable}`;
242
+ reachable <= recognizable ||
243
+ Fail`refmismatch(set) ${kref} ${reachable},${recognizable}`;
244
+ kvStore.set(`${kref}.refCount`, `${reachable},${recognizable}`);
245
+ };
246
+
247
+ /**
248
+ * Increment the reference count associated with some kernel object.
249
+ *
250
+ * We track references to promises and objects, but not devices. Promises
251
+ * have only a "reachable" count, whereas objects track both "reachable"
252
+ * and "recognizable" counts.
253
+ *
254
+ * @param { (key: string) => string} getRequired
255
+ * @param { import('@agoric/swing-store').KVStore } kvStore
256
+ * @param {string} kref The kernel slot whose refcount is to be incremented.
257
+ * @param {string?} tag Debugging note with rough source of the reference.
258
+ * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options
259
+ * 'isExport' means the reference comes from a clist export, which counts
260
+ * for promises but not objects. 'onlyRecognizable' means the reference
261
+ * provides only recognition, not reachability
262
+ */
263
+ export const incrementReferenceCount = (
264
+ getRequired,
265
+ kvStore,
266
+ kref,
267
+ tag,
268
+ options = {},
269
+ ) => {
270
+ const { isExport = false, onlyRecognizable = false } = options;
271
+ kref || Fail`incrementRefCount called with empty kref, tag=${tag}`;
272
+ const { type } = parseKernelSlot(kref);
273
+ if (type === 'promise') {
274
+ const refCount = Number(getRequired(`${kref}.refCount`)) + 1;
275
+ // kdebug(`++ ${kref} ${tag} ${refCount}`);
276
+ kvStore.set(`${kref}.refCount`, `${refCount}`);
277
+ }
278
+ if (type === 'object' && !isExport) {
279
+ let { reachable, recognizable } = getObjectReferenceCount(kvStore, kref);
280
+ if (!onlyRecognizable) {
281
+ reachable += 1;
282
+ }
283
+ recognizable += 1;
284
+ // kdebug(`++ ${kref} ${tag} ${reachable},${recognizable}`);
285
+ setObjectReferenceCount(kvStore, kref, { reachable, recognizable });
286
+ }
287
+ };
288
+
289
+ export function* readQueue(queue, getRequired) {
290
+ const [head, tail] = JSON.parse(getRequired(`${queue}`));
291
+ for (let i = head; i < tail; i += 1) {
292
+ yield JSON.parse(getRequired(`${queue}.${i}`));
293
+ }
294
+ }
295
+
214
296
  // we use different starting index values for the various vNN/koNN/kdNN/kpNN
215
297
  // slots, to reduce confusing overlap when looking at debug messages (e.g.
216
298
  // seeing both kp1 and ko1, which are completely unrelated despite having the
@@ -388,14 +470,7 @@ export default function makeKernelKeeper(
388
470
  return tail - head;
389
471
  }
390
472
 
391
- function dumpQueue(queue) {
392
- const [head, tail] = JSON.parse(getRequired(`${queue}`));
393
- const result = [];
394
- for (let i = head; i < tail; i += 1) {
395
- result.push(JSON.parse(getRequired(`${queue}.${i}`)));
396
- }
397
- return result;
398
- }
473
+ const dumpQueue = queue => [...readQueue(queue, getRequired)];
399
474
 
400
475
  /**
401
476
  * @param {InternalKernelOptions} kernelOptions
@@ -619,26 +694,9 @@ export default function makeKernelKeeper(
619
694
  return parseReachableAndVatSlot(kvStore.get(kernelKey));
620
695
  }
621
696
 
622
- function getObjectRefCount(kernelSlot) {
623
- const data = kvStore.get(`${kernelSlot}.refCount`);
624
- if (!data) {
625
- return { reachable: 0, recognizable: 0 };
626
- }
627
- const [reachable, recognizable] = commaSplit(data).map(Number);
628
- reachable <= recognizable ||
629
- Fail`refmismatch(get) ${kernelSlot} ${reachable},${recognizable}`;
630
- return { reachable, recognizable };
631
- }
632
-
633
- function setObjectRefCount(kernelSlot, { reachable, recognizable }) {
634
- assert.typeof(reachable, 'number');
635
- assert.typeof(recognizable, 'number');
636
- (reachable >= 0 && recognizable >= 0) ||
637
- Fail`${kernelSlot} underflow ${reachable},${recognizable}`;
638
- reachable <= recognizable ||
639
- Fail`refmismatch(set) ${kernelSlot} ${reachable},${recognizable}`;
640
- kvStore.set(`${kernelSlot}.refCount`, `${reachable},${recognizable}`);
641
- }
697
+ const getObjectRefCount = kref => getObjectReferenceCount(kvStore, kref);
698
+ const setObjectRefCount = (kref, counts) =>
699
+ setObjectReferenceCount(kvStore, kref, counts);
642
700
 
643
701
  /**
644
702
  * Iterate over non-durable objects exported by a vat.
@@ -790,43 +848,41 @@ export default function makeKernelKeeper(
790
848
  return kpid;
791
849
  }
792
850
 
851
+ /**
852
+ * @param {string} kernelSlot
853
+ * @returns {PromiseRecord}
854
+ */
793
855
  function getKernelPromise(kernelSlot) {
794
856
  insistKernelType('promise', kernelSlot);
795
- const p = { state: getRequired(`${kernelSlot}.state`) };
796
- switch (p.state) {
797
- case undefined: {
798
- throw Fail`unknown kernelPromise '${kernelSlot}'`;
799
- }
857
+ const state = getRequired(`${kernelSlot}.state`);
858
+ const refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
859
+ switch (state) {
800
860
  case 'unresolved': {
801
- p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
802
- p.decider = kvStore.get(`${kernelSlot}.decider`);
803
- if (p.decider === '') {
804
- p.decider = undefined;
805
- }
806
- p.policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore';
807
- p.subscribers = commaSplit(kvStore.get(`${kernelSlot}.subscribers`));
808
- p.queue = Array.from(
861
+ const decider = kvStore.get(`${kernelSlot}.decider`) || undefined;
862
+ const policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore';
863
+ const subscribers = commaSplit(
864
+ kvStore.get(`${kernelSlot}.subscribers`) || '',
865
+ );
866
+ const queue = Array.from(
809
867
  getPrefixedValues(kvStore, `${kernelSlot}.queue.`),
810
868
  ).map(s => JSON.parse(s));
811
- break;
869
+ return harden({ state, refCount, decider, policy, subscribers, queue });
812
870
  }
813
871
  case 'fulfilled':
814
872
  case 'rejected': {
815
- p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
816
- p.data = {
817
- body: kvStore.get(`${kernelSlot}.data.body`),
818
- slots: commaSplit(kvStore.get(`${kernelSlot}.data.slots`)),
873
+ const data = {
874
+ body: getRequired(`${kernelSlot}.data.body`),
875
+ slots: commaSplit(getRequired(`${kernelSlot}.data.slots`)),
819
876
  };
820
- for (const s of p.data.slots) {
877
+ for (const s of data.slots) {
821
878
  parseKernelSlot(s);
822
879
  }
823
- break;
880
+ return harden({ state, refCount, data });
824
881
  }
825
882
  default: {
826
- throw Fail`unknown state for ${kernelSlot}: ${p.state}`;
883
+ throw Fail`unknown state for ${kernelSlot}: ${state}`;
827
884
  }
828
885
  }
829
- return harden(p);
830
886
  }
831
887
 
832
888
  function getResolveablePromise(kpid, expectedDecider) {
@@ -835,7 +891,9 @@ export default function makeKernelKeeper(
835
891
  insistVatID(expectedDecider);
836
892
  }
837
893
  const p = getKernelPromise(kpid);
838
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
894
+ if (p.state !== 'unresolved') {
895
+ throw Fail`${kpid} was already resolved`;
896
+ }
839
897
  if (expectedDecider) {
840
898
  p.decider === expectedDecider ||
841
899
  Fail`${kpid} is decided by ${p.decider}, not ${expectedDecider}`;
@@ -894,6 +952,7 @@ export default function makeKernelKeeper(
894
952
  // up the resolution *now* and set the correct target early. Doing that
895
953
  // might make it easier to remove the Promise Table entry earlier.
896
954
  const p = getKernelPromise(kernelSlot);
955
+ assert.equal(p.state, 'unresolved');
897
956
  for (const msg of p.queue) {
898
957
  const entry = harden({ type: 'send', target: kernelSlot, msg });
899
958
  enqueue('acceptanceQueue', entry);
@@ -964,6 +1023,7 @@ export default function makeKernelKeeper(
964
1023
  const work = {
965
1024
  exports: 0,
966
1025
  imports: 0,
1026
+ promises: 0,
967
1027
  kv: 0,
968
1028
  snapshots: 0,
969
1029
  transcripts: 0,
@@ -988,6 +1048,7 @@ export default function makeKernelKeeper(
988
1048
  const clistPrefix = `${vatID}.c.`;
989
1049
  const exportPrefix = `${clistPrefix}o+`;
990
1050
  const importPrefix = `${clistPrefix}o-`;
1051
+ const promisePrefix = `${clistPrefix}p`;
991
1052
 
992
1053
  // Note: ASCII order is "+,-./", and we rely upon this to split the
993
1054
  // keyspace into the various o+NN/o-NN/etc spaces. If we were using a
@@ -1040,8 +1101,22 @@ export default function makeKernelKeeper(
1040
1101
  }
1041
1102
  }
1042
1103
 
1043
- // the caller used enumeratePromisesByDecider() before calling us,
1044
- // so they already know the orphaned promises to reject
1104
+ // The caller used enumeratePromisesByDecider() before calling us,
1105
+ // so they have already rejected the orphan promises, but those
1106
+ // kpids are still present in the dead vat's c-list. Clean those
1107
+ // up now.
1108
+ remaining = budget.promises ?? budget.default;
1109
+ for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1110
+ const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1111
+ const vref = stripPrefix(clistPrefix, k);
1112
+ vatKeeper.deleteCListEntry(kref, vref);
1113
+ // that will also delete both db keys
1114
+ work.promises += 1;
1115
+ remaining -= 1;
1116
+ if (remaining <= 0) {
1117
+ return { done: false, work };
1118
+ }
1119
+ }
1045
1120
 
1046
1121
  // now loop back through everything and delete it all
1047
1122
  remaining = budget.kv ?? budget.default;
@@ -1139,18 +1214,22 @@ export default function makeKernelKeeper(
1139
1214
  function setDecider(kpid, decider) {
1140
1215
  insistVatID(decider);
1141
1216
  const p = getKernelPromise(kpid);
1142
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
1143
- !p.decider || Fail`${kpid} has decider ${p.decider}, not empty`;
1217
+ assert.equal(p.state, 'unresolved', `${kpid} was already resolved`);
1218
+ assert(!p.decider, `${kpid} has decider ${p.decider}, not empty`);
1144
1219
  kvStore.set(`${kpid}.decider`, decider);
1145
1220
  }
1146
1221
 
1147
1222
  function clearDecider(kpid) {
1148
1223
  const p = getKernelPromise(kpid);
1149
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
1150
- p.decider || Fail`${kpid} does not have a decider`;
1224
+ assert.equal(p.state, 'unresolved', `${kpid} was already resolved`);
1225
+ assert(p.decider, `${kpid} does not have a decider`);
1151
1226
  kvStore.set(`${kpid}.decider`, '');
1152
1227
  }
1153
1228
 
1229
+ /**
1230
+ * @param {string} vatID
1231
+ * @returns {IterableIterator<[kpid: string, p: PromiseRecord]>}
1232
+ */
1154
1233
  function* enumeratePromisesByDecider(vatID) {
1155
1234
  insistVatID(vatID);
1156
1235
  const promisePrefix = `${vatID}.c.p`;
@@ -1164,10 +1243,10 @@ export default function makeKernelKeeper(
1164
1243
  // whether the vat is the decider or not. If it is, we add the promise
1165
1244
  // to the list of promises that must be rejected because the dead vat
1166
1245
  // will never be able to act upon them.
1167
- const kpid = kvStore.get(k);
1246
+ const kpid = getRequired(k);
1168
1247
  const p = getKernelPromise(kpid);
1169
1248
  if (p.state === 'unresolved' && p.decider === vatID) {
1170
- yield kpid;
1249
+ yield [kpid, p];
1171
1250
  }
1172
1251
  }
1173
1252
  }
@@ -1177,6 +1256,7 @@ export default function makeKernelKeeper(
1177
1256
  insistKernelType('promise', kernelSlot);
1178
1257
  insistVatID(vatID);
1179
1258
  const p = getKernelPromise(kernelSlot);
1259
+ assert.equal(p.state, 'unresolved');
1180
1260
  const s = new Set(p.subscribers);
1181
1261
  s.add(vatID);
1182
1262
  const v = Array.from(s).sort().join(',');
@@ -1207,6 +1287,27 @@ export default function makeKernelKeeper(
1207
1287
  return dequeue('acceptanceQueue');
1208
1288
  }
1209
1289
 
1290
+ function injectQueuedUpgradeEvents() {
1291
+ // refcounts: Any krefs in `upgradeEvents` must have a refcount to
1292
+ // represent the list's hold on those objects. When
1293
+ // upgradeSwingset() creates these events, it must also
1294
+ // incref(kref), otherwise we run the risk of dropping the kref by
1295
+ // the time injectQueuedUpgradeEvents() is called. We're nominally
1296
+ // removing each event from upgradeEvents (decref), then pushing
1297
+ // it onto the run-queue (incref), but since those two cancel each
1298
+ // other out, we don't actually need to modify any reference
1299
+ // counts from within this function. Note that
1300
+ // addToAcceptanceQueue does not increment refcounts, just kernel
1301
+ // queue-length stats.
1302
+
1303
+ const events = JSON.parse(kvStore.get('upgradeEvents') || '[]');
1304
+ kvStore.delete('upgradeEvents');
1305
+ for (const e of events) {
1306
+ assert(e.type, `not an event`);
1307
+ addToAcceptanceQueue(e);
1308
+ }
1309
+ }
1310
+
1210
1311
  function allocateMeter(remaining, threshold) {
1211
1312
  if (remaining !== 'unlimited') {
1212
1313
  assert.typeof(remaining, 'bigint');
@@ -1414,40 +1515,8 @@ export default function makeKernelKeeper(
1414
1515
  maybeFreeKrefs.add(kref);
1415
1516
  }
1416
1517
 
1417
- /**
1418
- * Increment the reference count associated with some kernel object.
1419
- *
1420
- * We track references to promises and objects, but not devices. Promises
1421
- * have only a "reachable" count, whereas objects track both "reachable"
1422
- * and "recognizable" counts.
1423
- *
1424
- * @param {unknown} kernelSlot The kernel slot whose refcount is to be incremented.
1425
- * @param {string?} tag Debugging note with rough source of the reference.
1426
- * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options
1427
- * 'isExport' means the reference comes from a clist export, which counts
1428
- * for promises but not objects. 'onlyRecognizable' means the reference
1429
- * provides only recognition, not reachability
1430
- */
1431
- function incrementRefCount(kernelSlot, tag, options = {}) {
1432
- const { isExport = false, onlyRecognizable = false } = options;
1433
- kernelSlot ||
1434
- Fail`incrementRefCount called with empty kernelSlot, tag=${tag}`;
1435
- const { type } = parseKernelSlot(kernelSlot);
1436
- if (type === 'promise') {
1437
- const refCount = Nat(BigInt(getRequired(`${kernelSlot}.refCount`))) + 1n;
1438
- // kdebug(`++ ${kernelSlot} ${tag} ${refCount}`);
1439
- kvStore.set(`${kernelSlot}.refCount`, `${refCount}`);
1440
- }
1441
- if (type === 'object' && !isExport) {
1442
- let { reachable, recognizable } = getObjectRefCount(kernelSlot);
1443
- if (!onlyRecognizable) {
1444
- reachable += 1;
1445
- }
1446
- recognizable += 1;
1447
- // kdebug(`++ ${kernelSlot} ${tag} ${reachable},${recognizable}`);
1448
- setObjectRefCount(kernelSlot, { reachable, recognizable });
1449
- }
1450
- }
1518
+ const incrementRefCount = (kref, tag, options = {}) =>
1519
+ incrementReferenceCount(getRequired, kvStore, kref, tag, options);
1451
1520
 
1452
1521
  /**
1453
1522
  * Decrement the reference count associated with some kernel object.
@@ -1539,13 +1608,15 @@ export default function makeKernelKeeper(
1539
1608
  const kp = getKernelPromise(kpid);
1540
1609
  if (kp.refCount === 0) {
1541
1610
  let idx = 0;
1542
- // TODO (#9889) don't assume promise is settled
1543
- for (const slot of kp.data.slots) {
1544
- // Note: the following decrement can result in an addition to the
1545
- // maybeFreeKrefs set, which we are in the midst of iterating.
1546
- // TC39 went to a lot of trouble to ensure that this is kosher.
1547
- decrementRefCount(slot, `gc|${kpid}|s${idx}`);
1548
- idx += 1;
1611
+ if (kp.state === 'fulfilled' || kp.state === 'rejected') {
1612
+ // #9889 don't assume promise is settled
1613
+ for (const slot of kp.data.slots) {
1614
+ // Note: the following decrement can result in an addition to the
1615
+ // maybeFreeKrefs set, which we are in the midst of iterating.
1616
+ // TC39 went to a lot of trouble to ensure that this is kosher.
1617
+ decrementRefCount(slot, `gc|${kpid}|s${idx}`);
1618
+ idx += 1;
1619
+ }
1549
1620
  }
1550
1621
  deleteKernelPromise(kpid);
1551
1622
  }
@@ -1936,6 +2007,8 @@ export default function makeKernelKeeper(
1936
2007
  getAcceptanceQueueLength,
1937
2008
  getNextAcceptanceQueueMsg,
1938
2009
 
2010
+ injectQueuedUpgradeEvents,
2011
+
1939
2012
  allocateMeter,
1940
2013
  addMeterRemaining,
1941
2014
  setMeterThreshold,
@@ -1981,3 +2054,4 @@ export default function makeKernelKeeper(
1981
2054
  dump,
1982
2055
  });
1983
2056
  }
2057
+ /** @typedef {ReturnType<typeof makeKernelKeeper>} KernelKeeper */
@@ -7,6 +7,7 @@ import { isObject } from '@endo/marshal';
7
7
  import { parseKernelSlot } from '../parseKernelSlots.js';
8
8
  import { makeVatSlot, parseVatSlot } from '../../lib/parseVatSlots.js';
9
9
  import { insistVatID } from '../../lib/id.js';
10
+ import { insistCapData } from '../../lib/capdata.js';
10
11
  import { kdebug } from '../../lib/kdebug.js';
11
12
  import {
12
13
  parseReachableAndVatSlot,
@@ -173,6 +174,35 @@ export function makeVatKeeper(
173
174
  return harden(options);
174
175
  }
175
176
 
177
+ /**
178
+ * @param {SwingSetCapData} newVPCD
179
+ */
180
+ function setVatParameters(newVPCD) {
181
+ insistCapData(newVPCD);
182
+ const key = `${vatID}.vatParameters`;
183
+ // increment-before-decrement to minimize spurious rc=0 checks
184
+ for (const kref of newVPCD.slots) {
185
+ incrementRefCount(kref, `${vatID}.vatParameters`);
186
+ }
187
+ const old = kvStore.get(key) || '{"slots":[]}';
188
+ for (const kref of JSON.parse(old).slots) {
189
+ decrementRefCount(kref, `${vatID}.vatParameters`);
190
+ }
191
+ kvStore.set(key, JSON.stringify(newVPCD));
192
+ }
193
+
194
+ /**
195
+ * @returns {SwingSetCapData | undefined} vpcd
196
+ */
197
+ function getVatParameters() {
198
+ const key = `${vatID}.vatParameters`;
199
+ const old = kvStore.get(key);
200
+ if (old) {
201
+ return JSON.parse(old);
202
+ }
203
+ return undefined;
204
+ }
205
+
176
206
  // This is named "addDirt" because it should increment all dirt
177
207
  // counters (both for reap/BOYD and for heap snapshotting). We don't
178
208
  // have `heapSnapshotDirt` yet, but when we do, it should get
@@ -768,6 +798,8 @@ export function makeVatKeeper(
768
798
  setSourceAndOptions,
769
799
  getSourceAndOptions,
770
800
  getOptions,
801
+ setVatParameters,
802
+ getVatParameters,
771
803
  addDirt,
772
804
  getReapDirt,
773
805
  clearReapDirt,
@@ -50,6 +50,7 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
50
50
  parseVatSlot(targetSlot).allocatedByVat || Fail`deliver() to wrong vat`;
51
51
  } else if (type === 'promise') {
52
52
  const p = kernelKeeper.getKernelPromise(target);
53
+ assert(p.state === 'unresolved');
53
54
  p.decider === vatID || Fail`wrong decider`;
54
55
  }
55
56
  const inputSlots = msg.methargs.slots.map(slot =>
@@ -59,7 +60,9 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
59
60
  if (msg.result) {
60
61
  insistKernelType('promise', msg.result);
61
62
  const p = kernelKeeper.getKernelPromise(msg.result);
62
- p.state === 'unresolved' || Fail`result ${msg.result} already resolved`;
63
+ if (p.state !== 'unresolved') {
64
+ throw Fail`result ${msg.result} already resolved`;
65
+ }
63
66
  !p.decider || Fail`result ${msg.result} already has decider ${p.decider}`;
64
67
  resultSlot = vatKeeper.mapKernelSlotToVatSlot(msg.result);
65
68
  insistVatType('promise', resultSlot);
@@ -318,8 +321,9 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
318
321
  // In the case of non-pipelining vats these checks are redundant since
319
322
  // we're guaranteed to have a promise newly allocated by the vat.
320
323
  const p = kernelKeeper.getKernelPromise(result);
321
- p.state === 'unresolved' ||
322
- Fail`send() result ${result} is already resolved`;
324
+ if (p.state !== 'unresolved') {
325
+ throw Fail`send() result ${result} is already resolved`;
326
+ }
323
327
  p.decider === vatID ||
324
328
  Fail`send() result ${result} is decided by ${p.decider} not ${vatID}`;
325
329
  kernelKeeper.clearDecider(result);
@@ -124,7 +124,7 @@ export {};
124
124
  *
125
125
  * @typedef { { transcriptCount: number } } VatStats
126
126
  * @typedef { ReturnType<typeof import('./kernel/state/vatKeeper.js').makeVatKeeper> } VatKeeper
127
- * @typedef { ReturnType<typeof import('./kernel/state/kernelKeeper.js').default> } KernelKeeper
127
+ * @typedef { import('./kernel/state/kernelKeeper.js').KernelKeeper } KernelKeeper
128
128
  * @typedef { Awaited<ReturnType<typeof import('@agoric/xsnap').xsnap>> } XSnap
129
129
  * @typedef { (dr: VatDeliveryResult) => void } SlogFinishDelivery
130
130
  * @typedef { (ksr: KernelSyscallResult, vsr: VatSyscallResult) => void } SlogFinishSyscall
@@ -227,6 +227,7 @@ export {};
227
227
  *
228
228
  * @typedef { { exports: number,
229
229
  * imports: number,
230
+ * promises: number,
230
231
  * kv: number,
231
232
  * snapshots: number,
232
233
  * transcripts: number,
@@ -283,6 +284,13 @@ export {};
283
284
  * @property { boolean } [restartWorkerOnSnapshot] Reload worker immediately upon snapshot creation
284
285
  */
285
286
 
287
+ /**
288
+ * @typedef { import('./devices/mailbox/mailbox.js').Mailbox } Mailbox
289
+ */
290
+ /**
291
+ * @typedef { import('./devices/mailbox/mailbox.js').MailboxExport } MailboxExport
292
+ */
293
+
286
294
  /**
287
295
  * Vat Creation and Management
288
296
  *
@@ -92,6 +92,17 @@ export {};
92
92
  * @property {number} [computrons]
93
93
  */
94
94
 
95
+ /**
96
+ * @typedef {{ state: 'unresolved', refCount: number,
97
+ * decider: string | undefined, policy: string,
98
+ * subscribers: string[], queue: string[],
99
+ * }} UnresolvedPromiseRecord
100
+ * @typedef {{ state: 'fulfilled' | 'rejected', refCount: number,
101
+ * data: SwingSetCapData,
102
+ * }} SettledPromiseRecord
103
+ * @typedef {UnresolvedPromiseRecord | SettledPromiseRecord} PromiseRecord
104
+ */
105
+
95
106
  /**
96
107
  * @typedef {{
97
108
  * enablePipelining: boolean,
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Like prepare-strict-test-env but also sets up ses-ava and provides
3
+ * the ses-ava `test` function to be used as if it is the ava
4
+ * `test` function.
5
+ */
6
+
7
+ import '@agoric/swingset-liveslots/tools/prepare-strict-test-env.js';
8
+
9
+ import { wrapTest } from '@endo/ses-ava';
10
+ import rawTest from 'ava';
11
+
12
+ export * from '@agoric/swingset-liveslots/tools/prepare-strict-test-env.js';
13
+
14
+ export const test = wrapTest(rawTest);
15
+
16
+ // Does not import from a module because we're testing the global env
17
+ /* global globalThis */
18
+ export const VatData = globalThis.VatData;
19
+ assert(VatData);