@agoric/swingset-vat 0.33.0-u19.1 → 0.33.0-u20.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 +28 -28
- package/src/controller/controller.js +420 -330
- package/src/controller/startNodeSubprocess.js +6 -0
- package/src/kernel/kernel.js +27 -14
- package/src/kernel/slogger.js +109 -35
- package/src/kernel/state/kernelKeeper.js +1 -1
- package/src/kernel/state/stats.js +12 -9
- package/src/kernel/state/storageHelper.js +4 -0
- package/src/kernel/vat-warehouse.js +5 -2
- package/src/lib/message.js +4 -0
- package/src/types-external.js +12 -4
- package/src/vats/plugin-manager.js +1 -2
- package/tools/run-utils.js +2 -1
- package/src/kernel/metrics.js +0 -152
|
@@ -9,7 +9,7 @@ import { tmpName } from 'tmp';
|
|
|
9
9
|
import anylogger from 'anylogger';
|
|
10
10
|
import microtime from 'microtime';
|
|
11
11
|
|
|
12
|
-
import { assert, Fail } from '@endo/errors';
|
|
12
|
+
import { assert, q, Fail } from '@endo/errors';
|
|
13
13
|
import { importBundle } from '@endo/import-bundle';
|
|
14
14
|
import { initSwingStore } from '@agoric/swing-store';
|
|
15
15
|
|
|
@@ -38,12 +38,17 @@ import { makeStartSubprocessWorkerNode } from './startNodeSubprocess.js';
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* @import {EReturn} from '@endo/far';
|
|
41
|
+
* @import {LimitedConsole} from '@agoric/internal';
|
|
42
|
+
* @import {VatID} from '../types-internal.js';
|
|
41
43
|
*/
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
|
-
* @typedef {
|
|
46
|
+
* @typedef {Record<string, unknown> & {type: string, time?: never, monotime?: never}} SlogProps
|
|
47
|
+
* @typedef {Omit<SlogProps, 'type'> & {type?: never, seconds?: never}} SlogDurationProps
|
|
45
48
|
*/
|
|
46
49
|
|
|
50
|
+
const { hasOwn } = Object;
|
|
51
|
+
|
|
47
52
|
const endoZipBase64Sha512Shape = harden({
|
|
48
53
|
moduleFormat: 'endoZipBase64',
|
|
49
54
|
endoZipBase64: M.string(harden({ stringLengthLimit: Infinity })),
|
|
@@ -85,14 +90,29 @@ function makeConsole(prefixer) {
|
|
|
85
90
|
});
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
/**
|
|
94
|
+
* A console-like object for logging. It starts as the global console but is
|
|
95
|
+
* immediately replaced with an anylogger instance dedicated to this file, and
|
|
96
|
+
* upon creation of a controller is replaced again with an anylogger dedicated
|
|
97
|
+
* to that controller (and which also emits slog entries).
|
|
98
|
+
*
|
|
99
|
+
* @type {LimitedConsole}
|
|
100
|
+
*/
|
|
101
|
+
let sloggingConsole = console;
|
|
102
|
+
/** @type {(newConsole: LimitedConsole) => void} */
|
|
103
|
+
const setSloggingConsole = newConsole => {
|
|
104
|
+
sloggingConsole = newConsole;
|
|
105
|
+
};
|
|
106
|
+
setSloggingConsole(makeConsole('SwingSet:controller'));
|
|
107
|
+
|
|
88
108
|
/**
|
|
89
109
|
* @param {unknown} e
|
|
90
110
|
* @param {Promise} pr
|
|
91
111
|
*/
|
|
92
|
-
function
|
|
112
|
+
function onUnhandledRejection(e, pr) {
|
|
93
113
|
// Don't trigger sensitive hosts (like AVA).
|
|
94
114
|
pr.catch(() => {});
|
|
95
|
-
|
|
115
|
+
sloggingConsole.error('🤞 UnhandledPromiseRejection:', e);
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
/**
|
|
@@ -123,11 +143,9 @@ export async function makeSwingsetController(
|
|
|
123
143
|
const kvStore = kernelStorage.kvStore;
|
|
124
144
|
insistStorageAPI(kvStore);
|
|
125
145
|
|
|
126
|
-
// Use ambient process.env only if caller did not specify.
|
|
127
|
-
const { env = process.env } = runtimeOptions;
|
|
128
|
-
|
|
129
|
-
// build console early so we can add console.log to diagnose early problems
|
|
130
146
|
const {
|
|
147
|
+
// Use ambient process.env only if caller did not specify.
|
|
148
|
+
env = process.env,
|
|
131
149
|
verbose,
|
|
132
150
|
debugPrefix = '',
|
|
133
151
|
slogCallbacks,
|
|
@@ -138,8 +156,7 @@ export async function makeSwingsetController(
|
|
|
138
156
|
xsnapBundleData = makeXsnapBundleData(),
|
|
139
157
|
profileVats = [],
|
|
140
158
|
debugVats = [],
|
|
141
|
-
|
|
142
|
-
const {
|
|
159
|
+
|
|
143
160
|
bundleHandler = makeWorkerBundleHandler(
|
|
144
161
|
kernelStorage.bundleStore,
|
|
145
162
|
xsnapBundleData,
|
|
@@ -150,6 +167,103 @@ export async function makeSwingsetController(
|
|
|
150
167
|
throw Error('SES must be installed before calling makeSwingsetController');
|
|
151
168
|
}
|
|
152
169
|
|
|
170
|
+
/** @type {(obj: SlogProps) => void} */
|
|
171
|
+
function writeSlogObject(obj) {
|
|
172
|
+
if (!slogSender) return;
|
|
173
|
+
|
|
174
|
+
const { type, seconds, ...props } = obj;
|
|
175
|
+
const timings = {
|
|
176
|
+
// microtime gives POSIX gettimeofday() with microsecond resolution
|
|
177
|
+
time: microtime.nowDouble(),
|
|
178
|
+
// this is CLOCK_MONOTONIC, seconds since process start
|
|
179
|
+
monotime: performance.now() / 1000,
|
|
180
|
+
...(seconds === undefined ? undefined : { seconds }),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// rearrange the fields a bit to make it more legible to humans
|
|
184
|
+
slogSender(harden({ type, ...props, ...timings }));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Capture an extended process in the slog, writing an entry with `type`
|
|
189
|
+
* $startLabel and then later (if the function returns successfully or calls
|
|
190
|
+
* the finish callback provided to it) another entry with `type` $endLabel and
|
|
191
|
+
* a `seconds` property valued with the total elapsed duration in seconds.
|
|
192
|
+
* Finish is implied by settlement of the function's awaited return value, so
|
|
193
|
+
* any explicit use of the finish callback MUST NOT follow that settlement.
|
|
194
|
+
*
|
|
195
|
+
* @template T
|
|
196
|
+
* @template {unknown[]} A
|
|
197
|
+
* @param {readonly [startLabel: string, endLabel: string]} labels
|
|
198
|
+
* @param {SlogDurationProps} startProps for both slog entries
|
|
199
|
+
* @param {(finish: (extraProps?: SlogDurationProps) => void, ...args: A) => (T | Promise<T>)} fn
|
|
200
|
+
* @param {unknown[] & A} args
|
|
201
|
+
* @returns {Promise<T>}
|
|
202
|
+
*/
|
|
203
|
+
const slogDuration = async (labels, startProps, fn, ...args) => {
|
|
204
|
+
const [startLabel, endLabel] = labels;
|
|
205
|
+
const props = { ...startProps };
|
|
206
|
+
if (hasOwn(props, 'type') || hasOwn(props, 'seconds')) {
|
|
207
|
+
const msg = 'startProps must not include "type" or "seconds"';
|
|
208
|
+
sloggingConsole.error(Error(msg));
|
|
209
|
+
delete props.type;
|
|
210
|
+
delete props.seconds;
|
|
211
|
+
}
|
|
212
|
+
let finished = false;
|
|
213
|
+
/** @type {(extraProps?: SlogDurationProps) => void} */
|
|
214
|
+
const finish = extraProps => {
|
|
215
|
+
const seconds = (performance.now() - t0) / 1000;
|
|
216
|
+
if (finished) {
|
|
217
|
+
// `finish` should only be called once.
|
|
218
|
+
// Log a stack-bearing error instance, but throw something more opaque.
|
|
219
|
+
const msg = `slog event ${startLabel} ${q(startProps || {})} already finished; ignoring props ${q(extraProps || {})}`;
|
|
220
|
+
sloggingConsole.error(Error(msg));
|
|
221
|
+
Fail`slog event ${startLabel} already finished`;
|
|
222
|
+
}
|
|
223
|
+
finished = true;
|
|
224
|
+
if (extraProps) {
|
|
225
|
+
// Preserve extraProps as an atomic unit by deleting prior occurrences.
|
|
226
|
+
for (const name of Object.keys(extraProps)) delete props[name];
|
|
227
|
+
if (hasOwn(extraProps, 'type') || hasOwn(extraProps, 'seconds')) {
|
|
228
|
+
const msg = `extraProps ${q(extraProps)} must not include "type" or "seconds"`;
|
|
229
|
+
sloggingConsole.error(Error(msg));
|
|
230
|
+
const {
|
|
231
|
+
type: _ignoredType,
|
|
232
|
+
seconds: _ignoredSeconds,
|
|
233
|
+
...validProps
|
|
234
|
+
} = extraProps;
|
|
235
|
+
extraProps = validProps;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
writeSlogObject({ type: endLabel, ...props, ...extraProps, seconds });
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
writeSlogObject({ type: startLabel, ...props });
|
|
242
|
+
const t0 = performance.now();
|
|
243
|
+
try {
|
|
244
|
+
// We need to synchronously provide the finish function.
|
|
245
|
+
// eslint-disable-next-line @jessie.js/safe-await-separator
|
|
246
|
+
const result = await fn(finish, ...args);
|
|
247
|
+
if (!finished) finish();
|
|
248
|
+
return result;
|
|
249
|
+
} catch (cause) {
|
|
250
|
+
if (!finished) {
|
|
251
|
+
const msg = `unfinished slog event ${startLabel} ${q(startProps || {})}`;
|
|
252
|
+
sloggingConsole.error(Error(msg, { cause }));
|
|
253
|
+
}
|
|
254
|
+
throw cause;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const controllerConsole = makeConsole(`${debugPrefix}SwingSet:controller`);
|
|
259
|
+
const controllerSloggingConsole = makeLimitedConsole(level => {
|
|
260
|
+
return (...args) => {
|
|
261
|
+
controllerConsole[level](...args);
|
|
262
|
+
writeSlogObject({ type: 'console', source: 'controller', level, args });
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
setSloggingConsole(controllerSloggingConsole);
|
|
266
|
+
|
|
153
267
|
const startXSnap = makeStartXSnap({
|
|
154
268
|
bundleHandler,
|
|
155
269
|
snapStore: kernelStorage.snapStore,
|
|
@@ -167,340 +281,316 @@ export async function makeSwingsetController(
|
|
|
167
281
|
debugVats,
|
|
168
282
|
);
|
|
169
283
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// rearrange the fields a bit to make it more legible to humans
|
|
182
|
-
const timedObj = { type: undefined, ...obj, time, monotime };
|
|
183
|
-
|
|
184
|
-
// Allow the SwingSet host to do anything they want with slog messages.
|
|
185
|
-
slogSender(timedObj);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const console = makeConsole(`${debugPrefix}SwingSet:controller`);
|
|
189
|
-
// We can harden this 'console' because it's new, but if we were using the
|
|
190
|
-
// original 'console' object (which has a unique prototype), we'd have to
|
|
191
|
-
// harden(Object.getPrototypeOf(console));
|
|
192
|
-
// see https://github.com/Agoric/SES-shim/issues/292 for details
|
|
193
|
-
harden(console);
|
|
194
|
-
|
|
195
|
-
writeSlogObject({ type: 'kernel-init-start' });
|
|
196
|
-
|
|
197
|
-
writeSlogObject({ type: 'bundle-kernel-start' });
|
|
198
|
-
await null;
|
|
199
|
-
const { kernelBundle = await buildKernelBundle() } = runtimeOptions;
|
|
200
|
-
writeSlogObject({ type: 'bundle-kernel-finish' });
|
|
201
|
-
|
|
202
|
-
// FIXME: Put this somewhere better.
|
|
203
|
-
const handlers = process.listeners('unhandledRejection');
|
|
204
|
-
let haveUnhandledRejectionHandler = false;
|
|
205
|
-
for (const handler of handlers) {
|
|
206
|
-
if (handler === unhandledRejectionHandler) {
|
|
207
|
-
haveUnhandledRejectionHandler = true;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (!haveUnhandledRejectionHandler) {
|
|
212
|
-
process.on('unhandledRejection', unhandledRejectionHandler);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const kernelConsole = makeConsole(`${debugPrefix}SwingSet:kernel`);
|
|
216
|
-
const sloggingKernelConsole = makeLimitedConsole(level => {
|
|
217
|
-
return (...args) => {
|
|
218
|
-
kernelConsole[level](...args);
|
|
219
|
-
writeSlogObject({ type: 'console', source: 'kernel', level, args });
|
|
220
|
-
};
|
|
221
|
-
});
|
|
222
|
-
writeSlogObject({ type: 'import-kernel-start' });
|
|
223
|
-
const kernelNS = await importBundle(kernelBundle, {
|
|
224
|
-
filePrefix: 'kernel/...',
|
|
225
|
-
endowments: {
|
|
226
|
-
console: sloggingKernelConsole,
|
|
227
|
-
// See https://github.com/Agoric/agoric-sdk/issues/9515
|
|
228
|
-
assert: globalThis.assert,
|
|
229
|
-
require: harden(
|
|
230
|
-
what => Fail`kernelRequire unprepared to satisfy require(${what})`,
|
|
231
|
-
),
|
|
232
|
-
URL: globalThis.Base64, // Unavailable only on XSnap
|
|
233
|
-
Base64: globalThis.Base64, // Available only on XSnap
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
const buildKernel = kernelNS.default;
|
|
237
|
-
writeSlogObject({ type: 'import-kernel-finish' });
|
|
238
|
-
|
|
239
|
-
// all vats get these in their global scope, plus a vat-specific 'console'
|
|
240
|
-
const vatEndowments = harden({});
|
|
241
|
-
|
|
242
|
-
const kernelEndowments = {
|
|
243
|
-
waitUntilQuiescent,
|
|
244
|
-
kernelStorage,
|
|
245
|
-
debugPrefix,
|
|
246
|
-
vatEndowments,
|
|
247
|
-
makeConsole,
|
|
248
|
-
startSubprocessWorkerNode,
|
|
249
|
-
startXSnap,
|
|
250
|
-
slogCallbacks,
|
|
251
|
-
writeSlogObject,
|
|
252
|
-
WeakRef,
|
|
253
|
-
FinalizationRegistry,
|
|
254
|
-
gcAndFinalize: makeGcAndFinalize(engineGC),
|
|
255
|
-
bundleHandler,
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const kernelRuntimeOptions = {
|
|
259
|
-
verbose,
|
|
260
|
-
warehousePolicy,
|
|
261
|
-
overrideVatManagerOptions,
|
|
262
|
-
};
|
|
263
|
-
/** @type { ReturnType<typeof import('../kernel/kernel.js').default> } */
|
|
264
|
-
const kernel = buildKernel(
|
|
265
|
-
kernelEndowments,
|
|
266
|
-
deviceEndowments,
|
|
267
|
-
kernelRuntimeOptions,
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
if (runtimeOptions.verbose) {
|
|
271
|
-
kernel.kdebugEnable(true);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
await kernel.start();
|
|
284
|
+
const kernelInitLabels = /** @type {const} */ ([
|
|
285
|
+
'kernel-init-start',
|
|
286
|
+
'kernel-init-finish',
|
|
287
|
+
]);
|
|
288
|
+
const controller = await slogDuration(kernelInitLabels, {}, async () => {
|
|
289
|
+
const kernelBundle = await slogDuration(
|
|
290
|
+
['bundle-kernel-start', 'bundle-kernel-finish'],
|
|
291
|
+
{},
|
|
292
|
+
async () => runtimeOptions.kernelBundle ?? buildKernelBundle(),
|
|
293
|
+
);
|
|
275
294
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
* @param {BundleID} [allegedBundleID]
|
|
281
|
-
* @returns {Promise<BundleID>}
|
|
282
|
-
*/
|
|
283
|
-
async function validateAndInstallBundle(bundle, allegedBundleID = undefined) {
|
|
284
|
-
// TODO The following assertion may be removed when checkBundle subsumes
|
|
285
|
-
// the responsibility to verify the permanence of a bundle's properties.
|
|
286
|
-
// https://github.com/endojs/endo/issues/1106
|
|
287
|
-
mustMatch(bundle, endoZipBase64Sha512Shape);
|
|
288
|
-
await checkBundle(bundle, computeSha512, allegedBundleID);
|
|
289
|
-
const { endoZipBase64Sha512 } = bundle;
|
|
290
|
-
assert.typeof(endoZipBase64Sha512, 'string');
|
|
291
|
-
const bundleID = `b1-${endoZipBase64Sha512}`;
|
|
292
|
-
if (allegedBundleID !== undefined) {
|
|
293
|
-
bundleID === allegedBundleID ||
|
|
294
|
-
Fail`alleged bundleID ${allegedBundleID} does not match actual ${bundleID}`;
|
|
295
|
+
// FIXME: Put this somewhere better.
|
|
296
|
+
const rejectionHandlers = process.listeners('unhandledRejection');
|
|
297
|
+
if (!rejectionHandlers.includes(onUnhandledRejection)) {
|
|
298
|
+
process.on('unhandledRejection', onUnhandledRejection);
|
|
295
299
|
}
|
|
296
|
-
await kernel.installBundle(bundleID, bundle);
|
|
297
|
-
return bundleID;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// the kernel won't leak our objects into the Vats, we must do
|
|
301
|
-
// the same in this wrapper
|
|
302
|
-
const controller = harden({
|
|
303
|
-
log(str) {
|
|
304
|
-
kernel.log(str);
|
|
305
|
-
},
|
|
306
300
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
reapAllVats() {
|
|
338
|
-
kernel.reapAllVats();
|
|
339
|
-
},
|
|
340
|
-
|
|
341
|
-
changeKernelOptions(options) {
|
|
342
|
-
kernel.changeKernelOptions(options);
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
getStats() {
|
|
346
|
-
return deepCopyJsonable(kernel.getStats());
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
getStatus() {
|
|
350
|
-
return deepCopyJsonable(kernel.getStatus());
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
getActivityhash() {
|
|
354
|
-
return kernelStorage.getActivityhash();
|
|
355
|
-
},
|
|
356
|
-
|
|
357
|
-
// everything beyond here is for tests, and everything should be migrated
|
|
358
|
-
// to be on this 'debug' object to make that clear
|
|
359
|
-
|
|
360
|
-
debug: {
|
|
361
|
-
addDeviceHook: kernel.addDeviceHook,
|
|
362
|
-
},
|
|
363
|
-
|
|
364
|
-
pinVatRoot(vatName) {
|
|
365
|
-
const vatID = kernel.vatNameToID(vatName);
|
|
366
|
-
const kref = kernel.getRootObject(vatID);
|
|
367
|
-
kernel.pinObject(kref);
|
|
368
|
-
kernelStorage.emitCrankHashes();
|
|
369
|
-
return kref;
|
|
370
|
-
},
|
|
371
|
-
|
|
372
|
-
kpRegisterInterest(kpid) {
|
|
373
|
-
return kernel.kpRegisterInterest(kpid);
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
kpStatus(kpid) {
|
|
377
|
-
return kernel.kpStatus(kpid);
|
|
378
|
-
},
|
|
379
|
-
|
|
380
|
-
kpResolution(kpid, options) {
|
|
381
|
-
const result = kernel.kpResolution(kpid, options);
|
|
382
|
-
// kpResolution does DB write (changes refcounts) so we need emitCrankHashes here
|
|
383
|
-
kernelStorage.emitCrankHashes();
|
|
384
|
-
return result;
|
|
385
|
-
},
|
|
301
|
+
const kernelConsole = makeConsole(`${debugPrefix}SwingSet:kernel`);
|
|
302
|
+
const sloggingKernelConsole = makeLimitedConsole(level => {
|
|
303
|
+
return (...args) => {
|
|
304
|
+
kernelConsole[level](...args);
|
|
305
|
+
writeSlogObject({ type: 'console', source: 'kernel', level, args });
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
const buildKernel = await slogDuration(
|
|
309
|
+
['import-kernel-start', 'import-kernel-finish'],
|
|
310
|
+
{},
|
|
311
|
+
async () => {
|
|
312
|
+
const kernelNS = await importBundle(kernelBundle, {
|
|
313
|
+
filePrefix: 'kernel/...',
|
|
314
|
+
endowments: {
|
|
315
|
+
console: sloggingKernelConsole,
|
|
316
|
+
// See https://github.com/Agoric/agoric-sdk/issues/9515
|
|
317
|
+
assert: globalThis.assert,
|
|
318
|
+
require: harden(
|
|
319
|
+
what =>
|
|
320
|
+
Fail`kernelRequire unprepared to satisfy require(${what})`,
|
|
321
|
+
),
|
|
322
|
+
URL: globalThis.Base64, // Unavailable only on XSnap
|
|
323
|
+
Base64: globalThis.Base64, // Available only on XSnap
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
return kernelNS.default;
|
|
327
|
+
},
|
|
328
|
+
);
|
|
386
329
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
330
|
+
const kernelEndowments = {
|
|
331
|
+
waitUntilQuiescent,
|
|
332
|
+
kernelStorage,
|
|
333
|
+
debugPrefix,
|
|
334
|
+
// all vats get these in their global scope, plus a vat-specific 'console'
|
|
335
|
+
vatEndowments: harden({}),
|
|
336
|
+
makeConsole,
|
|
337
|
+
startSubprocessWorkerNode,
|
|
338
|
+
startXSnap,
|
|
339
|
+
slogCallbacks,
|
|
340
|
+
writeSlogObject,
|
|
341
|
+
slogDuration,
|
|
342
|
+
WeakRef,
|
|
343
|
+
FinalizationRegistry,
|
|
344
|
+
gcAndFinalize: makeGcAndFinalize(engineGC),
|
|
345
|
+
bundleHandler,
|
|
346
|
+
};
|
|
347
|
+
const kernelRuntimeOptions = {
|
|
348
|
+
verbose,
|
|
349
|
+
warehousePolicy,
|
|
350
|
+
overrideVatManagerOptions,
|
|
351
|
+
};
|
|
393
352
|
|
|
394
|
-
|
|
353
|
+
/** @type { ReturnType<typeof import('../kernel/kernel.js').default> } */
|
|
354
|
+
const kernel = buildKernel(
|
|
355
|
+
kernelEndowments,
|
|
356
|
+
deviceEndowments,
|
|
357
|
+
kernelRuntimeOptions,
|
|
358
|
+
);
|
|
395
359
|
|
|
396
|
-
|
|
397
|
-
* Queue a method call into the named vat
|
|
398
|
-
*
|
|
399
|
-
* @param {string} vatName
|
|
400
|
-
* @param {string|symbol} method
|
|
401
|
-
* @param {unknown[]} args
|
|
402
|
-
* @param {ResolutionPolicy} resultPolicy
|
|
403
|
-
*/
|
|
404
|
-
queueToVatRoot(vatName, method, args = [], resultPolicy = 'ignore') {
|
|
405
|
-
const vatID = kernel.vatNameToID(vatName);
|
|
406
|
-
if (typeof method !== 'symbol') {
|
|
407
|
-
assert.typeof(method, 'string');
|
|
408
|
-
}
|
|
409
|
-
const kref = kernel.getRootObject(vatID);
|
|
410
|
-
const kpid = kernel.queueToKref(kref, method, args, resultPolicy);
|
|
411
|
-
if (kpid) {
|
|
412
|
-
kernel.kpRegisterInterest(kpid);
|
|
413
|
-
}
|
|
414
|
-
kernelStorage.emitCrankHashes();
|
|
415
|
-
return kpid;
|
|
416
|
-
},
|
|
360
|
+
await kernel.start();
|
|
417
361
|
|
|
418
362
|
/**
|
|
419
|
-
*
|
|
363
|
+
* Validate and install a code bundle.
|
|
420
364
|
*
|
|
421
|
-
* @param {
|
|
422
|
-
* @param {
|
|
423
|
-
* @
|
|
424
|
-
* @param {ResolutionPolicy} resultPolicy
|
|
365
|
+
* @param {EndoZipBase64Bundle} bundle
|
|
366
|
+
* @param {BundleID} [allegedBundleID]
|
|
367
|
+
* @returns {Promise<BundleID>}
|
|
425
368
|
*/
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
369
|
+
async function validateAndInstallBundle(
|
|
370
|
+
bundle,
|
|
371
|
+
allegedBundleID = undefined,
|
|
372
|
+
) {
|
|
373
|
+
// TODO The following assertion may be removed when checkBundle subsumes
|
|
374
|
+
// the responsibility to verify the permanence of a bundle's properties.
|
|
375
|
+
// https://github.com/endojs/endo/issues/1106
|
|
376
|
+
mustMatch(bundle, endoZipBase64Sha512Shape);
|
|
377
|
+
await checkBundle(bundle, computeSha512, allegedBundleID);
|
|
378
|
+
const { endoZipBase64Sha512 } = bundle;
|
|
379
|
+
assert.typeof(endoZipBase64Sha512, 'string');
|
|
380
|
+
const bundleID = `b1-${endoZipBase64Sha512}`;
|
|
381
|
+
if (allegedBundleID !== undefined) {
|
|
382
|
+
bundleID === allegedBundleID ||
|
|
383
|
+
Fail`alleged bundleID ${allegedBundleID} does not match actual ${bundleID}`;
|
|
435
384
|
}
|
|
436
|
-
|
|
437
|
-
return
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
upgradeStaticVat(vatName, shouldPauseFirst, bundleID, options = {}) {
|
|
441
|
-
const vatID = kernel.vatNameToID(vatName);
|
|
442
|
-
let pauseTarget = null;
|
|
443
|
-
if (shouldPauseFirst) {
|
|
444
|
-
pauseTarget = kslot(kernel.getRootObject(vatID));
|
|
445
|
-
}
|
|
446
|
-
if (!options.upgradeMessage) {
|
|
447
|
-
options.upgradeMessage = `vat ${vatName} upgraded`;
|
|
448
|
-
}
|
|
449
|
-
const result = controller.queueToVatRoot(
|
|
450
|
-
'vatAdmin',
|
|
451
|
-
'upgradeStaticVat',
|
|
452
|
-
[vatID, pauseTarget, bundleID, options],
|
|
453
|
-
'ignore',
|
|
454
|
-
);
|
|
455
|
-
// no emitCrankHashes here because queueToVatRoot did that
|
|
456
|
-
return result;
|
|
457
|
-
},
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* terminate a vat by ID
|
|
461
|
-
*
|
|
462
|
-
* This allows the host app to terminate any vat. The effect is
|
|
463
|
-
* equivalent to the holder of the vat's `adminNode` calling
|
|
464
|
-
* `E(adminNode).terminateWithFailure(reason)`, or the vat itself
|
|
465
|
-
* calling `vatPowers.exitVatWithFailure(reason)`. It accepts a
|
|
466
|
-
* reason capdata structure (use 'kser()' to build one), which
|
|
467
|
-
* will be included in rejection data for the promise available to
|
|
468
|
-
* `E(adminNode).done()`, just like the internal termination APIs.
|
|
469
|
-
* Note that no slots/krefs are allowed in 'reason' when
|
|
470
|
-
* terminating the vat externally.
|
|
471
|
-
*
|
|
472
|
-
* This is a superpower available only from the host app, not from
|
|
473
|
-
* within vats, since `vatID` is merely a string and can be forged
|
|
474
|
-
* trivially. The host app is responsible for supplying the right
|
|
475
|
-
* vatID to kill, by looking at the database or logs (note that
|
|
476
|
-
* vats do not know their own vatID, and `controller.vatNameToID`
|
|
477
|
-
* only works for static vats, not dynamic).
|
|
478
|
-
*
|
|
479
|
-
* This will cause state changes in the swing-store (specifically
|
|
480
|
-
* marking the vat as terminated, and rejection all its
|
|
481
|
-
* outstanding promises), which must be committed before they will
|
|
482
|
-
* be durable. Either call `hostStorage.commit()` immediately
|
|
483
|
-
* after calling this, or call `controller.run()` and *then*
|
|
484
|
-
* `hostStorage.commit()` as you would normally do in response to
|
|
485
|
-
* other I/O or timer activity.
|
|
486
|
-
*
|
|
487
|
-
* The first `controller.run()` after this call will delete all
|
|
488
|
-
* the old vat's state at once, unless you use a
|
|
489
|
-
* [`runPolicy`](../../docs/run-policy.md) to rate-limit cleanups.
|
|
490
|
-
*
|
|
491
|
-
* @param {VatID} vatID
|
|
492
|
-
* @param {SwingSetCapData} reasonCD
|
|
493
|
-
*/
|
|
385
|
+
await kernel.installBundle(bundleID, bundle);
|
|
386
|
+
return bundleID;
|
|
387
|
+
}
|
|
494
388
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
389
|
+
// the kernel won't leak our objects into the Vats, we must do
|
|
390
|
+
// the same in this wrapper
|
|
391
|
+
return harden({
|
|
392
|
+
log(str) {
|
|
393
|
+
kernel.log(str);
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
writeSlogObject,
|
|
397
|
+
|
|
398
|
+
slogDuration,
|
|
399
|
+
|
|
400
|
+
dump() {
|
|
401
|
+
return deepCopyJsonable(kernel.dump());
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
verboseDebugMode(flag) {
|
|
405
|
+
kernel.kdebugEnable(flag);
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
validateAndInstallBundle,
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Run the kernel until the policy says to stop, or the queue is empty.
|
|
412
|
+
*
|
|
413
|
+
* @param {RunPolicy} [policy] - a RunPolicy to limit the work being done
|
|
414
|
+
* @returns {Promise<number>} The number of cranks that were executed.
|
|
415
|
+
*/
|
|
416
|
+
async run(policy) {
|
|
417
|
+
return kernel.run(policy);
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
async step() {
|
|
421
|
+
return kernel.step();
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
async shutdown() {
|
|
425
|
+
return kernel.shutdown();
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
reapAllVats() {
|
|
429
|
+
kernel.reapAllVats();
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
changeKernelOptions(options) {
|
|
433
|
+
kernel.changeKernelOptions(options);
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
getStats() {
|
|
437
|
+
return kernel.getStats();
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
getStatus() {
|
|
441
|
+
return deepCopyJsonable(kernel.getStatus());
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
getActivityhash() {
|
|
445
|
+
return kernelStorage.getActivityhash();
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
// everything beyond here is for tests, and everything should be migrated
|
|
449
|
+
// to be on this 'debug' object to make that clear
|
|
450
|
+
|
|
451
|
+
debug: {
|
|
452
|
+
addDeviceHook: kernel.addDeviceHook,
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
pinVatRoot(vatName) {
|
|
456
|
+
const vatID = kernel.vatNameToID(vatName);
|
|
457
|
+
const kref = kernel.getRootObject(vatID);
|
|
458
|
+
kernel.pinObject(kref);
|
|
459
|
+
kernelStorage.emitCrankHashes();
|
|
460
|
+
return kref;
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
kpRegisterInterest(kpid) {
|
|
464
|
+
return kernel.kpRegisterInterest(kpid);
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
kpStatus(kpid) {
|
|
468
|
+
return kernel.kpStatus(kpid);
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
kpResolution(kpid, options) {
|
|
472
|
+
const result = kernel.kpResolution(kpid, options);
|
|
473
|
+
// kpResolution does DB write (changes refcounts) so we need emitCrankHashes here
|
|
474
|
+
kernelStorage.emitCrankHashes();
|
|
475
|
+
return result;
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
vatNameToID(vatName) {
|
|
479
|
+
return kernel.vatNameToID(vatName);
|
|
480
|
+
},
|
|
481
|
+
deviceNameToID(deviceName) {
|
|
482
|
+
return kernel.deviceNameToID(deviceName);
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
injectQueuedUpgradeEvents: () => kernel.injectQueuedUpgradeEvents(),
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Queue a method call into the named vat
|
|
489
|
+
*
|
|
490
|
+
* @param {string} vatName
|
|
491
|
+
* @param {string|symbol} method
|
|
492
|
+
* @param {unknown[]} args
|
|
493
|
+
* @param {ResolutionPolicy} resultPolicy
|
|
494
|
+
*/
|
|
495
|
+
queueToVatRoot(vatName, method, args = [], resultPolicy = 'ignore') {
|
|
496
|
+
const vatID = kernel.vatNameToID(vatName);
|
|
497
|
+
if (typeof method !== 'symbol') {
|
|
498
|
+
assert.typeof(method, 'string');
|
|
499
|
+
}
|
|
500
|
+
const kref = kernel.getRootObject(vatID);
|
|
501
|
+
const kpid = kernel.queueToKref(kref, method, args, resultPolicy);
|
|
502
|
+
if (kpid) {
|
|
503
|
+
kernel.kpRegisterInterest(kpid);
|
|
504
|
+
}
|
|
505
|
+
kernelStorage.emitCrankHashes();
|
|
506
|
+
return kpid;
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Queue a method call to an object represented by a kmarshal token
|
|
511
|
+
*
|
|
512
|
+
* @param {any} target
|
|
513
|
+
* @param {string|symbol} method
|
|
514
|
+
* @param {unknown[]} args
|
|
515
|
+
* @param {ResolutionPolicy} resultPolicy
|
|
516
|
+
*/
|
|
517
|
+
queueToVatObject(target, method, args = [], resultPolicy = 'ignore') {
|
|
518
|
+
const targetKref = krefOf(target);
|
|
519
|
+
assert.typeof(targetKref, 'string');
|
|
520
|
+
if (typeof method !== 'symbol') {
|
|
521
|
+
assert.typeof(method, 'string');
|
|
522
|
+
}
|
|
523
|
+
const kpid = kernel.queueToKref(targetKref, method, args, resultPolicy);
|
|
524
|
+
if (kpid) {
|
|
525
|
+
kernel.kpRegisterInterest(kpid);
|
|
526
|
+
}
|
|
527
|
+
kernelStorage.emitCrankHashes();
|
|
528
|
+
return kpid;
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
upgradeStaticVat(vatName, shouldPauseFirst, bundleID, options = {}) {
|
|
532
|
+
const vatID = kernel.vatNameToID(vatName);
|
|
533
|
+
let pauseTarget = null;
|
|
534
|
+
if (shouldPauseFirst) {
|
|
535
|
+
pauseTarget = kslot(kernel.getRootObject(vatID));
|
|
536
|
+
}
|
|
537
|
+
if (!options.upgradeMessage) {
|
|
538
|
+
options.upgradeMessage = `vat ${vatName} upgraded`;
|
|
539
|
+
}
|
|
540
|
+
const result = controller.queueToVatRoot(
|
|
541
|
+
'vatAdmin',
|
|
542
|
+
'upgradeStaticVat',
|
|
543
|
+
[vatID, pauseTarget, bundleID, options],
|
|
544
|
+
'ignore',
|
|
545
|
+
);
|
|
546
|
+
// no emitCrankHashes here because queueToVatRoot did that
|
|
547
|
+
return result;
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* terminate a vat by ID
|
|
552
|
+
*
|
|
553
|
+
* This allows the host app to terminate any vat. The effect is
|
|
554
|
+
* equivalent to the holder of the vat's `adminNode` calling
|
|
555
|
+
* `E(adminNode).terminateWithFailure(reason)`, or the vat itself
|
|
556
|
+
* calling `vatPowers.exitVatWithFailure(reason)`. It accepts a
|
|
557
|
+
* reason capdata structure (use 'kser()' to build one), which
|
|
558
|
+
* will be included in rejection data for the promise available to
|
|
559
|
+
* `E(adminNode).done()`, just like the internal termination APIs.
|
|
560
|
+
* Note that no slots/krefs are allowed in 'reason' when
|
|
561
|
+
* terminating the vat externally.
|
|
562
|
+
*
|
|
563
|
+
* This is a superpower available only from the host app, not from
|
|
564
|
+
* within vats, since `vatID` is merely a string and can be forged
|
|
565
|
+
* trivially. The host app is responsible for supplying the right
|
|
566
|
+
* vatID to kill, by looking at the database or logs (note that
|
|
567
|
+
* vats do not know their own vatID, and `controller.vatNameToID`
|
|
568
|
+
* only works for static vats, not dynamic).
|
|
569
|
+
*
|
|
570
|
+
* This will cause state changes in the swing-store (specifically
|
|
571
|
+
* marking the vat as terminated, and rejection all its
|
|
572
|
+
* outstanding promises), which must be committed before they will
|
|
573
|
+
* be durable. Either call `hostStorage.commit()` immediately
|
|
574
|
+
* after calling this, or call `controller.run()` and *then*
|
|
575
|
+
* `hostStorage.commit()` as you would normally do in response to
|
|
576
|
+
* other I/O or timer activity.
|
|
577
|
+
*
|
|
578
|
+
* The first `controller.run()` after this call will delete all
|
|
579
|
+
* the old vat's state at once, unless you use a
|
|
580
|
+
* [`runPolicy`](../../docs/run-policy.md) to rate-limit cleanups.
|
|
581
|
+
*
|
|
582
|
+
* @param {VatID} vatID
|
|
583
|
+
* @param {SwingSetCapData} reasonCD
|
|
584
|
+
*/
|
|
585
|
+
|
|
586
|
+
terminateVat(vatID, reasonCD) {
|
|
587
|
+
insistCapData(reasonCD);
|
|
588
|
+
assert(reasonCD.slots.length === 0, 'no slots allowed in reason');
|
|
589
|
+
kernel.terminateVatExternally(vatID, reasonCD);
|
|
590
|
+
},
|
|
591
|
+
});
|
|
500
592
|
});
|
|
501
593
|
|
|
502
|
-
writeSlogObject({ type: 'kernel-init-finish' });
|
|
503
|
-
|
|
504
594
|
return controller;
|
|
505
595
|
}
|
|
506
596
|
/** @typedef {EReturn<typeof makeSwingsetController>} SwingsetController */
|