From 56e706143ec386ff1244f4c392730c59b1eb9390 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 17 Jul 2020 22:29:32 -0700 Subject: [PATCH] fix(swingset): createVatDynamically option to disable metering refs #1307 --- packages/SwingSet/src/kernel/dynamicVat.js | 44 ++++++++++---- .../src/kernel/vatAdmin/vatAdmin-src.js | 4 +- .../src/kernel/vatAdmin/vatAdminWrapper.js | 4 +- .../metering/test-unmetered-dynamic-vat.js | 60 +++++++++++++++++++ .../test/metering/vat-load-dynamic.js | 4 +- 5 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 packages/SwingSet/test/metering/test-unmetered-dynamic-vat.js diff --git a/packages/SwingSet/src/kernel/dynamicVat.js b/packages/SwingSet/src/kernel/dynamicVat.js index f6157231ecd5..d55376bf0c06 100644 --- a/packages/SwingSet/src/kernel/dynamicVat.js +++ b/packages/SwingSet/src/kernel/dynamicVat.js @@ -27,23 +27,37 @@ export function makeDynamicVatCreator(stuff) { * defines the vat. This should be generated by calling bundle-source on a * module whose default export is makeRootObject(), which takes E as a * parameter and returns a root object. + * @param options an options bundle. The only option defined so far is + * 'metered', which defaults to 'true'. If 'true', the new dynamic vat is + * subject to a meter that limits the amount of computation and allocation + * that can occur during any given crank. Stack frames are limited as well. + * The meter is refilled between cranks, but if the meter ever underflows, + * the vat is terminated. If 'false', the vat is unmetered. * * @return { vatID } the vatID for a newly created vat. The success or * failure of the operation will be reported in a message to the admin vat, * citing this vatID */ - function createVatDynamically(vatSourceBundle) { - const vatID = allocateUnusedVatID(); + function createVatDynamically(vatSourceBundle, options = {}) { + const { metered = true, ...unknownOptions } = options; + if (Object.keys(unknownOptions).length) { + const msg = JSON.stringify(Object.keys(unknownOptions)); + throw Error(`createVatDynamically got unknown options ${msg}`); + } - // fail-stop: we refill the meter after each crank (in vatManager - // doProcess()), but if the vat exhausts its meter within a single crank, - // it will never run again. We set refillEachCrank:false because we want - // doProcess to do the refilling itself, so it can count the usage - const meterRecord = makeGetMeter({ - refillEachCrank: false, - refillIfExhausted: false, - }); + const vatID = allocateUnusedVatID(); + let meterRecord = null; + if (metered) { + // fail-stop: we refill the meter after each crank (in vatManager + // doProcess()), but if the vat exhausts its meter within a single crank, + // it will never run again. We set refillEachCrank:false because we want + // doProcess to do the refilling itself, so it can count the usage + meterRecord = makeGetMeter({ + refillEachCrank: false, + refillIfExhausted: false, + }); + } let terminated = false; @@ -80,9 +94,13 @@ export function makeDynamicVatCreator(stuff) { `createVatDynamically() requires bundle, not a plain string`, ); } - const getMeter = meterRecord.getMeter; - const inescapableTransforms = [src => transformMetering(src, getMeter)]; - const inescapableGlobalLexicals = { getMeter }; + const inescapableTransforms = []; + const inescapableGlobalLexicals = {}; + if (metered) { + const getMeter = meterRecord.getMeter; + inescapableTransforms.push(src => transformMetering(src, getMeter)); + inescapableGlobalLexicals.getMeter = getMeter; + } const vatNS = await importBundle(vatSourceBundle, { filePrefix: vatID, diff --git a/packages/SwingSet/src/kernel/vatAdmin/vatAdmin-src.js b/packages/SwingSet/src/kernel/vatAdmin/vatAdmin-src.js index 8e47aac95110..3e33e70e6713 100644 --- a/packages/SwingSet/src/kernel/vatAdmin/vatAdmin-src.js +++ b/packages/SwingSet/src/kernel/vatAdmin/vatAdmin-src.js @@ -33,8 +33,8 @@ export default function setup(syscall, state, helpers, endowments) { // Called by the wrapper vat to create a new vat. Gets a new ID from the // kernel's vat creator fn. Remember that the root object will arrive // separately. Clean up the outgoing and incoming arguments. - create(bundle) { - const vatID = kernelVatCreationFn(bundle); + create(bundle, options) { + const vatID = kernelVatCreationFn(bundle, options); return vatID; }, terminate(_vatID) { diff --git a/packages/SwingSet/src/kernel/vatAdmin/vatAdminWrapper.js b/packages/SwingSet/src/kernel/vatAdmin/vatAdminWrapper.js index b7e1ce9c9327..056476e080c7 100644 --- a/packages/SwingSet/src/kernel/vatAdmin/vatAdminWrapper.js +++ b/packages/SwingSet/src/kernel/vatAdmin/vatAdminWrapper.js @@ -21,8 +21,8 @@ export default function setup(syscall, state, helpers) { function createVatAdminService(vatAdminNode) { return harden({ - createVat(code) { - const vatID = D(vatAdminNode).create(code); + createVat(code, options) { + const vatID = D(vatAdminNode).create(code, options); const [promise, pendingRR] = producePRR(); pending.set(vatID, pendingRR); diff --git a/packages/SwingSet/test/metering/test-unmetered-dynamic-vat.js b/packages/SwingSet/test/metering/test-unmetered-dynamic-vat.js new file mode 100644 index 000000000000..9b5e40d42cc1 --- /dev/null +++ b/packages/SwingSet/test/metering/test-unmetered-dynamic-vat.js @@ -0,0 +1,60 @@ +/* global harden */ + +import '@agoric/install-metering-and-ses'; +import bundleSource from '@agoric/bundle-source'; +import tap from 'tap'; +import { buildVatController } from '../../src/index'; +import makeNextLog from '../make-nextlog'; + +function capdata(body, slots = []) { + return harden({ body, slots }); +} + +function capargs(args, slots = []) { + return capdata(JSON.stringify(args), slots); +} + +// Dynamic vats can be created without metering + +tap.test('unmetered dynamic vat', async t => { + const config = { + vats: new Map(), + bootstrapIndexJS: require.resolve('./vat-load-dynamic.js'), + }; + const c = await buildVatController(config, []); + const nextLog = makeNextLog(c); + + // let the vatAdminService get wired up before we create any new vats + await c.run(); + + // we'll give this bundle to the loader vat, which will use it to create a + // new (unmetered) dynamic vat + const dynamicVatBundle = await bundleSource( + require.resolve('./metered-dynamic-vat.js'), + ); + + // 'createVat' will import the bundle + c.queueToVatExport( + '_bootstrap', + 'o+0', + 'createVat', + capargs([dynamicVatBundle, { metered: false }]), + ); + await c.run(); + t.deepEqual(nextLog(), ['created'], 'first create'); + + // First, send a message to the dynamic vat that runs normally + c.queueToVatExport('_bootstrap', 'o+0', 'run', capargs([])); + await c.run(); + + t.deepEqual(nextLog(), ['did run'], 'first run ok'); + + // Tell the dynamic vat to call `Array(4e9)`. If metering was in place, + // this would be rejected. Without metering, it's harmless (Arrays are + // lazy). + c.queueToVatExport('_bootstrap', 'o+0', 'explode', capargs(['allocate'])); + await c.run(); + t.deepEqual(nextLog(), ['failed to explode'], 'metering disabled'); + + t.end(); +}); diff --git a/packages/SwingSet/test/metering/vat-load-dynamic.js b/packages/SwingSet/test/metering/vat-load-dynamic.js index 703f9bb63eff..e742efd9f35d 100644 --- a/packages/SwingSet/test/metering/vat-load-dynamic.js +++ b/packages/SwingSet/test/metering/vat-load-dynamic.js @@ -12,8 +12,8 @@ function build(buildStuff) { service = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); }, - async createVat(bundle) { - control = await E(service).createVat(bundle); + async createVat(bundle, options) { + control = await E(service).createVat(bundle, options); E(control.adminNode) .done() .then(