@agoric/swingset-vat 0.33.0-u17.1 → 0.33.0-u18.1
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 +31 -31
- package/src/controller/controller.js +10 -10
- package/src/controller/initializeKernel.js +0 -2
- package/src/controller/initializeSwingset.js +72 -63
- package/src/controller/upgradeSwingset.js +179 -36
- package/src/devices/bridge/device-bridge.js +3 -2
- package/src/devices/lib/deviceTools.js +0 -1
- package/src/devices/mailbox/mailbox.js +76 -43
- package/src/index.js +3 -0
- package/src/kernel/deviceTranslator.js +1 -1
- package/src/kernel/gc-actions.js +2 -3
- package/src/kernel/kernel.js +59 -21
- package/src/kernel/state/kernelKeeper.js +230 -128
- package/src/kernel/state/vatKeeper.js +74 -38
- package/src/kernel/vat-warehouse.js +22 -16
- package/src/kernel/vatTranslator.js +7 -3
- package/src/supervisors/subprocess-node/supervisor-subprocess-node.js +1 -0
- package/src/typeGuards.js +3 -2
- package/src/types-external.js +9 -1
- package/src/types-internal.js +11 -0
- package/src/vats/comms/delivery.js +0 -2
- package/src/vats/comms/state.js +0 -4
- package/src/vats/timer/vat-timer.js +0 -2
- package/src/vats/vat-admin/vat-vat-admin.js +0 -4
- package/tools/baggage-check.js +0 -2
- package/tools/bootstrap-dvo-test.js +0 -1
- package/tools/bootstrap-relay.js +9 -0
- package/tools/prepare-strict-test-env-ava.js +19 -0
- package/tools/run-utils.js +11 -4
- package/tools/vat-puppet.js +111 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-vat",
|
|
3
|
-
"version": "0.33.0-
|
|
3
|
+
"version": "0.33.0-u18.1",
|
|
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-
|
|
31
|
-
"@agoric/kmarshal": "^0.1.1-
|
|
32
|
-
"@agoric/store": "^0.9.3-
|
|
33
|
-
"@agoric/swing-store": "^0.
|
|
34
|
-
"@agoric/swingset-liveslots": "^0.10.3-
|
|
35
|
-
"@agoric/swingset-xsnap-supervisor": "^0.10.3-
|
|
36
|
-
"@agoric/time": "^0.3.3-
|
|
37
|
-
"@agoric/vat-data": "^0.5.3-
|
|
38
|
-
"@agoric/xsnap": "^0.14.
|
|
39
|
-
"@
|
|
40
|
-
"@endo/
|
|
41
|
-
"@endo/
|
|
42
|
-
"@endo/
|
|
43
|
-
"@endo/
|
|
44
|
-
"@endo/
|
|
45
|
-
"@endo/
|
|
46
|
-
"@endo/
|
|
47
|
-
"@endo/
|
|
48
|
-
"@endo/
|
|
49
|
-
"@endo/
|
|
50
|
-
"@endo/
|
|
51
|
-
"@endo/
|
|
52
|
-
"@endo/
|
|
53
|
-
"@endo/
|
|
54
|
-
"@endo/
|
|
55
|
-
"@endo/
|
|
56
|
-
"@endo/
|
|
57
|
-
"@endo/zip": "^1.0.7",
|
|
30
|
+
"@agoric/internal": "^0.4.0-u18.1",
|
|
31
|
+
"@agoric/kmarshal": "^0.1.1-u18.1",
|
|
32
|
+
"@agoric/store": "^0.9.3-u18.1",
|
|
33
|
+
"@agoric/swing-store": "^0.10.0-u18.1",
|
|
34
|
+
"@agoric/swingset-liveslots": "^0.10.3-u18.1",
|
|
35
|
+
"@agoric/swingset-xsnap-supervisor": "^0.10.3-u18.1",
|
|
36
|
+
"@agoric/time": "^0.3.3-u18.1",
|
|
37
|
+
"@agoric/vat-data": "^0.5.3-u18.1",
|
|
38
|
+
"@agoric/xsnap-lockdown": "^0.14.1-u18.1",
|
|
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":
|
|
104
|
+
"atLeast": 76.28
|
|
105
105
|
},
|
|
106
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "f8c45b8a2e29a51522a81a6692af25b2d7f6b50f"
|
|
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
|
|
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
|
|
337
|
+
return deepCopyJsonable(kernel.getStats());
|
|
344
338
|
},
|
|
345
339
|
|
|
346
340
|
getStatus() {
|
|
347
|
-
return
|
|
341
|
+
return deepCopyJsonable(kernel.getStatus());
|
|
348
342
|
},
|
|
349
343
|
|
|
350
344
|
getActivityhash() {
|
|
@@ -366,6 +360,10 @@ export async function makeSwingsetController(
|
|
|
366
360
|
return kref;
|
|
367
361
|
},
|
|
368
362
|
|
|
363
|
+
kpRegisterInterest(kpid) {
|
|
364
|
+
return kernel.kpRegisterInterest(kpid);
|
|
365
|
+
},
|
|
366
|
+
|
|
369
367
|
kpStatus(kpid) {
|
|
370
368
|
return kernel.kpStatus(kpid);
|
|
371
369
|
},
|
|
@@ -384,6 +382,8 @@ export async function makeSwingsetController(
|
|
|
384
382
|
return kernel.deviceNameToID(deviceName);
|
|
385
383
|
},
|
|
386
384
|
|
|
385
|
+
injectQueuedUpgradeEvents: () => kernel.injectQueuedUpgradeEvents(),
|
|
386
|
+
|
|
387
387
|
/**
|
|
388
388
|
* Queue a method call into the named vat
|
|
389
389
|
*
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* eslint-env node */
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
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 {
|
|
193
|
-
* @param {
|
|
194
|
-
* config file
|
|
195
|
-
* @param {
|
|
196
|
-
*
|
|
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(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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.
|
|
210
|
-
|
|
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
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* @
|
|
229
|
-
*
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
* @
|
|
234
|
-
*
|
|
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
|
-
|
|
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 =
|
|
326
|
+
config = deepCopyJsonable(config);
|
|
318
327
|
if (!config.bundles) {
|
|
319
328
|
config.bundles = {};
|
|
320
329
|
}
|
|
@@ -1,14 +1,47 @@
|
|
|
1
|
+
import { Fail } from '@endo/errors';
|
|
1
2
|
import {
|
|
2
3
|
DEFAULT_REAP_DIRT_THRESHOLD_KEY,
|
|
3
4
|
DEFAULT_GC_KREFS_PER_BOYD,
|
|
4
5
|
getAllDynamicVats,
|
|
5
6
|
getAllStaticVats,
|
|
7
|
+
incrementReferenceCount,
|
|
8
|
+
readQueue,
|
|
6
9
|
} from '../kernel/state/kernelKeeper.js';
|
|
10
|
+
import { enumeratePrefixedKeys } from '../kernel/state/storageHelper.js';
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @import {ReapDirtThreshold, RunQueueEvent} from '../types-internal.js';
|
|
14
|
+
*/
|
|
15
|
+
|
|
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
|
+
};
|
|
11
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
|
+
) => {
|
|
12
45
|
// schema v0:
|
|
13
46
|
// Each vat has a `vNN.reapInterval` and `vNN.reapCountdown`.
|
|
14
47
|
// vNN.options has a `.reapInterval` property (however it was not
|
|
@@ -25,15 +58,10 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
|
|
|
25
58
|
// `defaultReapDirtThreshold`)
|
|
26
59
|
|
|
27
60
|
const reapDirtKey = `${vatID}.reapDirt`;
|
|
28
|
-
|
|
29
|
-
assert(kvStore.has(oldReapIntervalKey), oldReapIntervalKey);
|
|
30
|
-
assert(kvStore.has(oldReapCountdownKey), oldReapCountdownKey);
|
|
31
61
|
assert(!kvStore.has(reapDirtKey), reapDirtKey);
|
|
32
62
|
|
|
33
|
-
const reapIntervalString =
|
|
34
|
-
const reapCountdownString =
|
|
35
|
-
assert(reapIntervalString !== undefined);
|
|
36
|
-
assert(reapCountdownString !== undefined);
|
|
63
|
+
const reapIntervalString = getRequired(oldReapIntervalKey);
|
|
64
|
+
const reapCountdownString = getRequired(oldReapCountdownKey);
|
|
37
65
|
|
|
38
66
|
const intervalIsNever = reapIntervalString === 'never';
|
|
39
67
|
const countdownIsNever = reapCountdownString === 'never';
|
|
@@ -54,8 +82,11 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
|
|
|
54
82
|
threshold.never = true;
|
|
55
83
|
} else {
|
|
56
84
|
// deduce delivery count from old countdown values
|
|
57
|
-
const reapInterval =
|
|
58
|
-
const reapCountdown =
|
|
85
|
+
const reapInterval = mustParseInt(reapIntervalString, oldReapIntervalKey);
|
|
86
|
+
const reapCountdown = mustParseInt(
|
|
87
|
+
reapCountdownString,
|
|
88
|
+
oldReapCountdownKey,
|
|
89
|
+
);
|
|
59
90
|
const deliveries = reapInterval - reapCountdown;
|
|
60
91
|
reapDirt.deliveries = Math.max(deliveries, 0); // just in case
|
|
61
92
|
if (reapInterval !== defaultReapDirtThreshold.deliveries) {
|
|
@@ -68,7 +99,7 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
|
|
|
68
99
|
kvStore.set(reapDirtKey, JSON.stringify(reapDirt));
|
|
69
100
|
|
|
70
101
|
// Update options to use the new schema.
|
|
71
|
-
const options = JSON.parse(
|
|
102
|
+
const options = JSON.parse(getRequired(vatOptionsKey));
|
|
72
103
|
delete options.reapInterval;
|
|
73
104
|
options.reapDirtThreshold = threshold;
|
|
74
105
|
kvStore.set(vatOptionsKey, JSON.stringify(options));
|
|
@@ -92,16 +123,15 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
|
|
|
92
123
|
* `hostStorage.commit()` afterwards.
|
|
93
124
|
*
|
|
94
125
|
* @param {SwingStoreKernelStorage} kernelStorage
|
|
95
|
-
* @returns {
|
|
126
|
+
* @returns {{ modified: boolean }}
|
|
96
127
|
*/
|
|
97
128
|
export const upgradeSwingset = kernelStorage => {
|
|
98
129
|
const { kvStore } = kernelStorage;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
let version = Number(vstring);
|
|
130
|
+
/** @type {RunQueueEvent[]} */
|
|
131
|
+
const upgradeEvents = [];
|
|
132
|
+
const vstring = kvStore.get('version');
|
|
133
|
+
const version = Number(vstring) || 0;
|
|
134
|
+
let newVersion;
|
|
105
135
|
|
|
106
136
|
/**
|
|
107
137
|
* @param {string} key
|
|
@@ -156,11 +186,7 @@ export const upgradeSwingset = kernelStorage => {
|
|
|
156
186
|
assert(kvStore.has(oldDefaultReapIntervalKey));
|
|
157
187
|
assert(!kvStore.has(DEFAULT_REAP_DIRT_THRESHOLD_KEY));
|
|
158
188
|
|
|
159
|
-
/**
|
|
160
|
-
* @typedef { import('../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
|
|
161
|
-
*/
|
|
162
|
-
|
|
163
|
-
/** @type ReapDirtThreshold */
|
|
189
|
+
/** @type {ReapDirtThreshold} */
|
|
164
190
|
const threshold = {
|
|
165
191
|
deliveries: 'never',
|
|
166
192
|
gcKrefs: 'never',
|
|
@@ -169,9 +195,7 @@ export const upgradeSwingset = kernelStorage => {
|
|
|
169
195
|
|
|
170
196
|
const oldValue = getRequired(oldDefaultReapIntervalKey);
|
|
171
197
|
if (oldValue !== 'never') {
|
|
172
|
-
|
|
173
|
-
assert.typeof(value, 'number');
|
|
174
|
-
threshold.deliveries = value;
|
|
198
|
+
threshold.deliveries = mustParseInt(oldValue, oldDefaultReapIntervalKey);
|
|
175
199
|
// if BOYD wasn't turned off entirely (eg
|
|
176
200
|
// defaultReapInterval='never', which only happens in unit
|
|
177
201
|
// tests), then pretend we wanted a gcKrefs= threshold all
|
|
@@ -186,27 +210,146 @@ export const upgradeSwingset = kernelStorage => {
|
|
|
186
210
|
|
|
187
211
|
// now upgrade all vats
|
|
188
212
|
for (const [_name, vatID] of getAllStaticVats(kvStore)) {
|
|
189
|
-
upgradeVatV0toV1(kvStore, threshold, vatID);
|
|
213
|
+
upgradeVatV0toV1(kvStore, getRequired, threshold, vatID);
|
|
190
214
|
}
|
|
191
215
|
for (const vatID of getAllDynamicVats(getRequired)) {
|
|
192
|
-
upgradeVatV0toV1(kvStore, threshold, vatID);
|
|
216
|
+
upgradeVatV0toV1(kvStore, getRequired, threshold, vatID);
|
|
193
217
|
}
|
|
194
218
|
|
|
195
|
-
|
|
196
|
-
version = 1;
|
|
219
|
+
newVersion = 1;
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
if (version < 2) {
|
|
200
223
|
// schema v2: add vats.terminated = []
|
|
201
224
|
assert(!kvStore.has('vats.terminated'));
|
|
202
225
|
kvStore.set('vats.terminated', JSON.stringify([]));
|
|
203
|
-
|
|
204
|
-
|
|
226
|
+
newVersion = 2;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (version < 3) {
|
|
230
|
+
// v3 means that we've completed remediation for bug #9039
|
|
231
|
+
console.log(`Starting remediation of bug #9039`);
|
|
232
|
+
|
|
233
|
+
// find all terminated vats
|
|
234
|
+
const terminated = new Set(JSON.parse(getRequired('vats.terminated')));
|
|
235
|
+
|
|
236
|
+
// find all live vats
|
|
237
|
+
const allVatIDs = [];
|
|
238
|
+
for (const [_name, vatID] of getAllStaticVats(kvStore)) {
|
|
239
|
+
if (!terminated.has(vatID)) {
|
|
240
|
+
allVatIDs.push(vatID);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const vatID of getAllDynamicVats(getRequired)) {
|
|
244
|
+
if (!terminated.has(vatID)) {
|
|
245
|
+
allVatIDs.push(vatID);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// find all pending notifies
|
|
250
|
+
const notifies = new Map(); // .get(kpid) = [vatIDs..];
|
|
251
|
+
for (const name of ['runQueue', 'acceptanceQueue']) {
|
|
252
|
+
for (const rq of readQueue(name, getRequired)) {
|
|
253
|
+
if (rq.type === 'notify') {
|
|
254
|
+
const { vatID, kpid } = rq;
|
|
255
|
+
assert(vatID);
|
|
256
|
+
assert(kpid);
|
|
257
|
+
let vats = notifies.get(kpid);
|
|
258
|
+
if (!vats) {
|
|
259
|
+
vats = [];
|
|
260
|
+
notifies.set(kpid, vats);
|
|
261
|
+
}
|
|
262
|
+
vats.push(vatID);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
console.log(` - pending notifies:`, notifies);
|
|
267
|
+
|
|
268
|
+
// cache of known-settled kpids: will grow to num(kpids)
|
|
269
|
+
const KPIDStatus = new Map();
|
|
270
|
+
const isSettled = kpid => {
|
|
271
|
+
if (KPIDStatus.has(kpid)) {
|
|
272
|
+
return KPIDStatus.get(kpid);
|
|
273
|
+
}
|
|
274
|
+
const state = kvStore.get(`${kpid}.state`);
|
|
275
|
+
// missing state means the kpid is deleted somehow, shouldn't happen
|
|
276
|
+
state || Fail`${kpid}.state is missing`;
|
|
277
|
+
const settled = state !== 'unresolved';
|
|
278
|
+
KPIDStatus.set(kpid, settled);
|
|
279
|
+
return settled;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// walk vNN.c.kpNN for all vats, for each one check the
|
|
283
|
+
// kpNN.state, for the settled ones check for a pending notify,
|
|
284
|
+
// record the ones without a pending notify
|
|
285
|
+
|
|
286
|
+
const buggyKPIDs = []; // [kpid, vatID]
|
|
287
|
+
for (const vatID of allVatIDs) {
|
|
288
|
+
const prefix = `${vatID}.c.`;
|
|
289
|
+
const len = prefix.length;
|
|
290
|
+
const ckpPrefix = `${vatID}.c.kp`;
|
|
291
|
+
for (const key of enumeratePrefixedKeys(kvStore, ckpPrefix)) {
|
|
292
|
+
const kpid = key.slice(len);
|
|
293
|
+
if (isSettled(kpid)) {
|
|
294
|
+
const n = notifies.get(kpid);
|
|
295
|
+
if (!n || !n.includes(vatID)) {
|
|
296
|
+
// there is no pending notify
|
|
297
|
+
buggyKPIDs.push([kpid, vatID]);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
console.log(` - found ${buggyKPIDs.length} buggy kpids, enqueueing fixes`);
|
|
303
|
+
|
|
304
|
+
// now fix it. The bug means we failed to delete the c-list entry
|
|
305
|
+
// and decref it back when the promise was rejected. That decref
|
|
306
|
+
// would have pushed the kpid onto maybeFreeKrefs, which would
|
|
307
|
+
// have triggered a refcount check at end-of-crank, which might
|
|
308
|
+
// have deleted the promise records (if nothing else was
|
|
309
|
+
// referencing the promise, like arguments in messages enqueued to
|
|
310
|
+
// unresolved promises, or something transient on the
|
|
311
|
+
// run-queue). Deleting those promise records might have decreffed
|
|
312
|
+
// krefs in the rejection data (although in general 9039 rejects
|
|
313
|
+
// those promises with non-slot-bearing DisconnectionObjects).
|
|
314
|
+
//
|
|
315
|
+
// To avoid duplicating a lot of kernel code inside this upgrade
|
|
316
|
+
// handler, we do the simplest possible thing: enqueue a notify to
|
|
317
|
+
// the upgraded vat for all these leftover promises. The new vat
|
|
318
|
+
// incarnation will ignore it (they don't recognize the vpid), but
|
|
319
|
+
// the dispatch.notify() delivery will clear the c-list and decref
|
|
320
|
+
// the kpid, and will trigger all the usual GC work. Note that
|
|
321
|
+
// these notifies will be delivered before any activity the host
|
|
322
|
+
// app might trigger for e.g. a chain upgrade, but they should not
|
|
323
|
+
// cause userspace-visible behavior (non-slot-bearing rejection
|
|
324
|
+
// data means no other vat will even get a gc-action delivery:
|
|
325
|
+
// only the upgraded vat will see anything, and those deliveries
|
|
326
|
+
// won't make it past liveslots).
|
|
327
|
+
|
|
328
|
+
let count = 0;
|
|
329
|
+
for (const [kpid, vatID] of buggyKPIDs) {
|
|
330
|
+
// account for the reference to this kpid in upgradeEvents
|
|
331
|
+
incrementReferenceCount(getRequired, kvStore, kpid, `enq|notify`);
|
|
332
|
+
upgradeEvents.push({ type: 'notify', vatID, kpid });
|
|
333
|
+
count += 1;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log(` - #9039 remediation complete, ${count} notifies to inject`);
|
|
337
|
+
newVersion = 3;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const modified = newVersion !== undefined;
|
|
341
|
+
|
|
342
|
+
if (upgradeEvents.length) {
|
|
343
|
+
assert(modified);
|
|
344
|
+
// stash until host calls controller.injectQueuedUpgradeEvents()
|
|
345
|
+
const oldEvents = JSON.parse(kvStore.get('upgradeEvents') || '[]');
|
|
346
|
+
const events = [...oldEvents, ...upgradeEvents];
|
|
347
|
+
kvStore.set('upgradeEvents', JSON.stringify(events));
|
|
205
348
|
}
|
|
206
349
|
|
|
207
350
|
if (modified) {
|
|
208
|
-
kvStore.set('version', `${
|
|
351
|
+
kvStore.set('version', `${newVersion}`);
|
|
209
352
|
}
|
|
210
|
-
return modified;
|
|
353
|
+
return harden({ modified });
|
|
211
354
|
};
|
|
212
355
|
harden(upgradeSwingset);
|
|
@@ -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
|
|
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 =
|
|
36
|
+
const safeArgs = deepCopyJsonable(args);
|
|
36
37
|
try {
|
|
37
38
|
SO(inboundHandler).inbound(...harden(safeArgs));
|
|
38
39
|
} catch (e) {
|