diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index 2c24c722146..25ef207246a 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -34,10 +34,6 @@ function makeConsole(tag) { return harden(cons); } -const ADMIN_DEVICE_PATH = require.resolve('./kernel/vatAdmin/vatAdmin-src'); -const ADMIN_VAT_PATH = require.resolve('./kernel/vatAdmin/vatAdminWrapper'); -const KERNEL_SOURCE_PATH = require.resolve('./kernel/kernel.js'); - function byName(a, b) { if (a.name < b.name) { return -1; @@ -205,33 +201,66 @@ export function loadSwingsetConfigFile(configPath) { } } +/** + * Build the kernel source bundles. + * + */ +export async function buildKernelBundles() { + // this takes 2.7s on my computer + const sources = { + kernel: require.resolve('./kernel/kernel.js'), + adminDevice: require.resolve('./kernel/vatAdmin/vatAdmin-src'), + adminVat: require.resolve('./kernel/vatAdmin/vatAdminWrapper'), + comms: require.resolve('./vats/comms'), + vattp: require.resolve('./vats/vat-tp'), + timer: require.resolve('./vats/vat-timerWrapper'), + }; + const kernelBundles = {}; + for (const name of Object.keys(sources)) { + // this was harder to read with Promise.all + // eslint-disable-next-line no-await-in-loop + kernelBundles[name] = await bundleSource(sources[name]); + } + return harden(kernelBundles); +} + export async function buildVatController( config, argv = [], runtimeOptions = {}, ) { - const { debugPrefix = '', verbose = false } = runtimeOptions; + // build console early so we can add console.log to diagnose early problems + const { debugPrefix = '' } = runtimeOptions; if (typeof Compartment === 'undefined') { throw Error('SES must be installed before calling buildVatController'); } // eslint-disable-next-line no-shadow const console = makeConsole(`${debugPrefix}SwingSet:controller`); + // We can harden this 'console' because it's new, but if we were using the + // original 'console' object (which has a unique prototype), we'd have to + // harden(Object.getPrototypeOf(console)); + // see https://github.com/Agoric/SES-shim/issues/292 for details + harden(console); + + const { + verbose = false, + kernelBundles = await buildKernelBundles(), + } = runtimeOptions; // FIXME: Put this somewhere better. process.on('unhandledRejection', e => console.error('UnhandledPromiseRejectionWarning:', e), ); - // https://github.com/Agoric/SES-shim/issues/292 - harden(Object.getPrototypeOf(console)); - harden(console); - if (config.bootstrap && argv) { - if (!config.vats[config.bootstrap].parameters) { - config.vats[config.bootstrap].parameters = {}; - } - config.vats[config.bootstrap].parameters.argv = argv; + // move 'argv' into parameters on the bootstrap vat, without changing the + // original config (which might be hardened or shared) + const bootstrapName = config.bootstrap; + const parameters = { ...config.vats[bootstrapName].parameters, argv }; + const bootstrapVat = { ...config.vats[bootstrapName], parameters }; + const vats = { ...config.vats, [bootstrapName]: bootstrapVat }; + config = { ...config, vats }; } function kernelRequire(what) { @@ -248,8 +277,7 @@ export async function buildVatController( throw Error(`kernelRequire unprepared to satisfy require(${what})`); } } - const kernelSource = await bundleSource(KERNEL_SOURCE_PATH); - const kernelNS = await importBundle(kernelSource, { + const kernelNS = await importBundle(kernelBundles.kernel, { filePrefix: 'kernel', endowments: { console: makeConsole(`${debugPrefix}SwingSet:kernel`), @@ -351,19 +379,15 @@ export async function buildVatController( } // the vatAdminDevice is given endowments by the kernel itself - const vatAdminVatBundle = await bundleSource(ADMIN_VAT_PATH); - kernel.addGenesisVat('vatAdmin', vatAdminVatBundle); - const vatAdminDeviceBundle = await bundleSource(ADMIN_DEVICE_PATH); - kernel.addVatAdminDevice(vatAdminDeviceBundle); + kernel.addGenesisVat('vatAdmin', kernelBundles.adminVat); + kernel.addVatAdminDevice(kernelBundles.adminDevice); // comms vat is added automatically, but TODO: bootstraps must still // connect it to vat-tp. TODO: test-message-patterns builds two comms and // two vattps, must handle somehow. - const commsVatSourcePath = require.resolve('./vats/comms'); - const commsVatBundle = await bundleSource(commsVatSourcePath); kernel.addGenesisVat( 'comms', - commsVatBundle, + kernelBundles.comms, {}, { enablePipelining: true, @@ -373,15 +397,11 @@ export async function buildVatController( // vat-tp is added automatically, but TODO: bootstraps must still connect // it to comms - const vatTPSourcePath = require.resolve('./vats/vat-tp'); - const vatTPBundle = await bundleSource(vatTPSourcePath); - kernel.addGenesisVat('vattp', vatTPBundle); + kernel.addGenesisVat('vattp', kernelBundles.vattp); // timer wrapper vat is added automatically, but TODO: bootstraps must // still provide a timer device, and connect it to the wrapper vat - const timerWrapperSourcePath = require.resolve('./vats/vat-timerWrapper'); - const timerWrapperBundle = await bundleSource(timerWrapperSourcePath); - kernel.addGenesisVat('timer', timerWrapperBundle); + kernel.addGenesisVat('timer', kernelBundles.timer); function addGenesisVat( name, diff --git a/packages/SwingSet/src/index.js b/packages/SwingSet/src/index.js index b71a668899b..ec0bbc5d430 100644 --- a/packages/SwingSet/src/index.js +++ b/packages/SwingSet/src/index.js @@ -2,6 +2,7 @@ export { loadBasedir, loadSwingsetConfigFile, buildVatController, + buildKernelBundles, } from './controller'; export { buildMailboxStateMap, buildMailbox } from './devices/mailbox'; diff --git a/packages/SwingSet/test/basedir-message-patterns/vat-a.js b/packages/SwingSet/test/basedir-message-patterns/vat-a.js index 6a1ddc8f3f2..4256c9192e8 100644 --- a/packages/SwingSet/test/basedir-message-patterns/vat-a.js +++ b/packages/SwingSet/test/basedir-message-patterns/vat-a.js @@ -17,7 +17,6 @@ export function buildRootObject(vatPowers) { }, async run(which) { - console.log(`running alice[${which}]`); await alice[which](); }, }); diff --git a/packages/SwingSet/test/test-devices.js b/packages/SwingSet/test/test-devices.js index a2a788bac7f..429047a58c4 100644 --- a/packages/SwingSet/test/test-devices.js +++ b/packages/SwingSet/test/test-devices.js @@ -4,7 +4,7 @@ import '@agoric/install-ses'; import test from 'ava'; import { initSwingStore, getAllState } from '@agoric/swing-store-simple'; -import { buildVatController } from '../src/index'; +import { buildVatController, buildKernelBundles } from '../src/index'; import { buildMailboxStateMap, buildMailbox } from '../src/devices/mailbox'; import buildCommand from '../src/devices/command'; @@ -16,6 +16,11 @@ function capargs(args, slots = []) { return capdata(JSON.stringify(args), slots); } +test.before(async t => { + const kernelBundles = await buildKernelBundles(); + t.context.data = { kernelBundles }; +}); + test('d0', async t => { const config = { bootstrap: 'bootstrap', @@ -27,7 +32,7 @@ test('d0', async t => { }, devices: [['d0', require.resolve('./files-devices/device-0'), {}]], }; - const c = await buildVatController(config); + const c = await buildVatController(config, [], t.context.data); await c.step(); // console.log(util.inspect(c.dump(), { depth: null })); t.deepEqual(JSON.parse(c.dump().log[0]), [ @@ -75,7 +80,7 @@ test('d1', async t => { ], ], }; - const c = await buildVatController(config); + const c = await buildVatController(config, [], t.context.data); await c.step(); c.queueToVatExport('bootstrap', 'o+0', 'step1', capargs([])); await c.step(); @@ -100,7 +105,7 @@ async function test2(t, mode) { }, devices: [['d2', require.resolve('./files-devices/device-2'), {}]], }; - const c = await buildVatController(config, [mode]); + const c = await buildVatController(config, [mode], t.context.data); await c.step(); if (mode === '1') { t.deepEqual(c.dump().log, ['calling d2.method1', 'method1 hello', 'done']); @@ -186,6 +191,7 @@ test('device state', async t => { // from bootstrap, and read it back. const c1 = await buildVatController(config, ['write+read'], { hostStorage: storage, + kernelBundles: t.context.data.kernelBundles, }); const d3 = c1.deviceNameToID('d3'); await c1.run(); @@ -208,7 +214,7 @@ test('mailbox outbound', async t => { devices: [['mailbox', mb.srcPath, mb.endowments]], }; - const c = await buildVatController(config, ['mailbox1']); + const c = await buildVatController(config, ['mailbox1'], t.context.data); await c.run(); t.deepEqual(s.exportToData(), { peer1: { @@ -248,7 +254,7 @@ test('mailbox inbound', async t => { let rc; - const c = await buildVatController(config, ['mailbox2']); + const c = await buildVatController(config, ['mailbox2'], t.context.data); await c.run(); rc = mb.deliverInbound( 'peer1', @@ -361,7 +367,7 @@ test('command broadcast', async t => { devices: [['command', cm.srcPath, cm.endowments]], }; - const c = await buildVatController(config, ['command1']); + const c = await buildVatController(config, ['command1'], t.context.data); await c.run(); t.deepEqual(broadcasts, [{ hello: 'everybody' }]); }); @@ -378,7 +384,7 @@ test('command deliver', async t => { devices: [['command', cm.srcPath, cm.endowments]], }; - const c = await buildVatController(config, ['command2']); + const c = await buildVatController(config, ['command2'], t.context.data); await c.run(); t.deepEqual(c.dump().log.length, 0); diff --git a/packages/SwingSet/test/test-message-patterns.js b/packages/SwingSet/test/test-message-patterns.js index 101d75b8f38..07e120de1f1 100644 --- a/packages/SwingSet/test/test-message-patterns.js +++ b/packages/SwingSet/test/test-message-patterns.js @@ -5,7 +5,8 @@ import '@agoric/install-ses'; import test from 'ava'; import path from 'path'; -import { buildVatController, loadBasedir } from '../src/index'; +import bundleSource from '@agoric/bundle-source'; +import { buildVatController, buildKernelBundles } from '../src/index'; import { buildLoopbox } from '../src/devices/loopbox'; import { buildPatterns } from './message-patterns'; @@ -38,13 +39,52 @@ async function runWithTrace(c) { } } -export async function runVatsLocally(t, name) { - console.log(`------ testing pattern (local) -- ${name}`); +test.before(async t => { + const kernelBundles = await buildKernelBundles(); const bdir = path.resolve(__dirname, 'basedir-message-patterns'); - const config = await loadBasedir(bdir); - config.bootstrap = 'bootstrap'; - config.vats.bootstrap = { sourceSpec: path.join(bdir, 'bootstrap-local.js') }; - const c = await buildVatController(config, [name]); + const bundleA = await bundleSource(path.resolve(bdir, 'vat-a.js')); + const bundleB = await bundleSource(path.resolve(bdir, 'vat-b.js')); + + const bootstrapLocal = path.resolve(bdir, 'bootstrap-local.js'); + const bundleLocal = await bundleSource(bootstrapLocal); + const localConfig = { + bootstrap: 'bootstrap', + vats: { + bootstrap: { bundle: bundleLocal }, + a: { bundle: bundleA }, + b: { bundle: bundleB }, + }, + }; + + const bootstrapComms = path.resolve(bdir, 'bootstrap-comms.js'); + const bundleComms = await bundleSource(bootstrapComms); + const moreComms = { + bundle: kernelBundles.comms, + creationOptions: { + enablePipelining: true, + enableSetup: true, + }, + }; + const moreVatTP = { bundle: kernelBundles.vattp }; + const commsConfig = { + bootstrap: 'bootstrap', + vats: { + bootstrap: { bundle: bundleComms }, + a: { bundle: bundleA }, + b: { bundle: bundleB }, + leftcomms: moreComms, + rightcomms: moreComms, + leftvattp: moreVatTP, + rightvattp: moreVatTP, + }, + }; + + t.context.data = { localConfig, commsConfig, kernelBundles }; +}); + +export async function runVatsLocally(t, name) { + const { localConfig: config, kernelBundles } = t.context.data; + const c = await buildVatController(config, [name], { kernelBundles }); // await runWithTrace(c); await c.run(); return c.dump().log; @@ -65,51 +105,26 @@ for (const name of Array.from(bp.patterns.keys()).sort()) { test.serial('local patterns', testLocalPattern, name); } -const commsSourcePath = require.resolve('../src/vats/comms'); -const vatTPSourcePath = require.resolve('../src/vats/vat-tp'); - -export async function runVatsInComms(t, enablePipelining, name) { - console.log(`------ testing pattern (comms) -- ${name}`); - const enableSetup = true; - const bdir = path.resolve(__dirname, 'basedir-message-patterns'); - const config = await loadBasedir(bdir); - config.bootstrap = 'bootstrap'; - config.vats.bootstrap = { sourceSpec: path.join(bdir, 'bootstrap-comms.js') }; - config.vats.leftcomms = { - sourceSpec: commsSourcePath, - creationOptions: { - enablePipelining, - enableSetup, - }, - }; - config.vats.rightcomms = { - sourceSpec: commsSourcePath, - creationOptions: { - enablePipelining, - enableSetup, - }, - }; - config.vats.leftvattp = { sourceSpec: vatTPSourcePath }; - config.vats.rightvattp = { sourceSpec: vatTPSourcePath }; +export async function runVatsInComms(t, name) { + const { commsConfig, kernelBundles } = t.context.data; const { passOneMessage, loopboxSrcPath, loopboxEndowments } = buildLoopbox( 'queued', ); - config.devices = [['loopbox', loopboxSrcPath, loopboxEndowments]]; - const c = await buildVatController(config, [name]); + const devices = [['loopbox', loopboxSrcPath, loopboxEndowments]]; + const config = { ...commsConfig, devices }; + const c = await buildVatController(config, [name], { kernelBundles }); // await runWithTrace(c); await c.run(); while (passOneMessage()) { await c.run(); } - console.log(`bootstrapResult`, c.bootstrapResult.status()); return c.dump().log; } async function testCommsPattern(t, name) { - const enablePipelining = true; - const logs = await runVatsInComms(t, enablePipelining, name); + const logs = await runVatsInComms(t, name); let expected; - if (enablePipelining && name in bp.expected_pipelined) { + if (name in bp.expected_pipelined) { expected = bp.expected_pipelined[name]; } else { expected = bp.expected[name]; diff --git a/packages/SwingSet/test/test-promises.js b/packages/SwingSet/test/test-promises.js index 15ca9ce60e0..ce8d9066ea2 100644 --- a/packages/SwingSet/test/test-promises.js +++ b/packages/SwingSet/test/test-promises.js @@ -1,13 +1,22 @@ import '@agoric/install-ses'; import test from 'ava'; import path from 'path'; -import { buildVatController, loadBasedir } from '../src/index'; +import { + buildVatController, + loadBasedir, + buildKernelBundles, +} from '../src/index'; const RETIRE_KPIDS = true; +test.before(async t => { + const kernelBundles = await buildKernelBundles(); + t.context.data = { kernelBundles }; +}); + test('flush', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['flush']); + const c = await buildVatController(config, ['flush'], t.context.data); // all promises should settle before c.step() fires await c.step(); t.deepEqual(c.dump().log, ['then1', 'then2']); @@ -15,7 +24,7 @@ test('flush', async t => { test('E() resolve', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['e-then']); + const c = await buildVatController(config, ['e-then'], t.context.data); await c.run(); t.deepEqual(c.dump().log, [ @@ -28,7 +37,7 @@ test('E() resolve', async t => { test('E(E(x).foo()).bar()', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['chain1']); + const c = await buildVatController(config, ['chain1'], t.context.data); /* while (true) { @@ -50,7 +59,7 @@ test('E(E(x).foo()).bar()', async t => { test('E(Promise.resolve(presence)).foo()', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['chain2']); + const c = await buildVatController(config, ['chain2'], t.context.data); await c.run(); t.deepEqual(c.dump().log, [ @@ -63,7 +72,7 @@ test('E(Promise.resolve(presence)).foo()', async t => { test('E(local).foo()', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['local1']); + const c = await buildVatController(config, ['local1'], t.context.data); await c.run(); t.deepEqual(c.dump().log, ['b.local1.finish', 'local.foo 1', 'b.resolved 2']); @@ -71,7 +80,7 @@ test('E(local).foo()', async t => { test('resolve-to-local', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['local2']); + const c = await buildVatController(config, ['local2'], t.context.data); await c.run(); t.deepEqual(c.dump().log, [ @@ -84,7 +93,7 @@ test('resolve-to-local', async t => { test('send-promise-resolve-to-local', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-promises')); - const c = await buildVatController(config, ['send-promise1']); + const c = await buildVatController(config, ['send-promise1'], t.context.data); await c.run(); t.deepEqual(c.dump().log, [ @@ -100,7 +109,11 @@ test('send-harden-promise-1', async t => { const config = await loadBasedir( path.resolve(__dirname, 'basedir-promises-2'), ); - const c = await buildVatController(config, ['harden-promise-1']); + const c = await buildVatController( + config, + ['harden-promise-1'], + t.context.data, + ); await c.run(); t.deepEqual(c.dump().log, [ @@ -119,7 +132,7 @@ test('send-harden-promise-1', async t => { test('circular promise resolution data', async t => { const config = await loadBasedir(path.resolve(__dirname, 'basedir-circular')); - const c = await buildVatController(config); + const c = await buildVatController(config, [], t.context.data); await c.run(); const expectedPromises = [ diff --git a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js index 756acc70f5c..9e328cb58da 100644 --- a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js +++ b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js @@ -7,7 +7,11 @@ import { getAllState, setAllState, } from '@agoric/swing-store-simple'; -import { buildVatController, loadSwingsetConfigFile } from '../../../src/index'; +import { + buildVatController, + loadSwingsetConfigFile, + buildKernelBundles, +} from '../../../src/index'; function capdata(body, slots = []) { return harden({ body, slots }); @@ -17,14 +21,15 @@ function capargs(args, slots = []) { return capdata(JSON.stringify(args), slots); } -function copy(data) { - return JSON.parse(JSON.stringify(data)); -} +test.before(async t => { + const kernelBundles = await buildKernelBundles(); + t.context.data = { kernelBundles }; +}); test('terminate', async t => { const configPath = path.resolve(__dirname, 'swingset-terminate.json'); const config = loadSwingsetConfigFile(configPath); - const controller = await buildVatController(config); + const controller = await buildVatController(config, [], t.context.data); t.is(controller.bootstrapResult.status(), 'pending'); await controller.run(); t.is(controller.bootstrapResult.status(), 'fulfilled'); @@ -55,8 +60,9 @@ test('dispatches to the dead do not harm kernel', async t => { const { storage: storage1 } = initSwingStore(); { - const c1 = await buildVatController(copy(config), [], { + const c1 = await buildVatController(config, [], { hostStorage: storage1, + kernelBundles: t.context.data.kernelBundles, }); await c1.run(); t.deepEqual(c1.bootstrapResult.resolution(), capargs('bootstrap done')); @@ -71,8 +77,9 @@ test('dispatches to the dead do not harm kernel', async t => { const { storage: storage2 } = initSwingStore(); setAllState(storage2, state1); { - const c2 = await buildVatController(copy(config), [], { + const c2 = await buildVatController(config, [], { hostStorage: storage2, + kernelBundles: t.context.data.kernelBundles, }); const r2 = c2.queueToVatExport( 'bootstrap', @@ -97,8 +104,9 @@ test('replay does not resurrect dead vat', async t => { const { storage: storage1 } = initSwingStore(); { - const c1 = await buildVatController(copy(config), [], { + const c1 = await buildVatController(config, [], { hostStorage: storage1, + kernelBundles: t.context.data.kernelBundles, }); await c1.run(); t.deepEqual(c1.bootstrapResult.resolution(), capargs('bootstrap done')); @@ -110,8 +118,9 @@ test('replay does not resurrect dead vat', async t => { const { storage: storage2 } = initSwingStore(); setAllState(storage2, state1); { - const c2 = await buildVatController(copy(config), [], { + const c2 = await buildVatController(config, [], { hostStorage: storage2, + kernelBundles: t.context.data.kernelBundles, }); await c2.run(); // ...which shouldn't run the second time through @@ -124,8 +133,9 @@ test('dead vat state removed', async t => { const config = loadSwingsetConfigFile(configPath); const { storage } = initSwingStore(); - const controller = await buildVatController(copy(config), [], { + const controller = await buildVatController(config, [], { hostStorage: storage, + kernelBundles: t.context.data.kernelBundles, }); await controller.run(); t.deepEqual(