@agoric/swingset-vat 0.32.3-dev-9bb987f.0.9bb987f → 0.32.3-dev-8d12ead.0.8d12ead

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-9bb987f.0.9bb987f",
3
+ "version": "0.32.3-dev-8d12ead.0.8d12ead",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -22,7 +22,7 @@
22
22
  "lint:eslint": "yarn run -T eslint ."
23
23
  },
24
24
  "devDependencies": {
25
- "@agoric/xsnap": "0.14.3-dev-9bb987f.0.9bb987f",
25
+ "@agoric/xsnap": "0.14.3-dev-8d12ead.0.8d12ead",
26
26
  "@types/better-sqlite3": "^7.6.13",
27
27
  "@types/microtime": "^2.1.0",
28
28
  "@types/tmp": "^0.2.0",
@@ -30,15 +30,15 @@
30
30
  "ava": "^5.3.0"
31
31
  },
32
32
  "dependencies": {
33
- "@agoric/internal": "0.3.3-dev-9bb987f.0.9bb987f",
34
- "@agoric/kmarshal": "0.1.1-dev-9bb987f.0.9bb987f",
35
- "@agoric/store": "0.9.3-dev-9bb987f.0.9bb987f",
36
- "@agoric/swing-store": "0.9.2-dev-9bb987f.0.9bb987f",
37
- "@agoric/swingset-liveslots": "0.10.3-dev-9bb987f.0.9bb987f",
38
- "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-9bb987f.0.9bb987f",
39
- "@agoric/time": "0.3.3-dev-9bb987f.0.9bb987f",
40
- "@agoric/vat-data": "0.5.3-dev-9bb987f.0.9bb987f",
41
- "@agoric/xsnap-lockdown": "0.14.1-dev-9bb987f.0.9bb987f",
33
+ "@agoric/internal": "0.3.3-dev-8d12ead.0.8d12ead",
34
+ "@agoric/kmarshal": "0.1.1-dev-8d12ead.0.8d12ead",
35
+ "@agoric/store": "0.9.3-dev-8d12ead.0.8d12ead",
36
+ "@agoric/swing-store": "0.9.2-dev-8d12ead.0.8d12ead",
37
+ "@agoric/swingset-liveslots": "0.10.3-dev-8d12ead.0.8d12ead",
38
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-8d12ead.0.8d12ead",
39
+ "@agoric/time": "0.3.3-dev-8d12ead.0.8d12ead",
40
+ "@agoric/vat-data": "0.5.3-dev-8d12ead.0.8d12ead",
41
+ "@agoric/xsnap-lockdown": "0.14.1-dev-8d12ead.0.8d12ead",
42
42
  "@endo/base64": "^1.0.12",
43
43
  "@endo/bundle-source": "^4.1.2",
44
44
  "@endo/captp": "^4.4.8",
@@ -105,5 +105,5 @@
105
105
  "typeCoverage": {
106
106
  "atLeast": 76.59
107
107
  },
108
- "gitHead": "9bb987f67986eb98d04d64f5adedafc0f2d10271"
108
+ "gitHead": "8d12ead740e840027e28a0b6be3bf8f125adc799"
109
109
  }
@@ -43,8 +43,41 @@ import { makeVatAdminHooks } from './vat-admin-hooks.js';
43
43
  /**
44
44
  * @import {MeterConsumption, VatDeliveryObject, VatDeliveryResult, VatSyscallObject, VatSyscallResult} from '@agoric/swingset-liveslots';
45
45
  * @import {PolicyInputCleanupCounts} from '../types-external.js';
46
+ * @import {
47
+ * VatID,
48
+ * InternalDynamicVatOptions,
49
+ * RunQueueEvent,
50
+ * RunQueueEventNotify,
51
+ * RunQueueEventSend,
52
+ * RunQueueEventCreateVat,
53
+ * RunQueueEventUpgradeVat,
54
+ * RunQueueEventChangeVatOptions,
55
+ * RunQueueEventStartVat,
56
+ * RunQueueEventDropExports,
57
+ * RunQueueEventRetireExports,
58
+ * RunQueueEventRetireImports,
59
+ * RunQueueEventNegatedGCAction,
60
+ * RunQueueEventBringOutYourDead,
61
+ * RunQueueEventCleanupTerminatedVat,
62
+ * } from '../types-internal.js';
46
63
  */
47
64
 
65
+ /** @typedef {typeof gcMessageTypes[number]} GcMessageType */
66
+ const gcMessageTypes = /** @type {const} */ ([
67
+ 'bringOutYourDead',
68
+ 'dropExports',
69
+ 'retireExports',
70
+ 'retireImports',
71
+ ]);
72
+
73
+ /** @typedef {Extract<RunQueueEvent, { type: GcMessageType }>} GcMessage */
74
+
75
+ /** @type {Set<RunQueueEvent['type']>} */
76
+ const gcMessageTypesSet = new Set(gcMessageTypes);
77
+
78
+ /** @type {(message: RunQueueEvent) => message is GcMessage} */
79
+ const isGcMessage = message => gcMessageTypesSet.has(message.type);
80
+
48
81
  function abbreviateReplacer(_, arg) {
49
82
  if (typeof arg === 'bigint') {
50
83
  return Number(arg);
@@ -1135,33 +1168,43 @@ export default function buildKernel(
1135
1168
  return results;
1136
1169
  }
1137
1170
 
1171
+ /** @type {(message: RunQueueEvent) => string} */
1138
1172
  function legibilizeMessage(message) {
1139
- if (message.type === 'send') {
1140
- const msg = message.msg;
1141
- const [method, argList] = legibilizeMessageArgs(msg.methargs);
1142
- const result = msg.result ? msg.result : 'null';
1143
- return `@${message.target} <- ${method}(${argList}) : @${result}`;
1144
- } else if (message.type === 'notify') {
1145
- return `notify(vatID: ${message.vatID}, kpid: @${message.kpid})`;
1146
- } else if (message.type === 'create-vat') {
1147
- // prettier-ignore
1148
- return `create-vat ${message.vatID} opts: ${JSON.stringify(message.dynamicOptions)} vatParameters: ${JSON.stringify(message.vatParameters)}`;
1149
- } else if (message.type === 'upgrade-vat') {
1150
- // prettier-ignore
1151
- return `upgrade-vat ${message.vatID} upgradeID: ${message.upgradeID} vatParameters: ${JSON.stringify(message.vatParameters)}`;
1152
- } else if (message.type === 'changeVatOptions') {
1153
- // prettier-ignore
1154
- return `changeVatOptions ${message.vatID} options: ${JSON.stringify(message.options)}`;
1155
- } else if (gcMessages.includes(message.type)) {
1156
- // prettier-ignore
1157
- return `${message.type} ${message.vatID} ${message.krefs.map(e=>`@${e}`).join(' ')}`;
1158
- } else if (
1159
- message.type === 'bringOutYourDead' ||
1160
- message.type === 'startVat'
1161
- ) {
1162
- return `${message.type} ${message.vatID}`;
1163
- } else {
1164
- return `unknown message type ${message.type}`;
1173
+ switch (message.type) {
1174
+ case 'send': {
1175
+ const msg = message.msg;
1176
+ const [method, argList] = legibilizeMessageArgs(msg.methargs);
1177
+ const result = msg.result ? msg.result : 'null';
1178
+ return `@${message.target} <- ${method}(${argList}) : @${result}`;
1179
+ }
1180
+ case 'notify':
1181
+ return `notify(vatID: ${message.vatID}, kpid: @${message.kpid})`;
1182
+ case 'create-vat': {
1183
+ const optsStr = JSON.stringify(message.dynamicOptions);
1184
+ const vatParametersStr = JSON.stringify(message.vatParameters);
1185
+ // prettier-ignore
1186
+ return `create-vat ${message.vatID} opts: ${optsStr} vatParameters: ${vatParametersStr}`;
1187
+ }
1188
+ case 'upgrade-vat': {
1189
+ const vatParametersStr = JSON.stringify(message.vatParameters);
1190
+ // prettier-ignore
1191
+ return `upgrade-vat ${message.vatID} upgradeID: ${message.upgradeID} vatParameters: ${vatParametersStr}`;
1192
+ }
1193
+ case 'changeVatOptions':
1194
+ // prettier-ignore
1195
+ return `changeVatOptions ${message.vatID} options: ${JSON.stringify(message.options)}`;
1196
+ case 'startVat':
1197
+ case 'cleanup-terminated-vat':
1198
+ case 'negated-gc-action':
1199
+ case 'bringOutYourDead':
1200
+ return `${message.type} ${message.vatID}`;
1201
+ default:
1202
+ if (isGcMessage(message)) {
1203
+ const krefs = message.krefs.map(e => `@${e}`).join(' ');
1204
+ return `${message.type} ${message.vatID} ${krefs}`;
1205
+ }
1206
+ // @ts-expect-error unreachable
1207
+ return `unknown message type ${message.type}`;
1165
1208
  }
1166
1209
  }
1167
1210
 
@@ -1264,27 +1307,6 @@ export default function buildKernel(
1264
1307
  kernelKeeper.decrementRefCount(message.kpid, `deq|notify`);
1265
1308
  }
1266
1309
 
1267
- const gcMessages = ['dropExports', 'retireExports', 'retireImports'];
1268
-
1269
- /**
1270
- * @typedef { import('../types-internal.js').VatID } VatID
1271
- * @typedef { import('../types-internal.js').InternalDynamicVatOptions } InternalDynamicVatOptions
1272
- *
1273
- * @typedef { import('../types-internal.js').RunQueueEventNotify } RunQueueEventNotify
1274
- * @typedef { import('../types-internal.js').RunQueueEventSend } RunQueueEventSend
1275
- * @typedef { import('../types-internal.js').RunQueueEventCreateVat } RunQueueEventCreateVat
1276
- * @typedef { import('../types-internal.js').RunQueueEventUpgradeVat } RunQueueEventUpgradeVat
1277
- * @typedef { import('../types-internal.js').RunQueueEventChangeVatOptions } RunQueueEventChangeVatOptions
1278
- * @typedef { import('../types-internal.js').RunQueueEventStartVat } RunQueueEventStartVat
1279
- * @typedef { import('../types-internal.js').RunQueueEventDropExports } RunQueueEventDropExports
1280
- * @typedef { import('../types-internal.js').RunQueueEventRetireExports } RunQueueEventRetireExports
1281
- * @typedef { import('../types-internal.js').RunQueueEventRetireImports } RunQueueEventRetireImports
1282
- * @typedef { import('../types-internal.js').RunQueueEventNegatedGCAction } RunQueueEventNegatedGCAction
1283
- * @typedef { import('../types-internal.js').RunQueueEventBringOutYourDead } RunQueueEventBringOutYourDead
1284
- * @typedef { import('../types-internal.js').RunQueueEventCleanupTerminatedVat } RunQueueEventCleanupTerminatedVat
1285
- * @typedef { import('../types-internal.js').RunQueueEvent } RunQueueEvent
1286
- */
1287
-
1288
1310
  /**
1289
1311
  *
1290
1312
  * Dispatch one delivery event. Eventually, this will be called in a
@@ -1344,16 +1366,17 @@ export default function buildKernel(
1344
1366
  deliverP = processUpgradeVat(message);
1345
1367
  } else if (message.type === 'changeVatOptions') {
1346
1368
  deliverP = processChangeVatOptions(message);
1347
- } else if (message.type === 'bringOutYourDead') {
1348
- deliverP = processBringOutYourDead(message);
1369
+ } else if (message.type === 'cleanup-terminated-vat') {
1370
+ deliverP = processCleanupTerminatedVat(message);
1349
1371
  } else if (message.type === 'negated-gc-action') {
1350
1372
  // processGCActionSet pruned some negated actions, but had no GC
1351
1373
  // action to perform. Record the DB changes in their own crank.
1352
- } else if (message.type === 'cleanup-terminated-vat') {
1353
- deliverP = processCleanupTerminatedVat(message);
1354
- } else if (gcMessages.includes(message.type)) {
1374
+ } else if (message.type === 'bringOutYourDead') {
1375
+ deliverP = processBringOutYourDead(message);
1376
+ } else if (isGcMessage(message)) {
1355
1377
  deliverP = processGCMessage(message);
1356
1378
  } else {
1379
+ // @ts-expect-error unreachable
1357
1380
  Fail`unable to process message.type ${message.type}`;
1358
1381
  }
1359
1382
 
@@ -1369,13 +1392,16 @@ export default function buildKernel(
1369
1392
  */
1370
1393
  async function processDeliveryMessage(message) {
1371
1394
  const messageType = message.type;
1395
+ const messageSummary = legibilizeMessage(message);
1396
+ const crankNum = kernelKeeper.getCrankNumber();
1397
+
1372
1398
  kdebug('');
1373
- // prettier-ignore
1374
- kdebug(`processQ crank ${kernelKeeper.getCrankNumber()} ${JSON.stringify(message)}`);
1375
- kdebug(legibilizeMessage(message));
1399
+ kdebug(`processQ crank ${crankNum} ${JSON.stringify(message)}`);
1400
+ kdebug(messageSummary);
1401
+
1376
1402
  const finish = kernelSlog.startDuration(['crank-start', 'crank-finish'], {
1377
1403
  crankType: 'delivery',
1378
- crankNum: kernelKeeper.getCrankNumber(),
1404
+ crankNum,
1379
1405
  messageType,
1380
1406
  message,
1381
1407
  });
@@ -1400,7 +1426,7 @@ export default function buildKernel(
1400
1426
 
1401
1427
  kernelKeeper.establishCrankSavepoint('deliver');
1402
1428
  const crankResults = await deliverRunQueueEvent(message);
1403
- // { abort/commit, deduct, terminate+notify, consumeMessage }
1429
+ let didSnapshot = false;
1404
1430
 
1405
1431
  if (message.type === 'cleanup-terminated-vat') {
1406
1432
  const { cleanups } = crankResults;
@@ -1433,7 +1459,7 @@ export default function buildKernel(
1433
1459
  } else {
1434
1460
  const vatID = crankResults.didDelivery;
1435
1461
  if (vatID) {
1436
- await vatWarehouse.maybeSaveSnapshot(vatID);
1462
+ didSnapshot = await vatWarehouse.maybeSaveSnapshot(vatID);
1437
1463
  }
1438
1464
  }
1439
1465
  const { computrons, meterID } = crankResults;
@@ -1475,8 +1501,18 @@ export default function buildKernel(
1475
1501
  queueToKref(vatAdminRootKref, method, args, 'logFailure');
1476
1502
  }
1477
1503
 
1478
- kernelKeeper.processRefcounts();
1479
- const crankNum = kernelKeeper.getCrankNumber();
1504
+ const { lostKrefs } = kernelKeeper.processRefcounts();
1505
+ // Lost krefs are expected for GC, but can also occur when a crank is rolled
1506
+ // back.
1507
+ if (lostKrefs.length && !isGcMessage(message)) {
1508
+ const vatID = crankResults.didDelivery;
1509
+ console.log(
1510
+ `⚠️ Ignoring lost krefs from crankNum ${crankNum} ${vatID} ${messageSummary}${didSnapshot ? ' + snapshot' : ''}`,
1511
+ lostKrefs,
1512
+ message,
1513
+ crankResults,
1514
+ );
1515
+ }
1480
1516
  kernelKeeper.incrementCrankNumber();
1481
1517
  const { crankhash, activityhash } = kernelKeeper.emitCrankHashes();
1482
1518
  finish({
@@ -221,12 +221,12 @@ export const getAllDynamicVats = getRequired => {
221
221
  const getObjectReferenceCount = (kvStore, kref) => {
222
222
  const data = kvStore.get(`${kref}.refCount`);
223
223
  if (!data) {
224
- return { reachable: 0, recognizable: 0 };
224
+ return { exists: false, reachable: 0, recognizable: 0 };
225
225
  }
226
226
  const [reachable, recognizable] = commaSplit(data).map(Number);
227
227
  reachable <= recognizable ||
228
228
  Fail`refmismatch(get) ${kref} ${reachable},${recognizable}`;
229
- return { reachable, recognizable };
229
+ return { exists: true, reachable, recognizable };
230
230
  };
231
231
 
232
232
  const setObjectReferenceCount = (kvStore, kref, counts) => {
@@ -1609,12 +1609,17 @@ export default function makeKernelKeeper(
1609
1609
  // leaving work for the next delivery.
1610
1610
 
1611
1611
  function processRefcounts() {
1612
+ const lostKrefs = [];
1612
1613
  if (enableKernelGC) {
1613
1614
  const actions = new Set();
1614
1615
  for (const kref of maybeFreeKrefs.values()) {
1615
1616
  const { type } = parseKernelSlot(kref);
1616
1617
  if (type === 'promise') {
1617
1618
  const kpid = kref;
1619
+ if (!hasKernelPromise(kpid)) {
1620
+ lostKrefs.push(kref);
1621
+ continue;
1622
+ }
1618
1623
  const kp = getKernelPromise(kpid);
1619
1624
  if (kp.refCount === 0) {
1620
1625
  let idx = 0;
@@ -1633,7 +1638,10 @@ export default function makeKernelKeeper(
1633
1638
  }
1634
1639
 
1635
1640
  if (type === 'object') {
1636
- const { reachable, recognizable } = getObjectRefCount(kref);
1641
+ const { exists, reachable, recognizable } = getObjectRefCount(kref);
1642
+ if (!exists) {
1643
+ lostKrefs.push(kref);
1644
+ }
1637
1645
  if (reachable === 0) {
1638
1646
  // We avoid ownerOfKernelObject(), which will report
1639
1647
  // 'undefined' if the owner is dead (and being slowly
@@ -1701,6 +1709,7 @@ export default function makeKernelKeeper(
1701
1709
  addGCActions([...actions]);
1702
1710
  }
1703
1711
  maybeFreeKrefs.clear();
1712
+ return { lostKrefs };
1704
1713
  }
1705
1714
 
1706
1715
  function createVatState(vatID, source, options) {
@@ -570,6 +570,7 @@ export function makeVatWarehouse({
570
570
  *
571
571
  * @param {VatID} vatID
572
572
  * @param {number} [minDeliveryCount]
573
+ * @returns {Promise<boolean>} whether a snapshot was saved
573
574
  */
574
575
  async function maybeSaveSnapshot(vatID, minDeliveryCount = snapshotInterval) {
575
576
  kernelKeeper.vatIsAlive(vatID) || Fail`${q(vatID)}: not alive`;
@@ -2,30 +2,45 @@
2
2
  * @file Source code for a bootstrap vat that runs blockchain behaviors (such as
3
3
  * bridge vat integration) and exposes reflective methods for use in testing.
4
4
  *
5
- * TODO: Build from ./vat-puppet.js makeReflectionMethods
6
- * and share code with packages/vats/tools/vat-reflective-chain-bootstrap.js
5
+ * TODO: Share code with packages/vats/tools/bootstrap-chain-reflective.js
7
6
  * (which basically extends this for better [mock] blockchain integration).
8
7
  */
9
8
 
10
9
  import { Fail, q } from '@endo/errors';
11
- import { objectMap } from '@agoric/internal';
12
10
  import { Far, E } from '@endo/far';
13
- import { makePromiseKit } from '@endo/promise-kit';
14
11
  import { buildManualTimer } from './manual-timer.js';
12
+ import { makeReflectionMethods } from './vat-puppet.js';
15
13
 
16
- export const buildRootObject = () => {
17
- const timer = buildManualTimer();
14
+ /** @import { CreateVatResults } from '../src/types-external.js' */
15
+
16
+ /**
17
+ * @typedef {{ root: object; incarnationNumber?: number }} VatRecord
18
+ * @typedef {VatRecord & CreateVatResults & { bundleCap: unknown }} DynamicVatRecord
19
+ */
20
+
21
+ export const buildRootObject = (vatPowers, bootstrapParameters, baggage) => {
22
+ const manualTimer = buildManualTimer();
23
+
24
+ // Captured/populated by bootstrap.
18
25
  let vatAdmin;
19
- const vatData = new Map();
20
26
  const devicesByName = new Map();
21
- const callLogsByRemotable = new Map();
27
+ /** @type {Map<string, VatRecord | DynamicVatRecord>} */
28
+ const vatRecords = new Map();
29
+
30
+ const reflectionMethods = makeReflectionMethods(
31
+ vatPowers,
32
+ baggage,
33
+ bootstrapParameters,
34
+ );
22
35
 
23
36
  return Far('root', {
37
+ ...reflectionMethods,
38
+
24
39
  bootstrap: async (vats, devices) => {
25
40
  vatAdmin = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin);
26
41
  for (const [name, root] of Object.entries(vats)) {
27
42
  if (name !== 'vatAdmin') {
28
- vatData.set(name, { root });
43
+ vatRecords.set(name, { root });
29
44
  }
30
45
  }
31
46
  for (const [name, device] of Object.entries(devices)) {
@@ -33,78 +48,81 @@ export const buildRootObject = () => {
33
48
  }
34
49
  },
35
50
 
36
- getDevice: async deviceName => devicesByName.get(deviceName),
51
+ getDevice: deviceName => devicesByName.get(deviceName),
37
52
 
38
- getVatAdmin: async () => vatAdmin,
53
+ /** @todo Reconcile with packages/vats/tools/bootstrap-chain-reflective.js */
54
+ getTimer: () => manualTimer,
39
55
 
40
- getTimer: async () => timer,
56
+ getVatAdmin: () => vatAdmin,
41
57
 
42
- getVatRoot: async vatName => {
43
- const vat = vatData.get(vatName) || Fail`unknown vat name: ${q(vatName)}`;
44
- const { root } = vat;
45
- return root;
58
+ getVatAdminNode: vatName => {
59
+ const vat =
60
+ vatRecords.get(vatName) || Fail`unknown vat name: ${q(vatName)}`;
61
+ const { adminNode } = /** @type {DynamicVatRecord} */ (vat);
62
+ return adminNode;
46
63
  },
47
64
 
48
- createVat: async (
49
- { name, bundleCapName, vatParameters = {} },
50
- options = {},
51
- ) => {
52
- const bcap = await E(vatAdmin).getNamedBundleCap(bundleCapName);
53
- const vatOptions = { ...options, vatParameters };
54
- const { adminNode, root } = await E(vatAdmin).createVat(bcap, vatOptions);
55
- vatData.set(name, { adminNode, root });
65
+ getVatRoot: vatName => {
66
+ const vat =
67
+ vatRecords.get(vatName) || Fail`unknown vat name: ${q(vatName)}`;
68
+ const { root } = vat;
56
69
  return root;
57
70
  },
58
71
 
59
- upgradeVat: async ({ name, bundleCapName, vatParameters = {} }) => {
60
- const vat = vatData.get(name) || Fail`unknown vat name: ${q(name)}`;
61
- const bcap = await E(vatAdmin).getNamedBundleCap(bundleCapName);
62
- const options = { vatParameters };
63
- const incarnationNumber = await E(vat.adminNode).upgrade(bcap, options);
64
- vat.incarnationNumber = incarnationNumber;
65
- return incarnationNumber;
66
- },
67
-
68
72
  /**
69
- * Derives a remotable from an object by mapping each object property into a method that returns the value.
70
- *
71
- * @param {string} label
72
- * @param {Record<string, any>} returnValues
73
+ * @todo Reconcile with packages/vats/tools/bootstrap-chain-reflective.js
74
+ * @param {object} details
75
+ * @param {string} details.name name by which the new vat can be referenced
76
+ * @param {string} [details.bundleCapName] defaults to name
77
+ * @param {Record<string, unknown>} [details.vatParameters]
78
+ * @param {{ vatParameters?: object } & Record<string, unknown>} [vatOptions] for specifying more than vatParameters
79
+ * @returns {Promise<DynamicVatRecord['root']>} root object of the new vat
73
80
  */
74
- makeRemotable: (label, returnValues) => {
75
- const callLogs = [];
76
- const makeGetterFunction = (value, name) => {
77
- const getValue = (...args) => {
78
- callLogs.push([name, ...args]);
79
- return value;
80
- };
81
- return getValue;
82
- };
83
- // `objectMap` hardens its result, but...
84
- const methods = objectMap(returnValues, makeGetterFunction);
85
- // ... `Far` requires its methods argument not to be hardened.
86
- const remotable = Far(label, { ...methods });
87
- callLogsByRemotable.set(remotable, callLogs);
88
- return remotable;
89
- },
90
-
91
- makePromiseKit: () => {
92
- const { promise, ...resolverMethods } = makePromiseKit();
93
- const resolver = Far('resolver', resolverMethods);
94
- return harden({ promise, resolver });
81
+ createVat: async (
82
+ { name, bundleCapName = name, vatParameters = undefined },
83
+ vatOptions = {},
84
+ ) => {
85
+ if (Object.hasOwn(vatOptions, 'vatParameters') && vatParameters) {
86
+ Fail`Conflicting specification of vatParameters`;
87
+ }
88
+ const bundleCap = await E(vatAdmin).getNamedBundleCap(bundleCapName);
89
+ const { root, adminNode } = await E(vatAdmin).createVat(bundleCap, {
90
+ vatParameters: vatParameters || {},
91
+ ...vatOptions,
92
+ });
93
+ vatRecords.set(name, { root, adminNode, bundleCap });
94
+ return root;
95
95
  },
96
96
 
97
97
  /**
98
- * Returns a copy of a remotable's logs.
99
- *
100
- * @param {object} remotable
98
+ * @todo Reconcile with packages/vats/tools/bootstrap-chain-reflective.js
99
+ * @param {object} details
100
+ * @param {string} details.name name of the vat to upgrade
101
+ * @param {string} [details.bundleCapName] defaults to the current vat bundle
102
+ * @param {Record<string, unknown>} [details.vatParameters]
103
+ * @param {{ vatParameters?: object } & Record<string, unknown>} [vatOptions] for specifying more than vatParameters
104
+ * @returns {Promise<{ incarnationNumber: number }>} the resulting incarnation number
101
105
  */
102
- getLogForRemotable: remotable => {
103
- const logs =
104
- callLogsByRemotable.get(remotable) ||
105
- Fail`logs not found for ${q(remotable)}`;
106
- // Return a copy so that the original remains mutable.
107
- return harden([...logs]);
106
+ upgradeVat: async (
107
+ { name, bundleCapName = undefined, vatParameters = undefined },
108
+ vatOptions = {},
109
+ ) => {
110
+ if (Object.hasOwn(vatOptions, 'vatParameters') && vatParameters) {
111
+ Fail`Conflicting specification of vatParameters`;
112
+ }
113
+ const vatRecord =
114
+ /** @type {DynamicVatRecord} */ (vatRecords.get(name)) ||
115
+ Fail`unknown vat name: ${q(name)}`;
116
+ const bundleCap = await (bundleCapName
117
+ ? E(vatAdmin).getNamedBundleCap(bundleCapName)
118
+ : vatRecord.bundleCap);
119
+ const options = { vatParameters: vatParameters || {}, ...vatOptions };
120
+ const { incarnationNumber } = await E(vatRecord.adminNode).upgrade(
121
+ bundleCap,
122
+ options,
123
+ );
124
+ vatRecord.incarnationNumber = incarnationNumber;
125
+ return { incarnationNumber };
108
126
  },
109
127
 
110
128
  awaitVatObject: async (presence, path = []) => {
@@ -7,6 +7,8 @@ import { Fail, q } from '@endo/errors';
7
7
  import { Far, E } from '@endo/far';
8
8
  import { makePromiseKit } from '@endo/promise-kit';
9
9
  import { objectMap } from '@agoric/internal';
10
+ import { M } from '@agoric/store';
11
+ import { prepareExoClass, watchPromise } from '@agoric/vat-data';
10
12
 
11
13
  /**
12
14
  * @callback Die
@@ -14,15 +16,61 @@ import { objectMap } from '@agoric/internal';
14
16
  * @param {[target: unknown, method: string, ...args: unknown[]]} [finalSend]
15
17
  */
16
18
 
19
+ /**
20
+ * @callback SetInStore
21
+ * @param {string} key
22
+ * @param {unknown} value
23
+ * @param {{ mode?: 'init' | 'replace' | 'replaceOrInit' }} [options]
24
+ */
25
+
17
26
  /**
18
27
  * @typedef {Array<[name: string, ...args: unknown[]]>} CallLog
19
28
  */
20
29
 
30
+ const PromiseWatcherI = M.interface('PromiseWatcher', {
31
+ onFulfilled: M.call(M.any()).returns(),
32
+ onRejected: M.call(M.any()).returns(),
33
+ getResult: M.call().returns(
34
+ M.or(['unsettled'], [M.or('fulfilled', 'rejected'), M.any()]),
35
+ ),
36
+ });
37
+ /** @param {unknown} [remoteTarget] */
38
+ const initPromiseWatcher = remoteTarget =>
39
+ harden({ remoteTarget, result: ['unsettled'] });
40
+ const promiseWatcherMethods = {
41
+ onFulfilled(value) {
42
+ this.state.result = harden(['fulfilled', value]);
43
+ if (!this.state.remoteTarget) return;
44
+ void E(this.state.remoteTarget).onFulfilled(value);
45
+ },
46
+ onRejected(value) {
47
+ this.state.result = harden(['rejected', value]);
48
+ if (!this.state.remoteTarget) return;
49
+ void E(this.state.remoteTarget).onRejected(value);
50
+ },
51
+ getResult() {
52
+ return this.state.result;
53
+ },
54
+ };
55
+
21
56
  /**
22
57
  * @param {import('@agoric/swingset-vat').VatPowers} vatPowers
23
58
  * @param {import('@agoric/vat-data').Baggage} baggage
59
+ * @param {unknown} [vatParameters]
24
60
  */
25
- export const makeReflectionMethods = (vatPowers, baggage) => {
61
+ export const makeReflectionMethods = (vatPowers, baggage, vatParameters) => {
62
+ // Avoid a @agoric/swingset-vat -> @agoric/zone -> @agoric/swingset-vat
63
+ // dependency cycle.
64
+ // const zone = makeDurableZone(baggage);
65
+ // const makePromiseWatcher = zone.exoClass(...);
66
+ const makePromiseWatcher = prepareExoClass(
67
+ baggage,
68
+ 'PromiseWatcher',
69
+ PromiseWatcherI,
70
+ initPromiseWatcher,
71
+ promiseWatcherMethods,
72
+ );
73
+
26
74
  let baggageHoldCount = 0;
27
75
  /** @type {Map<object, CallLog>} */
28
76
  const callLogsByRemotable = new Map();
@@ -52,6 +100,8 @@ export const makeReflectionMethods = (vatPowers, baggage) => {
52
100
  if (finalSend) send(...finalSend);
53
101
  },
54
102
 
103
+ getVatParameters: () => vatParameters,
104
+
55
105
  holdInBaggage: (...values) => {
56
106
  for (const value of values) {
57
107
  baggage.init(`held-${baggageHoldCount}`, value);
@@ -62,6 +112,25 @@ export const makeReflectionMethods = (vatPowers, baggage) => {
62
112
 
63
113
  holdInHeap: (...values) => heldInHeap.push(...values),
64
114
 
115
+ /** @type {SetInStore} */
116
+ setInBaggage: (key, value, { mode = 'replaceOrInit' } = {}) => {
117
+ const exists = baggage.has(key);
118
+ if (mode === 'init' || (mode === 'replaceOrInit' && !exists)) {
119
+ baggage.init(key, value);
120
+ } else if (mode === 'replace' || (mode === 'replaceOrInit' && exists)) {
121
+ baggage.set(key, value);
122
+ } else {
123
+ Fail`unknown mode ${q(mode)}`;
124
+ }
125
+ },
126
+
127
+ getFromBaggage: key => baggage.get(key),
128
+
129
+ callFromBaggage: (key, methodName, ...args) => {
130
+ const obj = baggage.get(key);
131
+ void E(obj)[methodName](...args);
132
+ },
133
+
65
134
  makePromiseKit: () => {
66
135
  const { promise, ...resolverMethods } = makePromiseKit();
67
136
  void promise.catch(() => {});
@@ -69,6 +138,8 @@ export const makeReflectionMethods = (vatPowers, baggage) => {
69
138
  return harden({ promise, resolver });
70
139
  },
71
140
 
141
+ makeSettledPromise: settlement => Promise.resolve(settlement),
142
+
72
143
  makeUnsettledPromise() {
73
144
  const { promise } = makePromiseKit();
74
145
  void promise.catch(() => {});
@@ -76,17 +147,18 @@ export const makeReflectionMethods = (vatPowers, baggage) => {
76
147
  },
77
148
 
78
149
  /**
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`.
150
+ * Return a remotable with a method for each property of an optional object
151
+ * that returns the corresponding property value. Invocations of those
152
+ * methods and their arguments are captured for later retrieval by
153
+ * `getLogForRemotable`.
82
154
  *
83
155
  * @param {string} [label]
84
- * @param {Record<string, any>} [fields]
156
+ * @param {Record<string, any>} [methodReturnValues]
85
157
  */
86
- makeRemotable: (label = 'Remotable', fields = {}) => {
158
+ makeRemotable: (label = 'Remotable', methodReturnValues = {}) => {
87
159
  /** @type {CallLog} */
88
160
  const callLog = [];
89
- const methods = objectMap(fields, (value, name) =>
161
+ const methods = objectMap(methodReturnValues, (value, name) =>
90
162
  makeSpy(value, name, callLog),
91
163
  );
92
164
  const remotable = Far(label, { ...methods });
@@ -95,17 +167,56 @@ export const makeReflectionMethods = (vatPowers, baggage) => {
95
167
  },
96
168
 
97
169
  /**
170
+ * Return a copy of a remotable's call log.
171
+ *
98
172
  * @param {object} remotable
99
173
  * @returns {CallLog}
100
174
  */
101
- getCallLogForRemotable: remotable =>
102
- callLogsByRemotable.get(remotable) ||
103
- Fail`unknown remotable ${q(remotable)}`,
175
+ getLogForRemotable: remotable => {
176
+ const callLog =
177
+ callLogsByRemotable.get(remotable) ||
178
+ Fail`unknown remotable ${q(remotable)}`;
179
+ // Return an immutable copy of the mutable original.
180
+ return harden([...callLog]);
181
+ },
182
+
183
+ sendOnly: (target, methodName, ...args) => {
184
+ void E(target)[methodName](...args);
185
+ },
186
+
187
+ throw: message => {
188
+ throw Error(message);
189
+ },
190
+
191
+ watchSettledPromiseByProxy: (settlement, watcher, ...args) => {
192
+ const watcherProxy = makePromiseWatcher(watcher);
193
+ watchPromise(Promise.resolve(settlement), watcherProxy, ...args);
194
+ },
104
195
  };
105
196
  };
106
197
  harden(makeReflectionMethods);
107
198
 
108
- export function buildRootObject(vatPowers, _vatParameters, baggage) {
109
- const methods = makeReflectionMethods(vatPowers, baggage);
110
- return Far('root', methods);
199
+ export function buildRootObject(vatPowers, vatParameters, baggage) {
200
+ const methods = makeReflectionMethods(vatPowers, baggage, vatParameters);
201
+ const rootObject = Far('root', methods);
202
+
203
+ // Invoke specified methods of the new root object *before* returning,
204
+ // supporting use of previous results as top-level arguments by interpreting
205
+ // registered symbols with decimal-integer descriptions as references into an
206
+ // array of previous results.
207
+ const { initialCalls = [] } = vatParameters || {};
208
+ const results = [];
209
+ const decimalIntPatt = /^-?(?:0|[1-9][0-9]*)$/;
210
+ const translateArg = arg => {
211
+ if (typeof arg !== 'symbol' || !Symbol.keyFor(arg)) return arg;
212
+ if (!arg.description?.match(decimalIntPatt)) return arg;
213
+ return results.at(Number(arg.description));
214
+ };
215
+ for (const [methodName, ...args] of initialCalls) {
216
+ const translatedArgs = args.map(translateArg);
217
+ const result = rootObject[methodName](...translatedArgs);
218
+ results.push(result);
219
+ }
220
+
221
+ return rootObject;
111
222
  }