@agoric/swingset-vat 0.32.3-dev-02c06c4.0 → 0.32.3-dev-fd68b6a.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.32.3-dev-02c06c4.0+02c06c4",
3
+ "version": "0.32.3-dev-fd68b6a.0+fd68b6a",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,15 +27,15 @@
27
27
  "@types/yargs-parser": "^21.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@agoric/internal": "0.3.3-dev-02c06c4.0+02c06c4",
31
- "@agoric/kmarshal": "0.1.1-dev-02c06c4.0+02c06c4",
32
- "@agoric/store": "0.9.3-dev-02c06c4.0+02c06c4",
33
- "@agoric/swing-store": "0.9.2-dev-02c06c4.0+02c06c4",
34
- "@agoric/swingset-liveslots": "0.10.3-dev-02c06c4.0+02c06c4",
35
- "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-02c06c4.0+02c06c4",
36
- "@agoric/time": "0.3.3-dev-02c06c4.0+02c06c4",
37
- "@agoric/vat-data": "0.5.3-dev-02c06c4.0+02c06c4",
38
- "@agoric/xsnap-lockdown": "0.14.1-dev-02c06c4.0+02c06c4",
30
+ "@agoric/internal": "0.3.3-dev-fd68b6a.0+fd68b6a",
31
+ "@agoric/kmarshal": "0.1.1-dev-fd68b6a.0+fd68b6a",
32
+ "@agoric/store": "0.9.3-dev-fd68b6a.0+fd68b6a",
33
+ "@agoric/swing-store": "0.9.2-dev-fd68b6a.0+fd68b6a",
34
+ "@agoric/swingset-liveslots": "0.10.3-dev-fd68b6a.0+fd68b6a",
35
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-fd68b6a.0+fd68b6a",
36
+ "@agoric/time": "0.3.3-dev-fd68b6a.0+fd68b6a",
37
+ "@agoric/vat-data": "0.5.3-dev-fd68b6a.0+fd68b6a",
38
+ "@agoric/xsnap-lockdown": "0.14.1-dev-fd68b6a.0+fd68b6a",
39
39
  "@endo/base64": "^1.0.8",
40
40
  "@endo/bundle-source": "^3.4.2",
41
41
  "@endo/captp": "^4.4.2",
@@ -103,5 +103,5 @@
103
103
  "typeCoverage": {
104
104
  "atLeast": 76.19
105
105
  },
106
- "gitHead": "02c06c489677233c0daa112284d16c314aff97c6"
106
+ "gitHead": "fd68b6ac1620d3ac9b6e98eeee7a507c3ccb3d79"
107
107
  }
@@ -2,7 +2,7 @@
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
 
5
- import { assert, Fail } from '@endo/errors';
5
+ import { assert, b, Fail } from '@endo/errors';
6
6
  import { makeTracer } from '@agoric/internal';
7
7
  import { mustMatch } from '@agoric/store';
8
8
  import bundleSource from '@endo/bundle-source';
@@ -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}`);
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 {
@@ -43,6 +43,7 @@ const enableKernelGC = true;
43
43
  * @typedef { import('../../types-external.js').SnapStore } SnapStore
44
44
  * @typedef { import('../../types-external.js').TranscriptStore } TranscriptStore
45
45
  * @typedef { import('../../types-external.js').VatKeeper } VatKeeper
46
+ * @typedef { Pick<VatKeeper, 'deleteCListEntry' | 'deleteSnapshots' | 'deleteTranscripts'> } VatUndertaker
46
47
  * @typedef { import('../../types-internal.js').InternalKernelOptions } InternalKernelOptions
47
48
  * @typedef { import('../../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
48
49
  * @import {PromiseRecord} from '../../types-internal.js';
@@ -422,6 +423,8 @@ export default function makeKernelKeeper(
422
423
  const ephemeral = harden({
423
424
  /** @type { Map<string, VatKeeper> } */
424
425
  vatKeepers: new Map(),
426
+ /** @type { Map<string, VatUndertaker> } */
427
+ vatUndertakers: new Map(),
425
428
  deviceKeepers: new Map(), // deviceID -> deviceKeeper
426
429
  });
427
430
 
@@ -1044,7 +1047,7 @@ export default function makeKernelKeeper(
1044
1047
  // first or vref first), and delete the other one in the same
1045
1048
  // call, so we don't wind up with half an entry.
1046
1049
 
1047
- const vatKeeper = provideVatKeeper(vatID);
1050
+ const undertaker = provideVatUndertaker(vatID);
1048
1051
  const clistPrefix = `${vatID}.c.`;
1049
1052
  const exportPrefix = `${clistPrefix}o+`;
1050
1053
  const importPrefix = `${clistPrefix}o-`;
@@ -1092,7 +1095,7 @@ export default function makeKernelKeeper(
1092
1095
  // drop+retire
1093
1096
  const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1094
1097
  const vref = stripPrefix(clistPrefix, k);
1095
- vatKeeper.deleteCListEntry(kref, vref);
1098
+ undertaker.deleteCListEntry(kref, vref);
1096
1099
  // that will also delete both db keys
1097
1100
  work.imports += 1;
1098
1101
  remaining -= 1;
@@ -1109,7 +1112,7 @@ export default function makeKernelKeeper(
1109
1112
  for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1110
1113
  const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1111
1114
  const vref = stripPrefix(clistPrefix, k);
1112
- vatKeeper.deleteCListEntry(kref, vref);
1115
+ undertaker.deleteCListEntry(kref, vref);
1113
1116
  // that will also delete both db keys
1114
1117
  work.promises += 1;
1115
1118
  remaining -= 1;
@@ -1131,7 +1134,7 @@ export default function makeKernelKeeper(
1131
1134
 
1132
1135
  // this will internally loop through 'budget' deletions
1133
1136
  remaining = budget.snapshots ?? budget.default;
1134
- const dsc = vatKeeper.deleteSnapshots(remaining);
1137
+ const dsc = undertaker.deleteSnapshots(remaining);
1135
1138
  work.snapshots += dsc.cleanups;
1136
1139
  remaining -= dsc.cleanups;
1137
1140
  if (remaining <= 0) {
@@ -1140,7 +1143,7 @@ export default function makeKernelKeeper(
1140
1143
 
1141
1144
  // same
1142
1145
  remaining = budget.transcripts ?? budget.default;
1143
- const dts = vatKeeper.deleteTranscripts(remaining);
1146
+ const dts = undertaker.deleteTranscripts(remaining);
1144
1147
  work.transcripts += dts.cleanups;
1145
1148
  remaining -= dts.cleanups;
1146
1149
  // last task, so increment cleanups, but dc.done is authoritative
@@ -1697,6 +1700,26 @@ export default function makeKernelKeeper(
1697
1700
  initializeVatState(kvStore, transcriptStore, vatID, source, options);
1698
1701
  }
1699
1702
 
1703
+ /** @type {import('./vatKeeper.js').VatKeeperPowers} */
1704
+ const vatKeeperPowers = {
1705
+ transcriptStore,
1706
+ kernelSlog,
1707
+ addKernelObject,
1708
+ addKernelPromiseForVat,
1709
+ kernelObjectExists,
1710
+ incrementRefCount,
1711
+ decrementRefCount,
1712
+ getObjectRefCount,
1713
+ setObjectRefCount,
1714
+ getReachableAndVatSlot,
1715
+ addMaybeFreeKref,
1716
+ incStat,
1717
+ decStat,
1718
+ getCrankNumber,
1719
+ scheduleReap,
1720
+ snapStore,
1721
+ };
1722
+
1700
1723
  function provideVatKeeper(vatID) {
1701
1724
  insistVatID(vatID);
1702
1725
  const found = ephemeral.vatKeepers.get(vatID);
@@ -1704,30 +1727,36 @@ export default function makeKernelKeeper(
1704
1727
  return found;
1705
1728
  }
1706
1729
  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
- );
1730
+ const vk = makeVatKeeper(vatID, kvStore, vatKeeperPowers);
1727
1731
  ephemeral.vatKeepers.set(vatID, vk);
1728
1732
  return vk;
1729
1733
  }
1730
1734
 
1735
+ /**
1736
+ * Produce an attenuated vatKeeper for slow vat termination (and that
1737
+ * therefore does not insist on liveness, unlike provideVatKeeper).
1738
+ *
1739
+ * @param {string} vatID
1740
+ */
1741
+ function provideVatUndertaker(vatID) {
1742
+ insistVatID(vatID);
1743
+ const found = ephemeral.vatUndertakers.get(vatID);
1744
+ if (found !== undefined) {
1745
+ return found;
1746
+ }
1747
+ const { deleteCListEntry, deleteSnapshots, deleteTranscripts } =
1748
+ ephemeral.vatKeepers.get(vatID) ||
1749
+ makeVatKeeper(vatID, kvStore, vatKeeperPowers);
1750
+ /** @type {VatUndertaker} */
1751
+ const undertaker = harden({
1752
+ deleteCListEntry,
1753
+ deleteSnapshots,
1754
+ deleteTranscripts,
1755
+ });
1756
+ ephemeral.vatUndertakers.set(vatID, undertaker);
1757
+ return undertaker;
1758
+ }
1759
+
1731
1760
  function vatIsAlive(vatID) {
1732
1761
  insistVatID(vatID);
1733
1762
  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
 
@@ -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';
@@ -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
+ }