@agoric/swingset-vat 0.33.0-upgrade-18-dev-aaebae4.0 → 0.33.0-upgrade-18-dev-cc4b6b8.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-18-dev-aaebae4.0+aaebae4",
3
+ "version": "0.33.0-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,34 +27,33 @@
27
27
  "@types/yargs-parser": "^21.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@agoric/internal": "0.4.0-upgrade-18-dev-aaebae4.0+aaebae4",
31
- "@agoric/kmarshal": "0.1.1-upgrade-18-dev-aaebae4.0+aaebae4",
32
- "@agoric/store": "0.9.3-upgrade-18-dev-aaebae4.0+aaebae4",
33
- "@agoric/swing-store": "0.10.0-upgrade-18-dev-aaebae4.0+aaebae4",
34
- "@agoric/swingset-liveslots": "0.10.3-upgrade-18-dev-aaebae4.0+aaebae4",
35
- "@agoric/swingset-xsnap-supervisor": "0.10.3-upgrade-18-dev-aaebae4.0+aaebae4",
36
- "@agoric/time": "0.3.3-upgrade-18-dev-aaebae4.0+aaebae4",
37
- "@agoric/vat-data": "0.5.3-upgrade-18-dev-aaebae4.0+aaebae4",
38
- "@agoric/xsnap": "0.14.3-upgrade-18-dev-aaebae4.0+aaebae4",
39
- "@agoric/xsnap-lockdown": "0.14.1-upgrade-18-dev-aaebae4.0+aaebae4",
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",
30
+ "@agoric/internal": "0.4.0-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
31
+ "@agoric/kmarshal": "0.1.1-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
32
+ "@agoric/store": "0.9.3-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
33
+ "@agoric/swing-store": "0.10.0-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
34
+ "@agoric/swingset-liveslots": "0.10.3-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
35
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
36
+ "@agoric/time": "0.3.3-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
37
+ "@agoric/vat-data": "0.5.3-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
38
+ "@agoric/xsnap-lockdown": "0.14.1-upgrade-18-dev-cc4b6b8.0+cc4b6b8",
39
+ "@endo/base64": "^1.0.9",
40
+ "@endo/bundle-source": "^3.5.0",
41
+ "@endo/captp": "^4.4.3",
42
+ "@endo/check-bundle": "^1.0.12",
43
+ "@endo/compartment-mapper": "^1.4.0",
44
+ "@endo/errors": "^1.2.8",
45
+ "@endo/eventual-send": "^1.2.8",
46
+ "@endo/far": "^1.1.9",
47
+ "@endo/import-bundle": "^1.3.2",
48
+ "@endo/init": "^1.1.7",
49
+ "@endo/marshal": "^1.6.2",
50
+ "@endo/nat": "^5.0.13",
51
+ "@endo/pass-style": "^1.4.7",
52
+ "@endo/patterns": "^1.4.7",
53
+ "@endo/promise-kit": "^1.1.8",
54
+ "@endo/ses-ava": "^1.2.8",
55
+ "@endo/stream": "^1.2.8",
56
+ "@endo/zip": "^1.0.9",
58
57
  "ansi-styles": "^6.2.1",
59
58
  "anylogger": "^0.21.0",
60
59
  "better-sqlite3": "^9.1.1",
@@ -65,6 +64,7 @@
65
64
  "yargs-parser": "^21.1.1"
66
65
  },
67
66
  "peerDependencies": {
67
+ "@agoric/xsnap": "^0.14.2",
68
68
  "ava": "^5.3.0"
69
69
  },
70
70
  "files": [
@@ -101,7 +101,7 @@
101
101
  "access": "public"
102
102
  },
103
103
  "typeCoverage": {
104
- "atLeast": 75.82
104
+ "atLeast": 76.28
105
105
  },
106
- "gitHead": "aaebae4892112869b545faad80bfbe0b8d8602b8"
106
+ "gitHead": "cc4b6b8b8131b14b4e50f45ff01d6018f9ee3995"
107
107
  }
@@ -15,6 +15,7 @@ import { initSwingStore } from '@agoric/swing-store';
15
15
 
16
16
  import { mustMatch, M } from '@endo/patterns';
17
17
  import { checkBundle } from '@endo/check-bundle/lite.js';
18
+ import { deepCopyJsonable } from '@agoric/internal/src/js-utils.js';
18
19
  import engineGC from '@agoric/internal/src/lib-nodejs/engine-gc.js';
19
20
  import { startSubprocessWorker } from '@agoric/internal/src/lib-nodejs/spawnSubprocessWorker.js';
20
21
  import { waitUntilQuiescent } from '@agoric/internal/src/lib-nodejs/waitUntilQuiescent.js';
@@ -263,13 +264,6 @@ export async function makeSwingsetController(
263
264
 
264
265
  await kernel.start();
265
266
 
266
- /**
267
- * @param {T} x
268
- * @returns {T}
269
- * @template T
270
- */
271
- const defensiveCopy = x => JSON.parse(JSON.stringify(x));
272
-
273
267
  /**
274
268
  * Validate and install a code bundle.
275
269
  *
@@ -304,7 +298,7 @@ export async function makeSwingsetController(
304
298
  writeSlogObject,
305
299
 
306
300
  dump() {
307
- return defensiveCopy(kernel.dump());
301
+ return deepCopyJsonable(kernel.dump());
308
302
  },
309
303
 
310
304
  verboseDebugMode(flag) {
@@ -340,11 +334,11 @@ export async function makeSwingsetController(
340
334
  },
341
335
 
342
336
  getStats() {
343
- return defensiveCopy(kernel.getStats());
337
+ return deepCopyJsonable(kernel.getStats());
344
338
  },
345
339
 
346
340
  getStatus() {
347
- return defensiveCopy(kernel.getStatus());
341
+ return deepCopyJsonable(kernel.getStatus());
348
342
  },
349
343
 
350
344
  getActivityhash() {
@@ -1,5 +1,3 @@
1
- /* eslint-disable no-use-before-define */
2
-
3
1
  import { assert, Fail } from '@endo/errors';
4
2
  import { makeMarshal } from '@endo/marshal';
5
3
  import { Far } from '@endo/far';
@@ -2,8 +2,8 @@
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
 
5
- import { assert, Fail } from '@endo/errors';
6
- import { makeTracer } from '@agoric/internal';
5
+ import { assert, b, Fail } from '@endo/errors';
6
+ import { deepCopyJsonable, makeTracer } from '@agoric/internal';
7
7
  import { mustMatch } from '@agoric/store';
8
8
  import bundleSource from '@endo/bundle-source';
9
9
  import { resolve as resolveModuleSpecifier } from 'import-meta-resolve';
@@ -86,16 +86,6 @@ export async function buildKernelBundles() {
86
86
  return harden({ kernel: kernelBundle, ...vdBundles });
87
87
  }
88
88
 
89
- function byName(a, b) {
90
- if (a.name < b.name) {
91
- return -1;
92
- }
93
- if (a.name > b.name) {
94
- return 1;
95
- }
96
- return 0;
97
- }
98
-
99
89
  /**
100
90
  * Scan a directory for files defining the vats to bootstrap for a swingset, and
101
91
  * produce a swingset config object for what was found there. Looks for files
@@ -126,18 +116,18 @@ export function loadBasedir(basedir, options = {}) {
126
116
  const { includeDevDependencies = false, bundleFormat = undefined } = options;
127
117
  /** @type { SwingSetConfigDescriptor } */
128
118
  const vats = {};
129
- const subs = fs.readdirSync(basedir, { withFileTypes: true });
130
- subs.sort(byName);
131
- for (const dirent of subs) {
132
- if (
133
- dirent.name.startsWith('vat-') &&
134
- dirent.name.endsWith('.js') &&
135
- dirent.isFile()
136
- ) {
137
- const name = dirent.name.slice('vat-'.length, -'.js'.length);
138
- const vatSourcePath = path.resolve(basedir, dirent.name);
139
- vats[name] = { sourceSpec: vatSourcePath, parameters: {} };
140
- }
119
+ const rVatName = /^vat-(.*)\.js$/s;
120
+ const files = fs.readdirSync(basedir, { withFileTypes: true });
121
+ const vatFiles = files.flatMap(dirent => {
122
+ const file = dirent.name;
123
+ const m = rVatName.exec(file);
124
+ return m && dirent.isFile() ? [{ file, label: m[1] }] : [];
125
+ });
126
+ // eslint-disable-next-line no-shadow,no-nested-ternary
127
+ vatFiles.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
128
+ for (const { file, label } of vatFiles) {
129
+ const vatSourcePath = path.resolve(basedir, file);
130
+ vats[label] = { sourceSpec: vatSourcePath, parameters: {} };
141
131
  }
142
132
  /** @type {string | void} */
143
133
  let bootstrapPath = path.resolve(basedir, 'bootstrap.js');
@@ -185,37 +175,42 @@ async function resolveSpecFromConfig(referrer, specPath) {
185
175
  }
186
176
 
187
177
  /**
188
- * For each entry in a config descriptor (i.e, `vats`, `bundles`, etc), convert
189
- * it to normal form: resolve each pathname to a context-insensitive absolute
190
- * path and make sure it has a `parameters` property if it's supposed to.
178
+ * Convert each entry in a config descriptor group (`vats`/`bundles`/etc.) to
179
+ * normal form: resolve each pathname to a context-insensitive absolute path and
180
+ * run any other appropriate fixup.
191
181
  *
192
- * @param {SwingSetConfigDescriptor | void} desc The config descriptor to be normalized.
193
- * @param {string} referrer The pathname of the file or directory in which the
194
- * config file was found
195
- * @param {boolean} expectParameters `true` if the entries should have parameters (for
196
- * example, `true` for `vats` but `false` for bundles).
182
+ * @param {SwingSetConfig} config
183
+ * @param {'vats' | 'bundles' | 'devices'} groupName
184
+ * @param {string | undefined} configPath of the containing config file
185
+ * @param {string} referrer URL
186
+ * @param {(entry: SwingSetConfigProperties, name?: string) => void} [fixupEntry]
187
+ * A function to call on each entry to e.g. add defaults for missing fields
188
+ * such as vat `parameters`.
197
189
  */
198
- async function normalizeConfigDescriptor(desc, referrer, expectParameters) {
199
- const normalizeSpec = async (entry, key) => {
200
- return resolveSpecFromConfig(referrer, entry[key]).then(spec => {
201
- fs.existsSync(spec) ||
202
- Fail`spec for ${entry[key]} does not exist: ${spec}`;
203
- entry[key] = spec;
204
- });
190
+ async function normalizeConfigDescriptor(
191
+ config,
192
+ groupName,
193
+ configPath,
194
+ referrer,
195
+ fixupEntry,
196
+ ) {
197
+ const normalizeSpec = async (entry, specKey, name) => {
198
+ const sourcePath = await resolveSpecFromConfig(referrer, entry[specKey]);
199
+ fs.existsSync(sourcePath) ||
200
+ Fail`${sourcePath} for ${b(groupName)}[${name}].${b(specKey)} in ${configPath} config file does not exist`;
201
+ entry[specKey] = sourcePath;
205
202
  };
206
203
 
207
204
  const jobs = [];
205
+ const desc = config[groupName];
208
206
  if (desc) {
209
- for (const name of Object.keys(desc)) {
210
- const entry = desc[name];
207
+ for (const [name, entry] of Object.entries(desc)) {
208
+ fixupEntry?.(entry, name);
211
209
  if ('sourceSpec' in entry) {
212
- jobs.push(normalizeSpec(entry, 'sourceSpec'));
210
+ jobs.push(normalizeSpec(entry, 'sourceSpec', name));
213
211
  }
214
212
  if ('bundleSpec' in entry) {
215
- jobs.push(normalizeSpec(entry, 'bundleSpec'));
216
- }
217
- if (expectParameters && !entry.parameters) {
218
- entry.parameters = {};
213
+ jobs.push(normalizeSpec(entry, 'bundleSpec', name));
219
214
  }
220
215
  }
221
216
  }
@@ -223,27 +218,41 @@ async function normalizeConfigDescriptor(desc, referrer, expectParameters) {
223
218
  }
224
219
 
225
220
  /**
226
- * Read and parse a swingset config file and return it in normalized form.
227
- *
228
- * @param {string} configPath Path to the config file to be processed
229
- *
230
- * @returns {Promise<SwingSetConfig | null>} the contained config object, in normalized form, or null if the
231
- * requested config file did not exist.
221
+ * @param {SwingSetConfig} config
222
+ * @param {string} [configPath]
223
+ * @returns {Promise<void>}
224
+ * @throws {Error} if the config is invalid
225
+ */
226
+ export async function normalizeConfig(config, configPath) {
227
+ const base = `file://${process.cwd()}/`;
228
+ const referrer = configPath
229
+ ? new URL(configPath, base).href
230
+ : new URL(base).href;
231
+ const fixupVat = vat => (vat.parameters ||= {});
232
+ await Promise.all([
233
+ normalizeConfigDescriptor(config, 'vats', configPath, referrer, fixupVat),
234
+ normalizeConfigDescriptor(config, 'bundles', configPath, referrer),
235
+ // TODO: represent devices
236
+ // normalizeConfigDescriptor(config, 'devices', configPath, referrer),
237
+ ]);
238
+ config.bootstrap ||
239
+ Fail`no designated bootstrap vat in ${configPath} config file`;
240
+ (config.vats && config.vats[/** @type {string} */ (config.bootstrap)]) ||
241
+ Fail`bootstrap vat ${config.bootstrap} not found in ${configPath} config file`;
242
+ }
243
+
244
+ /**
245
+ * Read and normalize a swingset config file.
232
246
  *
233
- * @throws {Error} if the file existed but was inaccessible, malformed, or otherwise
234
- * invalid.
247
+ * @param {string} configPath
248
+ * @returns {Promise<SwingSetConfig | null>} the normalized config,
249
+ * or null if the file did not exist
235
250
  */
236
251
  export async function loadSwingsetConfigFile(configPath) {
237
252
  await null;
238
253
  try {
239
254
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
240
- const referrer = new URL(configPath, `file://${process.cwd()}/`).toString();
241
- await normalizeConfigDescriptor(config.vats, referrer, true);
242
- await normalizeConfigDescriptor(config.bundles, referrer, false);
243
- // await normalizeConfigDescriptor(config.devices, referrer, true); // TODO: represent devices
244
- config.bootstrap || Fail`no designated bootstrap vat in ${configPath}`;
245
- (config.vats && config.vats[config.bootstrap]) ||
246
- Fail`bootstrap vat ${config.bootstrap} not found in ${configPath}`;
255
+ await normalizeConfig(config, configPath);
247
256
  return config;
248
257
  } catch (e) {
249
258
  console.error(`failed to load ${configPath}`);
@@ -314,7 +323,7 @@ export async function initializeSwingset(
314
323
  } = runtimeOptions;
315
324
 
316
325
  // copy config so we can safely mess with it even if it's shared or hardened
317
- config = JSON.parse(JSON.stringify(config));
326
+ config = deepCopyJsonable(config);
318
327
  if (!config.bundles) {
319
328
  config.bundles = {};
320
329
  }
@@ -10,13 +10,38 @@ import {
10
10
  import { enumeratePrefixedKeys } from '../kernel/state/storageHelper.js';
11
11
 
12
12
  /**
13
- * @import {RunQueueEvent} from '../types-internal.js';
13
+ * @import {ReapDirtThreshold, RunQueueEvent} from '../types-internal.js';
14
14
  */
15
15
 
16
- const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
17
- // This is called, once per vat, when upgradeSwingset migrates from
18
- // v0 to v1
16
+ /**
17
+ * Parse a string of decimal digits into a number.
18
+ *
19
+ * @param {string} digits
20
+ * @param {string} label
21
+ * @returns {number}
22
+ */
23
+ const mustParseInt = (digits, label) => {
24
+ assert(
25
+ digits.match(/^\d+$/),
26
+ `expected ${label}=${digits} to be a decimal integer`,
27
+ );
28
+ return Number(digits);
29
+ };
19
30
 
31
+ /**
32
+ * Called for each vat when upgradeSwingset migrates from v0 to v1.
33
+ *
34
+ * @param {KVStore} kvStore
35
+ * @param {(key: string) => string} getRequired
36
+ * @param {ReapDirtThreshold} defaultReapDirtThreshold
37
+ * @param {string} vatID
38
+ */
39
+ const upgradeVatV0toV1 = (
40
+ kvStore,
41
+ getRequired,
42
+ defaultReapDirtThreshold,
43
+ vatID,
44
+ ) => {
20
45
  // schema v0:
21
46
  // Each vat has a `vNN.reapInterval` and `vNN.reapCountdown`.
22
47
  // vNN.options has a `.reapInterval` property (however it was not
@@ -33,15 +58,10 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
33
58
  // `defaultReapDirtThreshold`)
34
59
 
35
60
  const reapDirtKey = `${vatID}.reapDirt`;
36
-
37
- assert(kvStore.has(oldReapIntervalKey), oldReapIntervalKey);
38
- assert(kvStore.has(oldReapCountdownKey), oldReapCountdownKey);
39
61
  assert(!kvStore.has(reapDirtKey), reapDirtKey);
40
62
 
41
- const reapIntervalString = kvStore.get(oldReapIntervalKey);
42
- const reapCountdownString = kvStore.get(oldReapCountdownKey);
43
- assert(reapIntervalString !== undefined);
44
- assert(reapCountdownString !== undefined);
63
+ const reapIntervalString = getRequired(oldReapIntervalKey);
64
+ const reapCountdownString = getRequired(oldReapCountdownKey);
45
65
 
46
66
  const intervalIsNever = reapIntervalString === 'never';
47
67
  const countdownIsNever = reapCountdownString === 'never';
@@ -62,8 +82,11 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
62
82
  threshold.never = true;
63
83
  } else {
64
84
  // deduce delivery count from old countdown values
65
- const reapInterval = Number.parseInt(reapIntervalString, 10);
66
- const reapCountdown = Number.parseInt(reapCountdownString, 10);
85
+ const reapInterval = mustParseInt(reapIntervalString, oldReapIntervalKey);
86
+ const reapCountdown = mustParseInt(
87
+ reapCountdownString,
88
+ oldReapCountdownKey,
89
+ );
67
90
  const deliveries = reapInterval - reapCountdown;
68
91
  reapDirt.deliveries = Math.max(deliveries, 0); // just in case
69
92
  if (reapInterval !== defaultReapDirtThreshold.deliveries) {
@@ -76,7 +99,7 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
76
99
  kvStore.set(reapDirtKey, JSON.stringify(reapDirt));
77
100
 
78
101
  // Update options to use the new schema.
79
- const options = JSON.parse(kvStore.get(vatOptionsKey));
102
+ const options = JSON.parse(getRequired(vatOptionsKey));
80
103
  delete options.reapInterval;
81
104
  options.reapDirtThreshold = threshold;
82
105
  kvStore.set(vatOptionsKey, JSON.stringify(options));
@@ -104,14 +127,11 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
104
127
  */
105
128
  export const upgradeSwingset = kernelStorage => {
106
129
  const { kvStore } = kernelStorage;
107
- let modified = false;
108
130
  /** @type {RunQueueEvent[]} */
109
131
  const upgradeEvents = [];
110
- let vstring = kvStore.get('version');
111
- if (vstring === undefined) {
112
- vstring = '0';
113
- }
114
- let version = Number(vstring);
132
+ const vstring = kvStore.get('version');
133
+ const version = Number(vstring) || 0;
134
+ let newVersion;
115
135
 
116
136
  /**
117
137
  * @param {string} key
@@ -166,11 +186,7 @@ export const upgradeSwingset = kernelStorage => {
166
186
  assert(kvStore.has(oldDefaultReapIntervalKey));
167
187
  assert(!kvStore.has(DEFAULT_REAP_DIRT_THRESHOLD_KEY));
168
188
 
169
- /**
170
- * @typedef { import('../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
171
- */
172
-
173
- /** @type ReapDirtThreshold */
189
+ /** @type {ReapDirtThreshold} */
174
190
  const threshold = {
175
191
  deliveries: 'never',
176
192
  gcKrefs: 'never',
@@ -179,9 +195,7 @@ export const upgradeSwingset = kernelStorage => {
179
195
 
180
196
  const oldValue = getRequired(oldDefaultReapIntervalKey);
181
197
  if (oldValue !== 'never') {
182
- const value = Number.parseInt(oldValue, 10);
183
- assert.typeof(value, 'number');
184
- threshold.deliveries = value;
198
+ threshold.deliveries = mustParseInt(oldValue, oldDefaultReapIntervalKey);
185
199
  // if BOYD wasn't turned off entirely (eg
186
200
  // defaultReapInterval='never', which only happens in unit
187
201
  // tests), then pretend we wanted a gcKrefs= threshold all
@@ -196,22 +210,20 @@ export const upgradeSwingset = kernelStorage => {
196
210
 
197
211
  // now upgrade all vats
198
212
  for (const [_name, vatID] of getAllStaticVats(kvStore)) {
199
- upgradeVatV0toV1(kvStore, threshold, vatID);
213
+ upgradeVatV0toV1(kvStore, getRequired, threshold, vatID);
200
214
  }
201
215
  for (const vatID of getAllDynamicVats(getRequired)) {
202
- upgradeVatV0toV1(kvStore, threshold, vatID);
216
+ upgradeVatV0toV1(kvStore, getRequired, threshold, vatID);
203
217
  }
204
218
 
205
- modified = true;
206
- version = 1;
219
+ newVersion = 1;
207
220
  }
208
221
 
209
222
  if (version < 2) {
210
223
  // schema v2: add vats.terminated = []
211
224
  assert(!kvStore.has('vats.terminated'));
212
225
  kvStore.set('vats.terminated', JSON.stringify([]));
213
- modified = true;
214
- version = 2;
226
+ newVersion = 2;
215
227
  }
216
228
 
217
229
  if (version < 3) {
@@ -322,10 +334,11 @@ export const upgradeSwingset = kernelStorage => {
322
334
  }
323
335
 
324
336
  console.log(` - #9039 remediation complete, ${count} notifies to inject`);
325
- modified = true;
326
- version = 3;
337
+ newVersion = 3;
327
338
  }
328
339
 
340
+ const modified = newVersion !== undefined;
341
+
329
342
  if (upgradeEvents.length) {
330
343
  assert(modified);
331
344
  // stash until host calls controller.injectQueuedUpgradeEvents()
@@ -335,7 +348,7 @@ export const upgradeSwingset = kernelStorage => {
335
348
  }
336
349
 
337
350
  if (modified) {
338
- kvStore.set('version', `${version}`);
351
+ kvStore.set('version', `${newVersion}`);
339
352
  }
340
353
  return harden({ modified });
341
354
  };
@@ -1,5 +1,6 @@
1
1
  import { Fail } from '@endo/errors';
2
2
  import { Far } from '@endo/far';
3
+ import { deepCopyJsonable } from '@agoric/internal/src/js-utils.js';
3
4
 
4
5
  function sanitize(data) {
5
6
  if (data === undefined) {
@@ -8,7 +9,7 @@ function sanitize(data) {
8
9
  if (data instanceof Error) {
9
10
  data = data.stack;
10
11
  }
11
- return JSON.parse(JSON.stringify(data));
12
+ return deepCopyJsonable(data);
12
13
  }
13
14
 
14
15
  /**
@@ -32,7 +33,7 @@ export function buildRootDeviceNode(tools) {
32
33
 
33
34
  function inboundCallback(...args) {
34
35
  inboundHandler || Fail`inboundHandler not yet registered`;
35
- const safeArgs = JSON.parse(JSON.stringify(args));
36
+ const safeArgs = deepCopyJsonable(args);
36
37
  try {
37
38
  SO(inboundHandler).inbound(...harden(safeArgs));
38
39
  } catch (e) {
@@ -24,7 +24,6 @@ export function buildSerializationTools(syscall, deviceName) {
24
24
  send(method, args) {
25
25
  assert.typeof(method, 'string');
26
26
  assert(Array.isArray(args), args);
27
- // eslint-disable-next-line no-use-before-define
28
27
  const capdata = serialize([method, args]);
29
28
  syscall.sendOnly(slot, capdata);
30
29
  },
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ export {
8
8
  buildKernelBundles,
9
9
  loadBasedir,
10
10
  loadSwingsetConfigFile,
11
+ normalizeConfig,
11
12
  } from './controller/initializeSwingset.js';
12
13
  export { upgradeSwingset } from './controller/upgradeSwingset.js';
13
14
  export {
@@ -1128,7 +1128,6 @@ export default function buildKernel(
1128
1128
  } else if (message.type === 'changeVatOptions') {
1129
1129
  // prettier-ignore
1130
1130
  return `changeVatOptions ${message.vatID} options: ${JSON.stringify(message.options)}`;
1131
- // eslint-disable-next-line no-use-before-define
1132
1131
  } else if (gcMessages.includes(message.type)) {
1133
1132
  // prettier-ignore
1134
1133
  return `${message.type} ${message.vatID} ${message.krefs.map(e=>`@${e}`).join(' ')}`;
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-use-before-define */
2
1
  import { Nat, isNat } from '@endo/nat';
3
2
  import { assert, Fail } from '@endo/errors';
4
3
  import {
@@ -43,6 +42,7 @@ const enableKernelGC = true;
43
42
  * @typedef { import('../../types-external.js').SnapStore } SnapStore
44
43
  * @typedef { import('../../types-external.js').TranscriptStore } TranscriptStore
45
44
  * @typedef { import('../../types-external.js').VatKeeper } VatKeeper
45
+ * @typedef { Pick<VatKeeper, 'deleteCListEntry' | 'deleteSnapshots' | 'deleteTranscripts'> } VatUndertaker
46
46
  * @typedef { import('../../types-internal.js').InternalKernelOptions } InternalKernelOptions
47
47
  * @typedef { import('../../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
48
48
  * @import {PromiseRecord} from '../../types-internal.js';
@@ -422,6 +422,8 @@ export default function makeKernelKeeper(
422
422
  const ephemeral = harden({
423
423
  /** @type { Map<string, VatKeeper> } */
424
424
  vatKeepers: new Map(),
425
+ /** @type { Map<string, VatUndertaker> } */
426
+ vatUndertakers: new Map(),
425
427
  deviceKeepers: new Map(), // deviceID -> deviceKeeper
426
428
  });
427
429
 
@@ -1044,7 +1046,7 @@ export default function makeKernelKeeper(
1044
1046
  // first or vref first), and delete the other one in the same
1045
1047
  // call, so we don't wind up with half an entry.
1046
1048
 
1047
- const vatKeeper = provideVatKeeper(vatID);
1049
+ const undertaker = provideVatUndertaker(vatID);
1048
1050
  const clistPrefix = `${vatID}.c.`;
1049
1051
  const exportPrefix = `${clistPrefix}o+`;
1050
1052
  const importPrefix = `${clistPrefix}o-`;
@@ -1092,7 +1094,7 @@ export default function makeKernelKeeper(
1092
1094
  // drop+retire
1093
1095
  const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1094
1096
  const vref = stripPrefix(clistPrefix, k);
1095
- vatKeeper.deleteCListEntry(kref, vref);
1097
+ undertaker.deleteCListEntry(kref, vref);
1096
1098
  // that will also delete both db keys
1097
1099
  work.imports += 1;
1098
1100
  remaining -= 1;
@@ -1109,7 +1111,7 @@ export default function makeKernelKeeper(
1109
1111
  for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1110
1112
  const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1111
1113
  const vref = stripPrefix(clistPrefix, k);
1112
- vatKeeper.deleteCListEntry(kref, vref);
1114
+ undertaker.deleteCListEntry(kref, vref);
1113
1115
  // that will also delete both db keys
1114
1116
  work.promises += 1;
1115
1117
  remaining -= 1;
@@ -1131,7 +1133,7 @@ export default function makeKernelKeeper(
1131
1133
 
1132
1134
  // this will internally loop through 'budget' deletions
1133
1135
  remaining = budget.snapshots ?? budget.default;
1134
- const dsc = vatKeeper.deleteSnapshots(remaining);
1136
+ const dsc = undertaker.deleteSnapshots(remaining);
1135
1137
  work.snapshots += dsc.cleanups;
1136
1138
  remaining -= dsc.cleanups;
1137
1139
  if (remaining <= 0) {
@@ -1140,7 +1142,7 @@ export default function makeKernelKeeper(
1140
1142
 
1141
1143
  // same
1142
1144
  remaining = budget.transcripts ?? budget.default;
1143
- const dts = vatKeeper.deleteTranscripts(remaining);
1145
+ const dts = undertaker.deleteTranscripts(remaining);
1144
1146
  work.transcripts += dts.cleanups;
1145
1147
  remaining -= dts.cleanups;
1146
1148
  // last task, so increment cleanups, but dc.done is authoritative
@@ -1697,6 +1699,26 @@ export default function makeKernelKeeper(
1697
1699
  initializeVatState(kvStore, transcriptStore, vatID, source, options);
1698
1700
  }
1699
1701
 
1702
+ /** @type {import('./vatKeeper.js').VatKeeperPowers} */
1703
+ const vatKeeperPowers = {
1704
+ transcriptStore,
1705
+ kernelSlog,
1706
+ addKernelObject,
1707
+ addKernelPromiseForVat,
1708
+ kernelObjectExists,
1709
+ incrementRefCount,
1710
+ decrementRefCount,
1711
+ getObjectRefCount,
1712
+ setObjectRefCount,
1713
+ getReachableAndVatSlot,
1714
+ addMaybeFreeKref,
1715
+ incStat,
1716
+ decStat,
1717
+ getCrankNumber,
1718
+ scheduleReap,
1719
+ snapStore,
1720
+ };
1721
+
1700
1722
  function provideVatKeeper(vatID) {
1701
1723
  insistVatID(vatID);
1702
1724
  const found = ephemeral.vatKeepers.get(vatID);
@@ -1704,30 +1726,36 @@ export default function makeKernelKeeper(
1704
1726
  return found;
1705
1727
  }
1706
1728
  assert(kvStore.has(`${vatID}.o.nextID`), `${vatID} was not initialized`);
1707
- const vk = makeVatKeeper(
1708
- kvStore,
1709
- transcriptStore,
1710
- kernelSlog,
1711
- vatID,
1712
- addKernelObject,
1713
- addKernelPromiseForVat,
1714
- kernelObjectExists,
1715
- incrementRefCount,
1716
- decrementRefCount,
1717
- getObjectRefCount,
1718
- setObjectRefCount,
1719
- getReachableAndVatSlot,
1720
- addMaybeFreeKref,
1721
- incStat,
1722
- decStat,
1723
- getCrankNumber,
1724
- scheduleReap,
1725
- snapStore,
1726
- );
1729
+ const vk = makeVatKeeper(vatID, kvStore, vatKeeperPowers);
1727
1730
  ephemeral.vatKeepers.set(vatID, vk);
1728
1731
  return vk;
1729
1732
  }
1730
1733
 
1734
+ /**
1735
+ * Produce an attenuated vatKeeper for slow vat termination (and that
1736
+ * therefore does not insist on liveness, unlike provideVatKeeper).
1737
+ *
1738
+ * @param {string} vatID
1739
+ */
1740
+ function provideVatUndertaker(vatID) {
1741
+ insistVatID(vatID);
1742
+ const found = ephemeral.vatUndertakers.get(vatID);
1743
+ if (found !== undefined) {
1744
+ return found;
1745
+ }
1746
+ const { deleteCListEntry, deleteSnapshots, deleteTranscripts } =
1747
+ ephemeral.vatKeepers.get(vatID) ||
1748
+ makeVatKeeper(vatID, kvStore, vatKeeperPowers);
1749
+ /** @type {VatUndertaker} */
1750
+ const undertaker = harden({
1751
+ deleteCListEntry,
1752
+ deleteSnapshots,
1753
+ deleteTranscripts,
1754
+ });
1755
+ ephemeral.vatUndertakers.set(vatID, undertaker);
1756
+ return undertaker;
1757
+ }
1758
+
1731
1759
  function vatIsAlive(vatID) {
1732
1760
  insistVatID(vatID);
1733
1761
  return kvStore.has(`${vatID}.o.nextID`) && !terminatedVats.includes(vatID);
@@ -84,49 +84,53 @@ export function initializeVatState(
84
84
  }
85
85
 
86
86
  /**
87
- * Produce a vat keeper for a vat.
87
+ * @typedef {object} VatKeeperPowers
88
+ * @property {TranscriptStore} transcriptStore Accompanying transcript store, for the transcripts
89
+ * @property {*} kernelSlog
90
+ * @property {*} addKernelObject Kernel function to add a new object to the kernel's mapping tables.
91
+ * @property {*} addKernelPromiseForVat Kernel function to add a new promise to the kernel's mapping tables.
92
+ * @property {(kernelSlot: string) => boolean} kernelObjectExists
93
+ * @property {*} incrementRefCount
94
+ * @property {*} decrementRefCount
95
+ * @property {(kernelSlot: string) => {reachable: number, recognizable: number}} getObjectRefCount
96
+ * @property {(kernelSlot: string, o: { reachable: number, recognizable: number }) => void} setObjectRefCount
97
+ * @property {(vatID: string, kernelSlot: string) => {isReachable: boolean, vatSlot: string}} getReachableAndVatSlot
98
+ * @property {(kernelSlot: string) => void} addMaybeFreeKref
99
+ * @property {*} incStat
100
+ * @property {*} decStat
101
+ * @property {*} getCrankNumber
102
+ * @property {*} scheduleReap
103
+ * @property {SnapStore} snapStore
104
+ */
105
+
106
+ /**
107
+ * Produce a "vat keeper" for the kernel state of a vat.
88
108
  *
89
- * @param {KVStore} kvStore The keyValue store in which the persistent state will be kept
90
- * @param {TranscriptStore} transcriptStore Accompanying transcript store, for the transcripts
91
- * @param {*} kernelSlog
92
109
  * @param {string} vatID The vat ID string of the vat in question
93
- * @param {*} addKernelObject Kernel function to add a new object to the kernel's
94
- * mapping tables.
95
- * @param {*} addKernelPromiseForVat Kernel function to add a new promise to the
96
- * kernel's mapping tables.
97
- * @param {(kernelSlot: string) => boolean} kernelObjectExists
98
- * @param {*} incrementRefCount
99
- * @param {*} decrementRefCount
100
- * @param {(kernelSlot: string) => {reachable: number, recognizable: number}} getObjectRefCount
101
- * @param {(kernelSlot: string, o: { reachable: number, recognizable: number }) => void} setObjectRefCount
102
- * @param {(vatID: string, kernelSlot: string) => {isReachable: boolean, vatSlot: string}} getReachableAndVatSlot
103
- * @param {(kernelSlot: string) => void} addMaybeFreeKref
104
- * @param {*} incStat
105
- * @param {*} decStat
106
- * @param {*} getCrankNumber
107
- * @param {*} scheduleReap
108
- * @param {SnapStore} [snapStore]
109
- * returns an object to hold and access the kernel's state for the given vat
110
+ * @param {KVStore} kvStore The keyValue store in which the persistent state will be kept
111
+ * @param {VatKeeperPowers} powers
110
112
  */
111
113
  export function makeVatKeeper(
112
- kvStore,
113
- transcriptStore,
114
- kernelSlog,
115
114
  vatID,
116
- addKernelObject,
117
- addKernelPromiseForVat,
118
- kernelObjectExists,
119
- incrementRefCount,
120
- decrementRefCount,
121
- getObjectRefCount,
122
- setObjectRefCount,
123
- getReachableAndVatSlot,
124
- addMaybeFreeKref,
125
- incStat,
126
- decStat,
127
- getCrankNumber,
128
- scheduleReap,
129
- snapStore = undefined,
115
+ kvStore,
116
+ {
117
+ transcriptStore,
118
+ kernelSlog,
119
+ addKernelObject,
120
+ addKernelPromiseForVat,
121
+ kernelObjectExists,
122
+ incrementRefCount,
123
+ decrementRefCount,
124
+ getObjectRefCount,
125
+ setObjectRefCount,
126
+ getReachableAndVatSlot,
127
+ addMaybeFreeKref,
128
+ incStat,
129
+ decStat,
130
+ getCrankNumber,
131
+ scheduleReap,
132
+ snapStore,
133
+ },
130
134
  ) {
131
135
  insistVatID(vatID);
132
136
 
@@ -97,6 +97,7 @@ export function makeSyscallSimulator(
97
97
  deliveryNum,
98
98
  transcriptEntry,
99
99
  ) {
100
+ const context = `anachrophobia in ${vatID} delivery d${deliveryNum}`;
100
101
  const syscallsExpected = [...transcriptEntry.sc]; // copy
101
102
  const syscallsMade = [];
102
103
  // syscallStatus's length will be max(syscallsExpected,
@@ -107,31 +108,36 @@ export function makeSyscallSimulator(
107
108
  let replayError; // sticky
108
109
 
109
110
  const explain = () => {
110
- console.log(`anachrophobia strikes ${vatID} on delivery ${deliveryNum}`);
111
+ console.log(
112
+ `anachrophobia strikes ${vatID} delivery d${deliveryNum} syscalls`,
113
+ );
111
114
  for (const [idx, status] of syscallStatus.entries()) {
112
115
  const expected = syscallsExpected[idx];
113
116
  const got = syscallsMade[idx];
114
117
  switch (status) {
115
118
  case 'ok': {
116
- console.log(`sc[${idx}]: ok: ${djson.stringify(got)}`);
119
+ console.log(`sc${idx}: ok: ${djson.stringify(got)}`);
117
120
  break;
118
121
  }
119
122
  case 'wrong': {
120
- console.log(`sc[${idx}]: wrong`);
121
- console.log(` expected: ${djson.stringify(expected.s)}`);
122
- console.log(` got : ${djson.stringify(got)}`);
123
+ console.log(
124
+ `
125
+ sc${idx}: WRONG
126
+ expected: ${djson.stringify(expected.s)}
127
+ got : ${djson.stringify(got)}`.trimStart(),
128
+ );
123
129
  break;
124
130
  }
125
131
  case 'extra': {
126
- console.log(`sc[${idx}]: extra: ${djson.stringify(got)}`);
132
+ console.log(`sc${idx}: EXTRA: ${djson.stringify(got)}`);
127
133
  break;
128
134
  }
129
135
  case 'missing': {
130
- console.log(`sc[${idx}]: missing: ${djson.stringify(expected.s)}`);
136
+ console.log(`sc${idx}: MISSING: ${djson.stringify(expected.s)}`);
131
137
  break;
132
138
  }
133
139
  default:
134
- Fail`bad ${status}`;
140
+ Fail`sc${idx}: bad status ${status}`;
135
141
  }
136
142
  }
137
143
  };
@@ -140,16 +146,16 @@ export function makeSyscallSimulator(
140
146
  // slog entries have no kernel-translated kso/ksr
141
147
  const finish = kernelSlog.syscall(vatID, undefined, vso);
142
148
  const expected = syscallsExpected[syscallsMade.length];
143
- syscallsMade.push(vso);
149
+ const idx = syscallsMade.push(vso) - 1;
144
150
  if (!expected) {
145
151
  syscallStatus.push('extra');
146
- const error = Error(`anachrophobia in ${vatID}: extra syscall`);
152
+ const error = Error(`${context}: extra syscall at index sc${idx}`);
147
153
  replayError ||= error;
148
154
  throw error;
149
155
  }
150
156
  if (!syscallsAreIdentical(expected.s, vso)) {
151
157
  syscallStatus.push('wrong');
152
- const error = Error(`anachrophobia in ${vatID}: wrong syscall`);
158
+ const error = Error(`${context}: wrong syscall at index sc${idx}`);
153
159
  replayError ||= error;
154
160
  throw error;
155
161
  }
@@ -159,12 +165,14 @@ export function makeSyscallSimulator(
159
165
  };
160
166
 
161
167
  const finishSimulation = () => {
162
- if (syscallsMade.length < syscallsExpected.length) {
163
- const missing = syscallsExpected.length - syscallsMade.length;
168
+ const missing = syscallsExpected.length - syscallsMade.length;
169
+ if (missing > 0) {
164
170
  for (let i = 0; i < missing; i += 1) {
165
171
  syscallStatus.push('missing');
166
172
  }
167
- const error = Error(`anachrophobia in ${vatID}: missing syscalls`);
173
+ const error = Error(
174
+ `${context}: missing ${missing} syscall(s) at index sc${syscallsMade.length}`,
175
+ );
168
176
  replayError ||= error;
169
177
  }
170
178
 
@@ -389,7 +397,6 @@ export function makeVatWarehouse({
389
397
  // entriesReplayed, // retval of replayTranscript() above
390
398
  // );
391
399
  ephemeral.vats.set(vatID, result);
392
- // eslint-disable-next-line no-use-before-define
393
400
  await applyAvailabilityPolicy(vatID);
394
401
  return result;
395
402
  }
@@ -596,7 +603,6 @@ export function makeVatWarehouse({
596
603
  //
597
604
  /** @type { KernelDeliveryObject } */
598
605
  const kd = harden(['bringOutYourDead']);
599
- // eslint-disable-next-line no-use-before-define
600
606
  const vd = kernelDeliveryToVatDelivery(vatID, kd);
601
607
  const vs = kernelSlog.provideVatSlogger(vatID).vatSlog;
602
608
  await deliverToVat(vatID, kd, vd, vs);
@@ -36,6 +36,7 @@ workerLog(`supervisor started`);
36
36
 
37
37
  function makeNetstringReader({ fd, encoding }) {
38
38
  const input = Buffer.alloc(32 * 1024);
39
+ /** @type {Buffer<ArrayBufferLike>} */
39
40
  let buffered = Buffer.alloc(0);
40
41
  let decoded = [];
41
42
 
package/src/typeGuards.js CHANGED
@@ -10,10 +10,11 @@ export const ManagerType = M.or(
10
10
 
11
11
  const Bundle = M.splitRecord({ moduleType: M.string() });
12
12
 
13
- const SwingsetConfigOptions = harden({
13
+ const SwingsetConfigOptions = {
14
14
  creationOptions: M.splitRecord({}, { critical: M.boolean() }),
15
15
  parameters: M.recordOf(M.string(), M.any()),
16
- });
16
+ };
17
+ harden(SwingsetConfigOptions);
17
18
 
18
19
  const SwingSetConfigProperties = M.or(
19
20
  M.splitRecord({ sourceSpec: M.string() }, SwingsetConfigOptions),
@@ -1,5 +1,3 @@
1
- /* eslint-disable no-use-before-define */
2
-
3
1
  import { assert, Fail } from '@endo/errors';
4
2
  import { kser } from '@agoric/kmarshal';
5
3
  import { parseLocalSlot, insistLocalType } from './parseLocalSlots.js';
@@ -150,7 +150,6 @@ export function makeState(syscall) {
150
150
  store.set('r.nextID', '1');
151
151
  store.set('initialized', 'true');
152
152
  if (controller) {
153
- // eslint-disable-next-line no-use-before-define
154
153
  addMetaObject(controller);
155
154
  cdebug(`comms controller is ${controller}`);
156
155
  }
@@ -393,7 +392,6 @@ export function makeState(syscall) {
393
392
  // the object is unreachable
394
393
 
395
394
  const { owner, isReachable, isRecognizable } =
396
- // eslint-disable-next-line no-use-before-define
397
395
  getOwnerAndStatus(lref);
398
396
  if (isReachable) {
399
397
  // but the exporter doesn't realize it yet, so schedule a
@@ -558,7 +556,6 @@ export function makeState(syscall) {
558
556
  isReachable = isReachableByKernel(lref);
559
557
  isRecognizable = !!mapToKernel(lref);
560
558
  } else {
561
- // eslint-disable-next-line no-use-before-define
562
559
  const remote = getRemote(owner);
563
560
  isReachable = remote.isReachable(lref);
564
561
  isRecognizable = !!remote.mapToRemote(lref);
@@ -794,7 +791,6 @@ export function makeState(syscall) {
794
791
  insistPromiseIsUnresolved,
795
792
  markPromiseAsResolved,
796
793
 
797
- // eslint-disable-next-line no-use-before-define
798
794
  getRemote,
799
795
  addRemote,
800
796
  getRemoteIDForName,
@@ -1,5 +1,3 @@
1
- /* eslint-disable no-use-before-define */
2
-
3
1
  import { assert } from '@endo/errors';
4
2
  import { Far, E, passStyleOf } from '@endo/far';
5
3
  import { makePromiseKit } from '@endo/promise-kit';
@@ -100,7 +100,6 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) {
100
100
  // getNotifier: ({ state }) => state.notifier, // XXX RESTORE
101
101
  getNotifier: ({ _self }) => Fail`not implemented, see #7234`, // XXX TEMP
102
102
  },
103
- // eslint-disable-next-line no-use-before-define
104
103
  { finish: finishMeter },
105
104
  );
106
105
 
@@ -120,18 +119,15 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) {
120
119
  // getNotifier: ({ state }) => state.notifier, // will never fire // XXX RESTORE
121
120
  getNotifier: ({ _self }) => Fail`not implemented, see #7234`, // XXX TEMP
122
121
  },
123
- // eslint-disable-next-line no-use-before-define
124
122
  { finish: finishMeter },
125
123
  );
126
124
 
127
125
  function finishMeter({ state, self }) {
128
- // eslint-disable-next-line no-use-before-define
129
126
  meterByID.init(
130
127
  state.meterID,
131
128
  // harden({ meter: self, updater: state.updater }), // XXX RESTORE
132
129
  harden({ meter: self }), // XXX TEMP
133
130
  );
134
- // eslint-disable-next-line no-use-before-define
135
131
  meterIDByMeter.set(self, state.meterID);
136
132
  }
137
133
 
@@ -1,8 +1,6 @@
1
1
  import { makeMarshal } from '@endo/marshal';
2
2
  import { Far } from '@endo/far';
3
3
 
4
- /* eslint-disable no-use-before-define */
5
-
6
4
  function fakeSTV(slot, iface = 'Remotable') {
7
5
  return Far(iface, {
8
6
  getSlot: () => slot,
@@ -11,7 +11,6 @@ export function buildRootObject() {
11
11
  async function runTests(phase) {
12
12
  testLog = [];
13
13
  doneP = makePromiseKit();
14
- // eslint-disable-next-line no-use-before-define
15
14
  await E(testVatRoot).runTests(self, phase);
16
15
  await doneP.promise;
17
16
  return testLog;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * @file Source code for a bootstrap vat that runs blockchain behaviors (such as
3
+ * bridge vat integration) and exposes reflective methods for use in testing.
4
+ *
5
+ * TODO: Build from ./vat-puppet.js makeReflectionMethods
6
+ * and share code with packages/vats/tools/vat-reflective-chain-bootstrap.js
7
+ * (which basically extends this for better [mock] blockchain integration).
8
+ */
9
+
1
10
  import { Fail, q } from '@endo/errors';
2
11
  import { objectMap } from '@agoric/internal';
3
12
  import { Far, E } from '@endo/far';
@@ -2,12 +2,18 @@ import { Fail, q } from '@endo/errors';
2
2
  import { kunser } from '@agoric/kmarshal';
3
3
  import { makeQueue } from '@endo/stream';
4
4
 
5
- /** @import { ERef } from '@endo/far' */
5
+ /**
6
+ * @import { ERef } from '@endo/far'
7
+ * @import { RunPolicy } from '../src/types-external.js'
8
+ */
9
+
10
+ /** @typedef {{ provideRunPolicy: () => RunPolicy | undefined }} RunHarness */
6
11
 
7
12
  /**
8
13
  * @param {import('../src/controller/controller.js').SwingsetController} controller
14
+ * @param {RunHarness} [harness]
9
15
  */
10
- export const makeRunUtils = controller => {
16
+ export const makeRunUtils = (controller, harness) => {
11
17
  const mutex = makeQueue();
12
18
  const logRunFailure = reason =>
13
19
  console.log('controller.run() failure', reason);
@@ -17,7 +23,7 @@ export const makeRunUtils = controller => {
17
23
  * Wait for exclusive access to the controller, then before relinquishing that access,
18
24
  * enqueue and process a delivery and return the result.
19
25
  *
20
- * @param {() => ERef<void | ReturnType<controller['queueToVatObject']>>} deliveryThunk
26
+ * @param {() => ERef<void | ReturnType<typeof controller['queueToVatObject']>>} deliveryThunk
21
27
  * function for enqueueing a delivery and returning the result kpid (if any)
22
28
  * @param {boolean} [voidResult] whether to ignore the result
23
29
  * @returns {Promise<any>}
@@ -25,7 +31,8 @@ export const makeRunUtils = controller => {
25
31
  const queueAndRun = async (deliveryThunk, voidResult = false) => {
26
32
  await mutex.get();
27
33
  const kpid = await deliveryThunk();
28
- const runResultP = controller.run();
34
+ const runPolicy = harness && harness.provideRunPolicy();
35
+ const runResultP = controller.run(runPolicy);
29
36
  mutex.put(runResultP.catch(logRunFailure));
30
37
  await runResultP;
31
38
 
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @file Source code for a vat that exposes reflective methods for use in
3
+ * testing.
4
+ */
5
+
6
+ import { Fail, q } from '@endo/errors';
7
+ import { Far, E } from '@endo/far';
8
+ import { makePromiseKit } from '@endo/promise-kit';
9
+ import { objectMap } from '@agoric/internal';
10
+
11
+ /**
12
+ * @callback Die
13
+ * @param {unknown} completion
14
+ * @param {[target: unknown, method: string, ...args: unknown[]]} [finalSend]
15
+ */
16
+
17
+ /**
18
+ * @typedef {Array<[name: string, ...args: unknown[]]>} CallLog
19
+ */
20
+
21
+ /**
22
+ * @param {import('@agoric/swingset-vat').VatPowers} vatPowers
23
+ * @param {import('@agoric/vat-data').Baggage} baggage
24
+ */
25
+ export const makeReflectionMethods = (vatPowers, baggage) => {
26
+ let baggageHoldCount = 0;
27
+ /** @type {Map<object, CallLog>} */
28
+ const callLogsByRemotable = new Map();
29
+ const heldInHeap = [];
30
+ const send = (target, method, ...args) => E(target)[method](...args);
31
+ const makeSpy = (value, name, callLog) => {
32
+ const spyName = `get ${name}`;
33
+ const spy = {
34
+ [spyName](...args) {
35
+ callLog.push([name, ...args]);
36
+ return value;
37
+ },
38
+ }[spyName];
39
+ return spy;
40
+ };
41
+
42
+ return {
43
+ /** @type {Die} */
44
+ dieHappy: (completion, finalSend) => {
45
+ vatPowers.exitVat(completion);
46
+ if (finalSend) send(...finalSend);
47
+ },
48
+
49
+ /** @type {Die} */
50
+ dieSad: (reason, finalSend) => {
51
+ vatPowers.exitVatWithFailure(/** @type {Error} */ (reason));
52
+ if (finalSend) send(...finalSend);
53
+ },
54
+
55
+ holdInBaggage: (...values) => {
56
+ for (const value of values) {
57
+ baggage.init(`held-${baggageHoldCount}`, value);
58
+ baggageHoldCount += 1;
59
+ }
60
+ return baggageHoldCount;
61
+ },
62
+
63
+ holdInHeap: (...values) => heldInHeap.push(...values),
64
+
65
+ makePromiseKit: () => {
66
+ const { promise, ...resolverMethods } = makePromiseKit();
67
+ void promise.catch(() => {});
68
+ const resolver = Far('resolver', resolverMethods);
69
+ return harden({ promise, resolver });
70
+ },
71
+
72
+ makeUnsettledPromise() {
73
+ const { promise } = makePromiseKit();
74
+ void promise.catch(() => {});
75
+ return promise;
76
+ },
77
+
78
+ /**
79
+ * Returns a remotable with methods that return provided values. Invocations
80
+ * of those methods and their arguments are captured for later retrieval by
81
+ * `getCallLogForRemotable`.
82
+ *
83
+ * @param {string} [label]
84
+ * @param {Record<string, any>} [fields]
85
+ */
86
+ makeRemotable: (label = 'Remotable', fields = {}) => {
87
+ /** @type {CallLog} */
88
+ const callLog = [];
89
+ const methods = objectMap(fields, (value, name) =>
90
+ makeSpy(value, name, callLog),
91
+ );
92
+ const remotable = Far(label, { ...methods });
93
+ callLogsByRemotable.set(remotable, callLog);
94
+ return remotable;
95
+ },
96
+
97
+ /**
98
+ * @param {object} remotable
99
+ * @returns {CallLog}
100
+ */
101
+ getCallLogForRemotable: remotable =>
102
+ callLogsByRemotable.get(remotable) ||
103
+ Fail`unknown remotable ${q(remotable)}`,
104
+ };
105
+ };
106
+ harden(makeReflectionMethods);
107
+
108
+ export function buildRootObject(vatPowers, _vatParameters, baggage) {
109
+ const methods = makeReflectionMethods(vatPowers, baggage);
110
+ return Far('root', methods);
111
+ }