@agoric/swingset-vat 0.33.0-u20.0 → 0.33.0-u21.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,11 +1,11 @@
1
1
  {
2
2
  "name": "@agoric/swingset-vat",
3
- "version": "0.33.0-u20.0",
3
+ "version": "0.33.0-u21.0",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "engines": {
8
- "node": "^18.12 || ^20.9"
8
+ "node": "^20.9 || ^22.11"
9
9
  },
10
10
  "bin": {
11
11
  "vat": "bin/vat"
@@ -13,29 +13,31 @@
13
13
  "scripts": {
14
14
  "build": "exit 0",
15
15
  "test": "ava",
16
+ "test:c8": "c8 --all $C8_OPTIONS ava",
16
17
  "test:xs": "SWINGSET_WORKER_TYPE=xs-worker ava",
17
18
  "test:xs-worker": "ava test/workers -m 'xsnap vat manager'",
18
19
  "lint-fix": "yarn lint:eslint --fix",
19
- "lint": "run-s --continue-on-error lint:*",
20
- "lint:types": "tsc",
21
- "lint:eslint": "eslint ."
20
+ "lint": "yarn run -T run-s --continue-on-error 'lint:*'",
21
+ "lint:types": "yarn run -T tsc",
22
+ "lint:eslint": "yarn run -T eslint ."
22
23
  },
23
24
  "devDependencies": {
24
- "@types/better-sqlite3": "^7.6.9",
25
+ "@types/better-sqlite3": "^7.6.13",
25
26
  "@types/microtime": "^2.1.0",
26
27
  "@types/tmp": "^0.2.0",
27
- "@types/yargs-parser": "^21.0.0"
28
+ "@types/yargs-parser": "^21.0.0",
29
+ "ava": "^5.3.0"
28
30
  },
29
31
  "dependencies": {
30
- "@agoric/internal": "^0.4.0-u20.0",
31
- "@agoric/kmarshal": "^0.1.1-u20.0",
32
- "@agoric/store": "^0.9.3-u20.0",
33
- "@agoric/swing-store": "^0.10.0-u20.0",
34
- "@agoric/swingset-liveslots": "^0.10.3-u20.0",
35
- "@agoric/swingset-xsnap-supervisor": "^0.10.3-u20.0",
36
- "@agoric/time": "^0.3.3-u20.0",
37
- "@agoric/vat-data": "^0.5.3-u20.0",
38
- "@agoric/xsnap-lockdown": "^0.14.1-u20.0",
32
+ "@agoric/internal": "workspace:*",
33
+ "@agoric/kmarshal": "workspace:*",
34
+ "@agoric/store": "workspace:*",
35
+ "@agoric/swing-store": "workspace:*",
36
+ "@agoric/swingset-liveslots": "workspace:*",
37
+ "@agoric/swingset-xsnap-supervisor": "workspace:*",
38
+ "@agoric/time": "workspace:*",
39
+ "@agoric/vat-data": "workspace:*",
40
+ "@agoric/xsnap-lockdown": "workspace:*",
39
41
  "@endo/base64": "^1.0.9",
40
42
  "@endo/bundle-source": "^4.0.0",
41
43
  "@endo/captp": "^4.4.5",
@@ -56,7 +58,7 @@
56
58
  "@endo/zip": "^1.0.9",
57
59
  "ansi-styles": "^6.2.1",
58
60
  "anylogger": "^0.21.0",
59
- "better-sqlite3": "^9.1.1",
61
+ "better-sqlite3": "^10.1.0",
60
62
  "import-meta-resolve": "^4.1.0",
61
63
  "microtime": "^3.1.0",
62
64
  "semver": "^6.3.0",
@@ -64,7 +66,7 @@
64
66
  "yargs-parser": "^21.1.1"
65
67
  },
66
68
  "peerDependencies": {
67
- "@agoric/xsnap": "^0.14.2",
69
+ "@agoric/xsnap": "workspace:*",
68
70
  "ava": "^5.3.0"
69
71
  },
70
72
  "files": [
@@ -100,7 +102,7 @@
100
102
  "access": "public"
101
103
  },
102
104
  "typeCoverage": {
103
- "atLeast": 76.25
105
+ "atLeast": 76.47
104
106
  },
105
- "gitHead": "8e4207fa19dabf76c1f91f8779b5b5b93570ecea"
107
+ "gitHead": "e4dd46857133403d584bcf822a81817b355532f9"
106
108
  }
@@ -425,8 +425,12 @@ export async function makeSwingsetController(
425
425
  return kernel.shutdown();
426
426
  },
427
427
 
428
- reapAllVats() {
429
- kernel.reapAllVats();
428
+ reapAllVats(previousVatPos) {
429
+ return kernel.reapAllVats(previousVatPos);
430
+ },
431
+
432
+ async snapshotAllVats() {
433
+ return kernel.snapshotAllVats();
430
434
  },
431
435
 
432
436
  changeKernelOptions(options) {
@@ -285,11 +285,9 @@ export const upgradeSwingset = kernelStorage => {
285
285
 
286
286
  const buggyKPIDs = []; // [kpid, vatID]
287
287
  for (const vatID of allVatIDs) {
288
- const prefix = `${vatID}.c.`;
289
- const len = prefix.length;
290
288
  const ckpPrefix = `${vatID}.c.kp`;
291
- for (const key of enumeratePrefixedKeys(kvStore, ckpPrefix)) {
292
- const kpid = key.slice(len);
289
+ for (const { suffix } of enumeratePrefixedKeys(kvStore, ckpPrefix)) {
290
+ const kpid = `kp${suffix}`;
293
291
  if (isSettled(kpid)) {
294
292
  const n = notifies.get(kpid);
295
293
  if (!n || !n.includes(vatID)) {
@@ -62,3 +62,5 @@ export function buildRootDeviceNode(tools) {
62
62
  },
63
63
  });
64
64
  }
65
+
66
+ /** @typedef {import('../../types-external.js').Device<ReturnType<typeof buildRootDeviceNode>>} BridgeDevice */
@@ -86,6 +86,7 @@ function makeTimerMap(state = undefined) {
86
86
  return copyState(schedule);
87
87
  }
88
88
 
89
+ /** @param {bigint} time */
89
90
  function eventsFor(time) {
90
91
  assert.typeof(time, 'bigint');
91
92
  for (let i = 0; i < schedule.length && schedule[i].time <= time; i += 1) {
@@ -101,17 +102,29 @@ function makeTimerMap(state = undefined) {
101
102
  // There's some question as to whether it's important to invoke the handlers
102
103
  // in the order of their deadlines. If so, we should probably ensure that the
103
104
  // recorded deadlines don't have finer granularity than the turns.
104
- function add(time, handler, repeater = undefined) {
105
+ /**
106
+ *
107
+ * @param {bigint} time
108
+ * @param {Waker} handler
109
+ * @param {number} [index]
110
+ * @returns {bigint}
111
+ */
112
+ function add(time, handler, index = undefined) {
105
113
  assert.typeof(time, 'bigint');
114
+ /** @type {IndexedHandler} */
106
115
  const handlerRecord =
107
- typeof repeater === 'number' ? { handler, index: repeater } : { handler };
116
+ typeof index === 'number' ? { handler, index } : { handler };
108
117
  const { handlers: records } = eventsFor(time);
109
118
  records.push(handlerRecord);
110
119
  schedule.sort((a, b) => Number(a.time - b.time));
111
120
  return time;
112
121
  }
113
122
 
114
- // Remove and return all pairs indexed by numbers up to target
123
+ /**
124
+ * Remove and return all pairs indexed by numbers up to target
125
+ *
126
+ * @param {bigint} target
127
+ */
115
128
  function removeEventsThrough(target) {
116
129
  assert.typeof(target, 'bigint');
117
130
  const returnValues = [];
@@ -132,33 +145,29 @@ function makeTimerMap(state = undefined) {
132
145
  }
133
146
 
134
147
  // We don't expect this to be called often, so we don't optimize for it.
148
+ /**
149
+ *
150
+ * @param {Waker} targetHandler
151
+ * @returns {bigint[]} times that have been removed (may contain duplicates)
152
+ */
135
153
  function remove(targetHandler) {
154
+ /** @type {bigint[]} */
136
155
  const droppedTimes = [];
137
156
  let i = 0;
138
157
  while (i < schedule.length) {
139
158
  const { time, handlers } = schedule[i];
140
- if (handlers.length === 1) {
141
- if (handlers[0].handler === targetHandler) {
142
- schedule.splice(i, 1);
159
+ // Nothing prevents a particular handler from appearing more than once
160
+ for (let j = handlers.length - 1; j >= 0; j -= 1) {
161
+ if (handlers[j].handler === targetHandler) {
162
+ handlers.splice(j, 1);
143
163
  droppedTimes.push(time);
144
- } else {
145
- i += 1;
146
164
  }
165
+ }
166
+ if (handlers.length === 0) {
167
+ // Splice out this element, preserving `i` so we visit any successor.
168
+ schedule.splice(i, 1);
147
169
  } else {
148
- // Nothing prevents a particular handler from appearing more than once
149
- for (const { handler } of handlers) {
150
- // @ts-expect-error xxx Waker vs IndexedHandler
151
- if (handler === targetHandler && handlers.indexOf(handler) !== -1) {
152
- // @ts-expect-error xxx Waker vs IndexedHandler
153
- handlers.splice(handlers.indexOf(handler), 1);
154
- droppedTimes.push(time);
155
- }
156
- }
157
- if (handlers.length === 0) {
158
- schedule.splice(i, 1);
159
- } else {
160
- i += 1;
161
- }
170
+ i += 1;
162
171
  }
163
172
  }
164
173
  return droppedTimes;
@@ -223,6 +232,7 @@ export function buildRootDeviceNode(tools) {
223
232
  // The latest time poll() was called. This might be a block height or it
224
233
  // might be a time from Date.now(). The current time is not reflected back
225
234
  // to the user.
235
+ /** @type {bigint} */
226
236
  let lastPolled = restart ? restart.lastPolled : 0n;
227
237
  let nextRepeater = restart ? restart.nextRepeater : 0;
228
238
 
@@ -276,6 +286,10 @@ export function buildRootDeviceNode(tools) {
276
286
  saveState();
277
287
  return baseTime;
278
288
  },
289
+ /**
290
+ * @param {Waker} handler
291
+ * @returns {bigint[]} times that have been removed (may contain duplicates)
292
+ */
279
293
  removeWakeup(handler) {
280
294
  const times = deadlines.remove(handler);
281
295
  saveState();
@@ -303,6 +317,10 @@ export function buildRootDeviceNode(tools) {
303
317
  saveState();
304
318
  return index;
305
319
  },
320
+ /**
321
+ * @param {number} index
322
+ * @param {Waker} handler
323
+ */
306
324
  schedule(index, handler) {
307
325
  const nextTime = nextScheduleTime(index, repeaters, lastPolled);
308
326
  deadlines.add(nextTime, handler, index);
@@ -311,6 +329,7 @@ export function buildRootDeviceNode(tools) {
311
329
  },
312
330
  });
313
331
  }
332
+ /** @typedef {import('../../types-external.js').Device<ReturnType<typeof buildRootDeviceNode>>} TimerDevice */
314
333
 
315
334
  // exported for testing. Only buildRootDeviceNode is intended for production
316
335
  // use.
@@ -1946,15 +1946,63 @@ export default function buildKernel(
1946
1946
  }
1947
1947
  }
1948
1948
 
1949
- function reapAllVats() {
1949
+ /** @returns {Generator<VatID>} */
1950
+ function* getAllVatIds() {
1950
1951
  for (const [_, vatID] of kernelKeeper.getStaticVats()) {
1951
- kernelKeeper.scheduleReap(vatID);
1952
+ yield vatID;
1952
1953
  }
1953
1954
  for (const vatID of kernelKeeper.getDynamicVats()) {
1954
- kernelKeeper.scheduleReap(vatID);
1955
+ yield vatID;
1955
1956
  }
1956
1957
  }
1957
1958
 
1959
+ function* getAllVatPosEntries() {
1960
+ for (const vatID of getAllVatIds()) {
1961
+ const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
1962
+ yield /** @type {const} */ ([
1963
+ vatID,
1964
+ vatKeeper.getTranscriptEndPosition(),
1965
+ ]);
1966
+ }
1967
+ }
1968
+
1969
+ function reapAllVats(previousVatPos = {}) {
1970
+ /** @type {Record<string, number>} */
1971
+ const currentVatPos = {};
1972
+
1973
+ for (const [vatID, endPos] of getAllVatPosEntries()) {
1974
+ const vatUsesTranscript = endPos !== 0;
1975
+ if (!vatUsesTranscript) {
1976
+ // The comms vat is a little special. It doesn't use a transcript,
1977
+ // doesn't implement BOYD, and is created with a never reap threshold.
1978
+ //
1979
+ // Here we conflate a vat that doesn't use a transcript with a vat
1980
+ // that cannot reap. We do not bother checking the vat options because
1981
+ // in tests we would actually like to force reap normal vats that may
1982
+ // have been configured with a never threshold.
1983
+ continue;
1984
+ } else if ((previousVatPos[vatID] ?? 0) < endPos) {
1985
+ kernelKeeper.scheduleReap(vatID);
1986
+ // We just added one delivery
1987
+ currentVatPos[vatID] = endPos + 1;
1988
+ } else {
1989
+ currentVatPos[vatID] = endPos;
1990
+ }
1991
+ }
1992
+
1993
+ return harden(currentVatPos);
1994
+ }
1995
+
1996
+ async function snapshotAllVats() {
1997
+ const snapshottedVats = [];
1998
+ await null;
1999
+ for (const vatID of getAllVatIds()) {
2000
+ const snapshotted = await vatWarehouse.maybeSaveSnapshot(vatID, 2);
2001
+ if (snapshotted) snapshottedVats.push(vatID);
2002
+ }
2003
+ return harden(snapshottedVats);
2004
+ }
2005
+
1958
2006
  async function step() {
1959
2007
  if (kernelPanic) {
1960
2008
  throw kernelPanic;
@@ -2159,6 +2207,7 @@ export default function buildKernel(
2159
2207
  run,
2160
2208
  shutdown,
2161
2209
  reapAllVats,
2210
+ snapshotAllVats,
2162
2211
  changeKernelOptions,
2163
2212
 
2164
2213
  // the rest are for testing and debugging
@@ -189,11 +189,12 @@ export function makeDeviceKeeper(kvStore, deviceID, tools) {
189
189
  /** @type {Array<[string, string, string]>} */
190
190
  const res = [];
191
191
  const prefix = `${deviceID}.c.`;
192
- for (const k of enumeratePrefixedKeys(kvStore, prefix)) {
193
- const slot = k.slice(prefix.length);
192
+ const prefixedKeys = enumeratePrefixedKeys(kvStore, prefix);
193
+
194
+ for (const { key, suffix: slot } of prefixedKeys) {
194
195
  if (!slot.startsWith('k')) {
195
196
  const devSlot = slot;
196
- const kernelSlot = kvStore.get(k);
197
+ const kernelSlot = kvStore.get(key);
197
198
  res.push([kernelSlot, deviceID, devSlot]);
198
199
  }
199
200
  }
@@ -212,9 +212,8 @@ function insistMeterID(m) {
212
212
  export const getAllStaticVats = kvStore => {
213
213
  const result = [];
214
214
  const prefix = 'vat.name.';
215
- for (const k of enumeratePrefixedKeys(kvStore, prefix)) {
216
- const vatID = kvStore.get(k) || Fail`getNextKey ensures get`;
217
- const name = k.slice(prefix.length);
215
+ for (const { key, suffix: name } of enumeratePrefixedKeys(kvStore, prefix)) {
216
+ const vatID = kvStore.get(key) || Fail`getNextKey ensures get`;
218
217
  result.push([name, vatID]);
219
218
  }
220
219
  return result;
@@ -718,17 +717,22 @@ export default function makeKernelKeeper(
718
717
  // We iterate through all ephemeral and virtual entries so the kernel
719
718
  // can ensure that they are abandoned by a vat being upgraded.
720
719
  const prefix = `${vatID}.c.`;
721
- const ephStart = `${prefix}o+`;
722
- const durStart = `${prefix}o+d`;
723
- const virStart = `${prefix}o+v`;
720
+ const ephStart = `o+`;
721
+ const durStart = `o+d`;
722
+ const virStart = `o+v`;
724
723
  /** @type {[string, string?][]} */
725
724
  const ranges = [[ephStart, durStart], [virStart]];
726
725
  for (const range of ranges) {
727
- for (const k of enumeratePrefixedKeys(kvStore, ...range)) {
728
- const vref = k.slice(prefix.length);
726
+ const rangeSuffix = range[0];
727
+ const args = /** @type {typeof ranges[0]} */ (
728
+ /** @type {unknown} */ (range.map(s => `${prefix}${s}`))
729
+ );
730
+ const prefixedKeys = enumeratePrefixedKeys(kvStore, ...args);
731
+ for (const { key, suffix } of prefixedKeys) {
732
+ const vref = `${rangeSuffix}${suffix}`;
729
733
  // exclude the root object, which is replaced by upgrade
730
734
  if (vref !== 'o+0') {
731
- const kref = kvStore.get(k);
735
+ const kref = kvStore.get(key);
732
736
  assert.typeof(kref, 'string');
733
737
  yield { kref, vref };
734
738
  }
@@ -1072,7 +1076,7 @@ export default function makeKernelKeeper(
1072
1076
 
1073
1077
  // first, scan for exported objects, which must be orphaned
1074
1078
  remaining = budget.exports ?? budget.default;
1075
- for (const k of enumeratePrefixedKeys(kvStore, exportPrefix)) {
1079
+ for (const { key } of enumeratePrefixedKeys(kvStore, exportPrefix)) {
1076
1080
  // The void for an object exported by a vat will always be of the form
1077
1081
  // `o+NN`. The '+' means that the vat exported the object (rather than
1078
1082
  // importing it) and therefore the object is owned by (i.e., within) the
@@ -1080,9 +1084,7 @@ export default function makeKernelKeeper(
1080
1084
  // begin with `vMM.c.o+`. In addition to deleting the c-list entry, we
1081
1085
  // must also delete the corresponding kernel owner entry for the object,
1082
1086
  // since the object will no longer be accessible.
1083
- const vref = stripPrefix(clistPrefix, k);
1084
- assert(vref.startsWith('o+'), vref);
1085
- const kref = kvStore.get(k);
1087
+ const kref = kvStore.get(key);
1086
1088
  // note: adds to maybeFreeKrefs, deletes c-list and .owner
1087
1089
  orphanKernelObject(kref, vatID);
1088
1090
  work.exports += 1;
@@ -1094,11 +1096,14 @@ export default function makeKernelKeeper(
1094
1096
 
1095
1097
  // then scan for imported objects, which must be decrefed
1096
1098
  remaining = budget.imports ?? budget.default;
1097
- for (const k of enumeratePrefixedKeys(kvStore, importPrefix)) {
1099
+ for (const { key, suffix } of enumeratePrefixedKeys(
1100
+ kvStore,
1101
+ importPrefix,
1102
+ )) {
1098
1103
  // abandoned imports: delete the clist entry as if the vat did a
1099
1104
  // drop+retire
1100
- const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1101
- const vref = stripPrefix(clistPrefix, k);
1105
+ const kref = kvStore.get(key) || Fail`getNextKey ensures get`;
1106
+ const vref = `o-${suffix}`;
1102
1107
  undertaker.deleteCListEntry(kref, vref);
1103
1108
  // that will also delete both db keys
1104
1109
  work.imports += 1;
@@ -1113,9 +1118,12 @@ export default function makeKernelKeeper(
1113
1118
  // kpids are still present in the dead vat's c-list. Clean those
1114
1119
  // up now.
1115
1120
  remaining = budget.promises ?? budget.default;
1116
- for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1117
- const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1118
- const vref = stripPrefix(clistPrefix, k);
1121
+ for (const { key, suffix } of enumeratePrefixedKeys(
1122
+ kvStore,
1123
+ promisePrefix,
1124
+ )) {
1125
+ const kref = kvStore.get(key) || Fail`getNextKey ensures get`;
1126
+ const vref = `p${suffix}`;
1119
1127
  undertaker.deleteCListEntry(kref, vref);
1120
1128
  // that will also delete both db keys
1121
1129
  work.promises += 1;
@@ -1127,8 +1135,8 @@ export default function makeKernelKeeper(
1127
1135
 
1128
1136
  // now loop back through everything and delete it all
1129
1137
  remaining = budget.kv ?? budget.default;
1130
- for (const k of enumeratePrefixedKeys(kvStore, `${vatID}.`)) {
1131
- kvStore.delete(k);
1138
+ for (const { key } of enumeratePrefixedKeys(kvStore, `${vatID}.`)) {
1139
+ kvStore.delete(key);
1132
1140
  work.kv += 1;
1133
1141
  remaining -= 1;
1134
1142
  if (remaining <= 0) {
@@ -1167,11 +1175,11 @@ export default function makeKernelKeeper(
1167
1175
  kvStore.set(DYNAMIC_IDS_KEY, JSON.stringify(newDynamicVatIDs));
1168
1176
  } else {
1169
1177
  kdebug(`removing static vat ${vatID}`);
1170
- for (const k of enumeratePrefixedKeys(kvStore, 'vat.name.')) {
1171
- if (kvStore.get(k) === vatID) {
1172
- kvStore.delete(k);
1178
+ const prefixedKeys = enumeratePrefixedKeys(kvStore, 'vat.name.');
1179
+ for (const { key, suffix: name } of prefixedKeys) {
1180
+ if (kvStore.get(key) === vatID) {
1181
+ kvStore.delete(key);
1173
1182
  const VAT_NAMES_KEY = 'vat.names';
1174
- const name = k.slice('vat.name.'.length);
1175
1183
  const oldStaticVatNames = JSON.parse(getRequired(VAT_NAMES_KEY));
1176
1184
  const newStaticVatNames = oldStaticVatNames.filter(v => v !== name);
1177
1185
  kvStore.set(VAT_NAMES_KEY, JSON.stringify(newStaticVatNames));
@@ -1240,7 +1248,7 @@ export default function makeKernelKeeper(
1240
1248
  function* enumeratePromisesByDecider(vatID) {
1241
1249
  insistVatID(vatID);
1242
1250
  const promisePrefix = `${vatID}.c.p`;
1243
- for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1251
+ for (const { key } of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1244
1252
  // The vpid for a promise imported or exported by a vat (and thus
1245
1253
  // potentially a promise for which the vat *might* be the decider) will
1246
1254
  // always be of the form `p+NN` or `p-NN`. The corresponding vpid->kpid
@@ -1250,7 +1258,7 @@ export default function makeKernelKeeper(
1250
1258
  // whether the vat is the decider or not. If it is, we add the promise
1251
1259
  // to the list of promises that must be rejected because the dead vat
1252
1260
  // will never be able to act upon them.
1253
- const kpid = getRequired(k);
1261
+ const kpid = getRequired(key);
1254
1262
  const p = getKernelPromise(kpid);
1255
1263
  if (p.state === 'unresolved' && p.decider === vatID) {
1256
1264
  yield [kpid, p];
@@ -1449,9 +1457,9 @@ export default function makeKernelKeeper(
1449
1457
 
1450
1458
  function getDevices() {
1451
1459
  const result = [];
1452
- for (const k of enumeratePrefixedKeys(kvStore, 'device.name.')) {
1453
- const name = k.slice(12);
1454
- const deviceID = kvStore.get(k) || Fail`getNextKey ensures get`;
1460
+ const prefixedKeys = enumeratePrefixedKeys(kvStore, 'device.name.');
1461
+ for (const { key, suffix: name } of prefixedKeys) {
1462
+ const deviceID = kvStore.get(key) || Fail`getNextKey ensures get`;
1455
1463
  result.push([name, deviceID]);
1456
1464
  }
1457
1465
  return result;
@@ -13,7 +13,8 @@ import { Fail } from '@endo/errors';
13
13
  * @param {KVStore} kvStore
14
14
  * @param {string} prefix
15
15
  * @param {string} [exclusiveEnd]
16
- * @yields {string} the next key with the prefix that is not >= exclusiveEnd
16
+ * @yields {{key: string; suffix: string}} the next `key` with the prefix that is not >= exclusiveEnd
17
+ * and the `suffix` which is obtained by stripping the supplied prefix from the key
17
18
  */
18
19
  export function* enumeratePrefixedKeys(kvStore, prefix, exclusiveEnd) {
19
20
  /** @type {string | undefined} */
@@ -26,7 +27,7 @@ export function* enumeratePrefixedKeys(kvStore, prefix, exclusiveEnd) {
26
27
  if (exclusiveEnd && key >= exclusiveEnd) {
27
28
  break;
28
29
  }
29
- yield key;
30
+ yield { key, suffix: key.slice(prefix.length) };
30
31
  }
31
32
  }
32
33
  harden(enumeratePrefixedKeys);
@@ -761,11 +761,13 @@ export function makeVatKeeper(
761
761
  function dumpState() {
762
762
  const res = [];
763
763
  const prefix = `${vatID}.c.`;
764
- for (const k of enumeratePrefixedKeys(kvStore, prefix)) {
765
- const slot = k.slice(prefix.length);
764
+ for (const { key, suffix: slot } of enumeratePrefixedKeys(
765
+ kvStore,
766
+ prefix,
767
+ )) {
766
768
  if (!slot.startsWith('k')) {
767
769
  const vatSlot = slot;
768
- const kernelSlot = kvStore.get(k) || Fail`getNextKey ensures get`;
770
+ const kernelSlot = kvStore.get(key) || Fail`getNextKey ensures get`;
769
771
  /** @type { [string, string, string] } */
770
772
  const item = [kernelSlot, vatID, vatSlot];
771
773
  res.push(item);
@@ -566,18 +566,20 @@ export function makeVatWarehouse({
566
566
 
567
567
  /**
568
568
  * Save a heap snapshot for the given vatID, if the snapshotInterval
569
- * is satisified
569
+ * is satisfied or a lower explicit delivery count has been reached.
570
570
  *
571
571
  * @param {VatID} vatID
572
+ * @param {number} [minDeliveryCount]
572
573
  */
573
- async function maybeSaveSnapshot(vatID) {
574
- const recreate = true; // PANIC in the failure case
575
- const { manager } = await ensureVatOnline(vatID, recreate);
576
- if (!manager.makeSnapshot) {
577
- return false; // worker cannot make snapshots
574
+ async function maybeSaveSnapshot(vatID, minDeliveryCount = snapshotInterval) {
575
+ kernelKeeper.vatIsAlive(vatID) || Fail`${q(vatID)}: not alive`;
576
+ const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
577
+ const vatOptions = vatKeeper.getOptions();
578
+
579
+ if (!vatOptions.useTranscript) {
580
+ return false;
578
581
  }
579
582
 
580
- const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
581
583
  let reason;
582
584
 
583
585
  const hasSnapshot = !!vatKeeper.getSnapshotInfo();
@@ -586,15 +588,21 @@ export function makeVatWarehouse({
586
588
  if (!hasSnapshot && deliveriesInSpan >= snapshotInitial) {
587
589
  // begin snapshot after 'snapshotInitial' deliveries in an incarnation
588
590
  reason = { snapshotInitial };
589
- } else if (deliveriesInSpan >= snapshotInterval) {
591
+ } else if (deliveriesInSpan >= minDeliveryCount) {
590
592
  // begin snapshot after 'snapshotInterval' deliveries in a span
591
- reason = { snapshotInterval };
593
+ reason = { snapshotInterval: minDeliveryCount };
592
594
  }
593
595
  // console.log('maybeSaveSnapshot: reason:', reason);
594
596
  if (!reason) {
595
597
  return false; // not time to make a snapshot
596
598
  }
597
599
 
600
+ const recreate = true; // PANIC in the failure case
601
+ const { manager } = await ensureVatOnline(vatID, recreate);
602
+ if (!manager.makeSnapshot) {
603
+ return false; // worker cannot make snapshots
604
+ }
605
+
598
606
  // always do a bringOutYourDead just before a snapshot, to shake
599
607
  // loose as much garbage as we can, and to minimize the GC
600
608
  // sensitivity effects of the forced GC that snapshots perform
@@ -1,24 +1,11 @@
1
- import { Fail } from '@endo/errors';
1
+ import { krefOf, kunser } from '@agoric/kmarshal';
2
2
  import { passStyleOf } from '@endo/far';
3
- import { kunser, krefOf } from '@agoric/kmarshal';
4
3
 
5
4
  /**
6
- * Assert function to ensure that something expected to be a capdata object
7
- * actually is. A capdata object should have a .body property that's a string
8
- * and a .slots property that's an array of strings.
9
- *
10
- * @param {any} capdata The object to be tested
11
- * @throws {Error} if, upon inspection, the parameter does not satisfy the above
12
- * criteria.
13
- * @returns {asserts capdata is import('@endo/marshal').CapData<string>}
5
+ * @import {CapData} from '@endo/marshal';
14
6
  */
15
- export function insistCapData(capdata) {
16
- typeof capdata.body === 'string' ||
17
- Fail`capdata has non-string .body ${capdata.body}`;
18
- Array.isArray(capdata.slots) ||
19
- Fail`capdata has non-Array slots ${capdata.slots}`;
20
- // TODO check that the .slots array elements are actually strings
21
- }
7
+
8
+ export { insistCapData } from '@agoric/swingset-liveslots/src/capdata.js';
22
9
 
23
10
  /**
24
11
  * Returns the slot of a presence if the provided capdata is composed
@@ -12,6 +12,13 @@ export {};
12
12
  * includes standard services which user-provided vat code might interact
13
13
  * with, like VatAdminService. */
14
14
 
15
+ /**
16
+ * @template T
17
+ * @typedef {'Device' & { __deviceType__: T }} Device
18
+ */
19
+
20
+ /** @typedef {<T>(target: Device<T>) => T} DProxy (approximately) */
21
+
15
22
  /**
16
23
  * @typedef {(extraProps?: SlogDurationProps) => void} FinishSlogDuration
17
24
  */
@@ -25,12 +32,17 @@ export {};
25
32
  * @typedef {'getExport' | 'nestedEvaluate' | 'endoZipBase64'} BundleFormat
26
33
  * @typedef { { moduleFormat: 'getExport', source: string, sourceMap?: string } } GetExportBundle
27
34
  * @typedef { { moduleFormat: 'nestedEvaluate', source: string, sourceMap?: string } } NestedEvaluateBundle
28
- * @typedef { { moduleFormat: 'test' } } TestBundle
35
+ * @typedef { { moduleFormat: 'test', [x: symbol]: Record<PropertyKey, unknown> } } TestBundle
29
36
  * @typedef { EndoZipBase64Bundle | GetExportBundle | NestedEvaluateBundle | TestBundle} Bundle
30
37
  */
31
38
 
32
39
  /**
33
40
  * @typedef { 'local' | 'node-subprocess' | 'xsnap' | 'xs-worker' } ManagerType
41
+ * The type of worker for hosting a vat.
42
+ * - **local**: a Compartment in the SwingSet Node.js process
43
+ * - **node-subprocess**: a child process using the same Node.js executable
44
+ * (`process.execPath`)
45
+ * - **xsnap** or **xs-worker**: an {@link @agoric/xsnap! @agoric/xsnap} worker
34
46
  */
35
47
 
36
48
  /**
@@ -390,7 +402,9 @@ export {};
390
402
  *
391
403
  * @typedef { { vatParameters?: object, upgradeMessage?: string } } VatUpgradeOptions
392
404
  * @typedef { { incarnationNumber: number } } VatUpgradeResults
393
- *
405
+ */
406
+
407
+ /**
394
408
  * @callback ShutdownWithFailure
395
409
  * Called to shut something down because something went wrong, where the reason
396
410
  * is supposed to be an Error that describes what went wrong. Some valid
@@ -401,7 +415,9 @@ export {};
401
415
  *
402
416
  * @param {Error} reason
403
417
  * @returns {void}
404
- *
418
+ */
419
+
420
+ /**
405
421
  * @typedef {object} VatAdminFacet
406
422
  * A powerful object corresponding with a vat
407
423
  * that can be used to upgrade it with new code or parameters,
@@ -419,8 +435,9 @@ export {};
419
435
  * in which the JS memory space is abandoned. The new image is launched with access to 'baggage'
420
436
  * and any durable storage reachable from it, and must fulfill all the obligations of the previous
421
437
  * incarnation.
422
- *
423
- *
438
+ */
439
+
440
+ /**
424
441
  * @typedef {{ adminNode: Guarded<VatAdminFacet>, root: object }} CreateVatResults
425
442
  *
426
443
  * @typedef {object} VatAdminSvc
@@ -429,5 +446,4 @@ export {};
429
446
  * @property {(name: string) => ERef<BundleCap>} getNamedBundleCap
430
447
  * @property {(name: string) => ERef<BundleID>} getBundleIDByName
431
448
  * @property {(bundleCap: BundleCap, options?: DynamicVatOptions) => ERef<CreateVatResults>} createVat
432
- *
433
449
  */
@@ -7,6 +7,8 @@ import { Far, E } from '@endo/far';
7
7
 
8
8
  /**
9
9
  * @import {ERef} from '@endo/far';
10
+ * @import {MapStore} from '@agoric/store';
11
+ * @import {Device, DProxy} from '@agoric/swingset-vat/src/types-external.js';
10
12
  */
11
13
 
12
14
  /** @type {{ onReset: (firstTime: Promise<boolean>) => void}} */
@@ -15,13 +17,6 @@ const DEFAULT_RESETTER = Far('resetter', { onReset: _ => {} });
15
17
  /** @type {{ walk: (pluginRootP: any) => any }} */
16
18
  const DEFAULT_WALKER = Far('walker', { walk: pluginRootP => pluginRootP });
17
19
 
18
- /**
19
- * @template T
20
- * @typedef {'Device' & { __deviceType__: T }} Device
21
- */
22
-
23
- /** @typedef {<T>(target: Device<T>) => T} DProxy (approximately) */
24
-
25
20
  /**
26
21
  * @callback LoadPlugin
27
22
  * @param {string} specifier
@@ -15,9 +15,11 @@ import { TimeMath } from '@agoric/time';
15
15
 
16
16
  /**
17
17
  * @import {LegacyWeakMap, WeakMapStore} from '@agoric/store';
18
- * @import {MapStore} from '@agoric/swingset-liveslots';
18
+ * @import {Baggage, MapStore} from '@agoric/swingset-liveslots';
19
19
  * @import {Passable, RemotableObject} from '@endo/pass-style';
20
20
  * @import {Key} from '@endo/patterns';
21
+ * @import {TimerDevice} from '../../devices/timer/device-timer.js';
22
+ * @import {DProxy} from '../../types-external.js';
21
23
  */
22
24
 
23
25
  // This consumes O(N) RAM only for outstanding promises, via wakeAt(),
@@ -237,9 +239,17 @@ const measureInterval = (start, interval, now) => {
237
239
  return { latest, next };
238
240
  };
239
241
 
242
+ /**
243
+ * @param {{
244
+ * D: DProxy;
245
+ * }} vatPowers
246
+ * @param {{}} _vatParameters
247
+ * @param {Baggage} baggage
248
+ */
240
249
  export const buildRootObject = (vatPowers, _vatParameters, baggage) => {
241
250
  const { D } = vatPowers;
242
251
 
252
+ /** @type {TimerDevice} */
243
253
  let timerDevice;
244
254
  const insistDevice = () => {
245
255
  assert(timerDevice, 'TimerService used before createTimerService()');
@@ -455,9 +465,6 @@ export const buildRootObject = (vatPowers, _vatParameters, baggage) => {
455
465
  },
456
466
  };
457
467
 
458
- /**
459
- * @returns { PromiseEvent }
460
- */
461
468
  const makePromiseEvent = prepareKind(
462
469
  baggage,
463
470
  'promiseEvent',
@@ -948,7 +955,7 @@ export const buildRootObject = (vatPowers, _vatParameters, baggage) => {
948
955
  * device, but we don't prohibit it from being called again (to
949
956
  * replace the device), just in case that's useful someday
950
957
  *
951
- * @param {unknown} timerNode
958
+ * @param {TimerDevice} timerNode
952
959
  * @returns {TimerService}
953
960
  */
954
961
  const createTimerService = timerNode => {
@@ -20,7 +20,7 @@ import {
20
20
 
21
21
  /**
22
22
  * @import {VatAdminRootDeviceNode} from '../../devices/vat-admin/device-vat-admin.js';
23
- * @import {DProxy} from'../plugin-manager.js';
23
+ * @import {DProxy} from'../../types-external.js';
24
24
  * @import {Baggage} from '@agoric/vat-data';
25
25
  */
26
26
 
@@ -49,7 +49,7 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) {
49
49
  const pendingBundles = new Map();
50
50
  const pendingUpgrades = new Map(); // upgradeID -> Promise<UpgradeResults>
51
51
 
52
- /** @type {import('../plugin-manager.js').Device<VatAdminRootDeviceNode>} */
52
+ /** @type {import('../../types-external.js').Device<VatAdminRootDeviceNode>} */
53
53
  let vatAdminDev;
54
54
 
55
55
  const runningVats = new Map(); // vatID -> [doneP, { resolve, reject }]
@@ -8,7 +8,7 @@ import { buildRootObject } from '../src/vats/timer/vat-timer.js';
8
8
  /**
9
9
  * @import {Timestamp} from '@agoric/time'
10
10
  * @import {TimerService} from '@agoric/time'
11
- * @import {Waker} from '../src/devices/timer/device-timer.js'
11
+ * @import {TimerDevice, Waker} from '../src/devices/timer/device-timer.js'
12
12
  *
13
13
  * @typedef {object} ManualTimerCallbacks
14
14
  * @property {(newTime: bigint, msg?: string) => void} [advanceTo]
@@ -34,7 +34,7 @@ const setup = callbacks => {
34
34
  currentWakeup: undefined,
35
35
  currentHandler: undefined,
36
36
  };
37
- const deviceMarker = harden({});
37
+ const deviceMarker = /** @type {TimerDevice} */ (harden({}));
38
38
  const timerDeviceFuncs = harden({
39
39
  getLastPolled: () => state.now,
40
40
  setWakeup: (when, handler) => {
@@ -54,6 +54,7 @@ const setup = callbacks => {
54
54
  state.currentHandler = undefined;
55
55
  },
56
56
  });
57
+ /** @type {any} */
57
58
  const D = node => {
58
59
  assert.equal(node, deviceMarker, 'fake D only supports devices.timer');
59
60
  return timerDeviceFuncs;