From 5b9563849fa7ca2f26b4ca7c55f10d1d37334f46 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 21 Jul 2021 17:31:35 -0700 Subject: [PATCH] feat(swingset)!: remove support for non-XS metering We no longer care about injected (Node.js) -based metering: XS is the only platform where we can reasonably+efficiently meter code. This removes support for passing `{ managerType: 'local', metered: true }` to `createVat()`. Doing so will throw an exception when the vatManager is invoked. Using just `{ metered: true }` is also an error when the swingset `config.defaultManagerType` is not set to `xs-worker`. Applications that want metered dynamic vats (i.e. all of them) should set `config.defaultManagerType = 'xs-worker'` to avoid this. This commit removes support throughout swingset: * `transformMetering` is no longer passed from controller to kernel to the local vatManager, and the transform is no longer applied to `inescapableTransforms` to ensure sub-Compartments are also transformed * `getMeter` is no longer placed on `inescapableLexicals` * `replaceGlobalMeter` is no longer passed through to `supervisor-helper`, where it was used to disable metering at the end of each crank * the kernel `meterManager` facilities were removed * all tests of metering non-XS workers are removed, including a test that exercised sub-Compartments (which was specific to the injection approach) Some tests have been modernized slightly, to use `controller.kpResolution` instead of appending strings to testLog, and to use `prepare-test-env-ava` instead of `@agoric/install-ses`. Dependency updates: Swingset no longer uses `@agoric/install-metering-and-ses`, or the `tame-metering`/`transform-metering` packages. A vestigal dependency on `babel-parser` (left over from the transform-tildot days) was removed. Swingset still depends upon `babel-standalone`, to pre-load before `lockdown` in `prepare-test-env-ava`. closes #3518 --- packages/SwingSet/bin/replay-transcript.js | 2 - packages/SwingSet/package.json | 4 - packages/SwingSet/src/controller.js | 36 ---- packages/SwingSet/src/kernel/kernel.js | 7 - packages/SwingSet/src/kernel/metering.js | 118 ------------- .../SwingSet/src/kernel/vatManager/factory.js | 4 - .../src/kernel/vatManager/manager-local.js | 47 +---- .../kernel/vatManager/supervisor-helper.js | 57 ------- packages/SwingSet/test/metering/grandchild.js | 6 - .../test/metering/install-global-metering.js | 5 - .../test/metering/metered-dynamic-vat.js | 15 -- .../test/metering/test-dynamic-vat-metered.js | 113 +++++-------- .../test-dynamic-vat-subcompartment.js | 89 ---------- .../metering/test-dynamic-vat-unmetered.js | 56 +++--- .../SwingSet/test/metering/test-metering.js | 160 ------------------ .../test/metering/vat-load-dynamic.js | 33 +--- .../vat-admin/terminate/test-terminate.js | 8 +- .../SwingSet/test/vat-admin/test-innerVat.js | 8 +- .../SwingSet/test/vat-admin/test-replay.js | 8 +- 19 files changed, 99 insertions(+), 677 deletions(-) delete mode 100644 packages/SwingSet/src/kernel/metering.js delete mode 100644 packages/SwingSet/test/metering/grandchild.js delete mode 100644 packages/SwingSet/test/metering/install-global-metering.js delete mode 100644 packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js delete mode 100644 packages/SwingSet/test/metering/test-metering.js diff --git a/packages/SwingSet/bin/replay-transcript.js b/packages/SwingSet/bin/replay-transcript.js index 2b1b76b1e1c..744c6e9df0f 100644 --- a/packages/SwingSet/bin/replay-transcript.js +++ b/packages/SwingSet/bin/replay-transcript.js @@ -79,8 +79,6 @@ async function replay(transcriptFile, worker = 'xs-worker') { allVatPowers, kernelKeeper: fakeKernelKeeper, vatEndowments: {}, - meterManager: {}, - transformMetering: undefined, gcTools, kernelSlog, }); diff --git a/packages/SwingSet/package.json b/packages/SwingSet/package.json index 242be764ef4..52e9538ca76 100644 --- a/packages/SwingSet/package.json +++ b/packages/SwingSet/package.json @@ -27,13 +27,11 @@ "lint:eslint": "eslint '**/*.js'" }, "devDependencies": { - "@agoric/install-metering-and-ses": "^0.2.20", "@endo/ses-ava": "^0.2.4", "ava": "^3.12.1" }, "dependencies": { "@agoric/assert": "^0.3.6", - "@agoric/babel-parser": "^7.6.4", "@agoric/babel-standalone": "^7.14.3", "@agoric/bundle-source": "^1.4.4", "@agoric/captp": "^1.7.20", @@ -47,8 +45,6 @@ "@agoric/store": "^0.4.22", "@agoric/swing-store-lmdb": "^0.5.6", "@agoric/swing-store-simple": "^0.4.6", - "@agoric/tame-metering": "^2.0.6", - "@agoric/transform-metering": "^1.4.19", "@agoric/xsnap": "^0.6.9", "@endo/base64": "^0.2.4", "@types/tmp": "^0.2.0", diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index 4e8580f0727..51502e6a194 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -10,9 +10,7 @@ import { Worker } from 'worker_threads'; import anylogger from 'anylogger'; import { assert, details as X } from '@agoric/assert'; -import { isTamed, tameMetering } from '@agoric/tame-metering'; import { importBundle } from '@agoric/import-bundle'; -import { makeMeteringTransformer } from '@agoric/transform-metering'; import { xsnap, recordXSnap } from '@agoric/xsnap'; import engineGC from './engine-gc.js'; @@ -220,22 +218,6 @@ export async function makeSwingsetController( const buildKernel = kernelNS.default; writeSlogObject({ type: 'import-kernel-finish' }); - // transformMetering() requires Babel, which imports 'fs' and 'path', so it - // cannot be implemented within a non-start-Compartment. We build it out - // here and pass it to the kernel, which then passes it to vats. This is - // intended to be powerless. - const mt = makeMeteringTransformer(); - function transformMetering(src, getMeter) { - // 'getMeter' provides the meter to which the transformation itself is - // billed (the COMPUTE meter is charged the length of the source string). - // The endowment must be present and truthy, otherwise the transformation - // is disabled. TODO: rethink that, and have @agoric/transform-metering - // export a simpler function (without 'endowments' or .rewrite). - const ss = mt.rewrite({ src, endowments: { getMeter } }); - return ss.src; - } - harden(transformMetering); - // all vats get these in their global scope, plus a vat-specific 'console' const vatEndowments = harden({ // re2 is a RegExp work-a-like that disables backtracking expressions for @@ -243,22 +225,6 @@ export async function makeSwingsetController( RegExp: re2, }); - // It is important that tameMetering() was called by application startup, - // before install-ses. Rather than ask applications to capture the return - // value and pass it all the way through to here, we just run - // tameMetering() again (and rely upon its only-once behavior) to get the - // control facet (replaceGlobalMeter), and pass it in through - // kernelEndowments. If our enclosing application decided to not tame the - // globals, we detect that and refrain from touching it later. - const replaceGlobalMeter = isTamed() ? tameMetering() : undefined; - if (verbose) { - console.log( - `SwingSet global metering is ${ - isTamed() ? 'enabled' : 'disabled (no replaceGlobalMeter)' - }`, - ); - } - // this launches a worker in a Node.js thread (aka "Worker") function makeNodeWorker() { // TODO: after we move away from `-r esm` and use real ES6 modules, point @@ -296,8 +262,6 @@ export async function makeSwingsetController( debugPrefix, vatEndowments, makeConsole, - replaceGlobalMeter, - transformMetering, makeNodeWorker, startSubprocessWorkerNode, startXSnap, diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index a344e1eb07c..06c61dc742a 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -14,7 +14,6 @@ import { insistStorageAPI } from '../storageAPI.js'; import { insistCapData } from '../capdata.js'; import { insistMessage, insistVatDeliveryResult } from '../message.js'; import { insistDeviceID, insistVatID } from './id.js'; -import { makeMeterManager } from './metering.js'; import { makeKernelSyscallHandler, doSend } from './kernelSyscall.js'; import { makeSlogger, makeDummySlogger } from './slogger.js'; import { getKpidsToRetire } from './cleanup.js'; @@ -120,8 +119,6 @@ export default function buildKernel( vatEndowments, slogCallbacks = {}, makeConsole, - replaceGlobalMeter, - transformMetering, makeNodeWorker, startSubprocessWorkerNode, startXSnap, @@ -159,8 +156,6 @@ export default function buildKernel( snapStore, ); - const meterManager = makeMeterManager(replaceGlobalMeter); - let started = false; /** @@ -717,8 +712,6 @@ export default function buildKernel( allVatPowers, kernelKeeper, vatEndowments, - meterManager, - transformMetering, makeNodeWorker, startSubprocessWorkerNode, startXSnap, diff --git a/packages/SwingSet/src/kernel/metering.js b/packages/SwingSet/src/kernel/metering.js deleted file mode 100644 index 8ea38a90d64..00000000000 --- a/packages/SwingSet/src/kernel/metering.js +++ /dev/null @@ -1,118 +0,0 @@ -import { makeMeter } from '@agoric/transform-metering/src/meter.js'; - -export function makeMeterManager(replaceGlobalMeter) { - // It is important that tameMetering() was called by application startup, - // before install-ses. We expect the controller to run tameMetering() again - // (and rely upon its only-once behavior) to get the control facet - // (replaceGlobalMeter), and pass it to us through kernelEndowments. - - // TODO: be more clever. Only refill meters that were touched. Use a - // WeakMap. Drop meters that vats forget. Minimize the fast path. Then wrap - // more (encapsulate importBundle?) and provide a single sensible thing to - // vats. - let complainedAboutGlobalMetering = false; - const allRefillers = new Set(); // refill() functions - - function makeGetMeter(options = {}) { - const { refillEachCrank = true, refillIfExhausted = true } = options; - - if (!replaceGlobalMeter && !complainedAboutGlobalMetering) { - console.error( - `note: makeGetMeter() cannot enable global metering, app must import @agoric/install-metering-and-ses`, - ); - // to fix this, your application program must - // `import('@agoric/install-metering-and-ses')` instead of - // `import('@agoric/install-ses')` - complainedAboutGlobalMetering = true; - } - - // zoe's importBundle(dapp-encouragement contract) causes babel to use - // about 5.4M computrons and 6.4M allocatrons (total 11.8M) in a single - // crank, so the default of 1e7 is insufficient - const FULL = 1e8; - const { meter, refillFacet } = makeMeter({ - budgetAllocate: FULL, - budgetCompute: FULL, - }); - - function refill() { - // console.error(`-- METER REFILL`); - // console.error(` allocate used: ${FULL - refillFacet.getAllocateBalance()}`); - // console.error(` compute used : ${FULL - refillFacet.getComputeBalance()}`); - const meterUsage = harden({ - // TODO: Change this meterType whenever the semantics change. - meterType: 'tame-metering-1', - allocate: FULL - refillFacet.getAllocateBalance(), - compute: FULL - refillFacet.getComputeBalance(), - }); - if (meter.isExhausted() && !refillIfExhausted) { - return meterUsage; - } - refillFacet.allocate(); - refillFacet.compute(); - refillFacet.stack(); - return meterUsage; - } - - if (refillEachCrank) { - allRefillers.add(refill); - } - - function getMeter(dontReplaceGlobalMeter = false) { - if (replaceGlobalMeter && !dontReplaceGlobalMeter) { - replaceGlobalMeter(meter); - } - return meter; - } - - function isExhausted() { - return meter.isExhausted(); - } - - // TODO: this will evolve. For now, we let the caller refill the meter as - // much as they want, although only tests will do this (normal vats will - // just rely on refillEachCrank). As this matures, vats will only be able - // to refill a meter by transferring credits from some other meter, so - // their total usage limit remains unchanged. - return harden({ - getMeter, - isExhausted, - refillFacet, - refill, - getAllocateBalance: refillFacet.getAllocateBalance, - getComputeBalance: refillFacet.getComputeBalance, - getCombinedBalance: refillFacet.getCombinedBalance, - }); - } - - function stopGlobalMeter() { - if (replaceGlobalMeter) { - replaceGlobalMeter(null); - } - } - - function refillAllMeters() { - for (const refiller of allRefillers) { - refiller(); - } - } - - function runWithoutGlobalMeter(f, ...args) { - if (!replaceGlobalMeter) { - return f(...args); - } - const oldMeter = replaceGlobalMeter(null); - try { - return f(...args); - } finally { - replaceGlobalMeter(oldMeter); - } - } - - return harden({ - makeGetMeter, - stopGlobalMeter, - refillAllMeters, - runWithoutGlobalMeter, - }); -} diff --git a/packages/SwingSet/src/kernel/vatManager/factory.js b/packages/SwingSet/src/kernel/vatManager/factory.js index 2e2e7cbee87..ee30cc6ea51 100644 --- a/packages/SwingSet/src/kernel/vatManager/factory.js +++ b/packages/SwingSet/src/kernel/vatManager/factory.js @@ -9,8 +9,6 @@ export function makeVatManagerFactory({ allVatPowers, kernelKeeper, vatEndowments, - meterManager, - transformMetering, makeNodeWorker, startSubprocessWorkerNode, startXSnap, @@ -22,8 +20,6 @@ export function makeVatManagerFactory({ allVatPowers, kernelKeeper, vatEndowments, - meterManager, - transformMetering, gcTools, kernelSlog, }); diff --git a/packages/SwingSet/src/kernel/vatManager/manager-local.js b/packages/SwingSet/src/kernel/vatManager/manager-local.js index 0c83e885a7d..50c957b04b4 100644 --- a/packages/SwingSet/src/kernel/vatManager/manager-local.js +++ b/packages/SwingSet/src/kernel/vatManager/manager-local.js @@ -5,7 +5,6 @@ import { makeLiveSlots } from '../liveSlots.js'; import { makeManagerKit } from './manager-helper.js'; import { makeSupervisorDispatch, - makeMeteredDispatch, makeSupervisorSyscall, makeVatConsole, } from './supervisor-helper.js'; @@ -15,26 +14,16 @@ export function makeLocalVatManagerFactory(tools) { allVatPowers, kernelKeeper, vatEndowments, - meterManager, - transformMetering, gcTools, kernelSlog, } = tools; - const { makeGetMeter, refillAllMeters, stopGlobalMeter } = meterManager; const baseVP = { makeMarshal: allVatPowers.makeMarshal, }; // testLog is also a vatPower, only for unit tests - function prepare( - vatID, - vatSyscallHandler, - meterRecord, - compareSyscalls, - useTranscript, - ) { - const mtools = harden({ stopGlobalMeter, meterRecord, refillAllMeters }); + function prepare(vatID, vatSyscallHandler, compareSyscalls, useTranscript) { const mk = makeManagerKit( vatID, kernelSlog, @@ -48,11 +37,7 @@ export function makeLocalVatManagerFactory(tools) { function finish(dispatch) { assert.typeof(dispatch, 'function'); // this 'deliverToWorker' never throws, even if liveslots has an internal error - const deliverToWorker = makeMeteredDispatch( - makeSupervisorDispatch(dispatch), - mtools, - ); - mk.setDeliverToWorker(deliverToWorker); + mk.setDeliverToWorker(makeSupervisorDispatch(dispatch)); async function shutdown() { // local workers don't need anything special to shut down between turns @@ -65,14 +50,13 @@ export function makeLocalVatManagerFactory(tools) { } function createFromSetup(vatID, setup, managerOptions, vatSyscallHandler) { - assert(!managerOptions.metered, X`unsupported`); + assert(!managerOptions.metered, `metering unsupported on 'local' workers`); assert(setup instanceof Function, 'setup is not an in-realm function'); const { vatParameters, compareSyscalls, useTranscript } = managerOptions; const { syscall, finish } = prepare( vatID, vatSyscallHandler, - null, compareSyscalls, useTranscript, ); @@ -91,9 +75,9 @@ export function makeLocalVatManagerFactory(tools) { managerOptions, vatSyscallHandler, ) { + assert(!managerOptions.metered, `metering unsupported on 'local' workers`); const { consensusMode, - metered = false, enableDisavow = false, enableSetup = false, vatParameters = {}, @@ -106,23 +90,9 @@ export function makeLocalVatManagerFactory(tools) { } = managerOptions; assert(vatConsole, 'vats need managerOptions.vatConsole'); - 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, - }); - } - const { syscall, finish } = prepare( vatID, vatSyscallHandler, - meterRecord, compareSyscalls, useTranscript, ); @@ -155,20 +125,11 @@ export function makeLocalVatManagerFactory(tools) { }), assert, }); - const inescapableTransforms = []; const inescapableGlobalProperties = { ...ls.inescapableGlobalProperties }; - const inescapableGlobalLexicals = {}; - if (metered) { - const getMeter = meterRecord.getMeter; - inescapableTransforms.push(src => transformMetering(src, getMeter)); - inescapableGlobalLexicals.getMeter = getMeter; - } const vatNS = await importBundle(bundle, { filePrefix: `vat-${vatID}/...`, endowments, - inescapableTransforms, - inescapableGlobalLexicals, inescapableGlobalProperties, }); diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js b/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js index 012bf258ff8..e2a33c21ecc 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-helper.js @@ -9,8 +9,6 @@ import '../../types.js'; /** * @typedef { (delivery: VatDeliveryObject) => (VatDeliveryResult | Promise) } VatDispatcherSyncAsync * @typedef { (delivery: VatDeliveryObject) => Promise } VatDispatcher - * @typedef { { refill: () => unknown, isExhausted: () => null | Error } } MeterRecord - * @typedef { { stopGlobalMeter: () => void, meterRecord: MeterRecord, refillAllMeters: () => void } } DispatchMeteringTools */ /** @@ -49,61 +47,6 @@ function makeSupervisorDispatch(dispatch) { harden(makeSupervisorDispatch); export { makeSupervisorDispatch }; -/** - * @param { VatDeliveryResult } status - * @param { DispatchMeteringTools } mtools - * @returns { VatDeliveryResult } - */ - -function processLocalMeters(status, mtools) { - const { stopGlobalMeter, meterRecord, refillAllMeters } = mtools; - stopGlobalMeter(); - /** @type VatDeliveryResult */ - status = [...status]; // mutable copy, to add usage - if (status[0] === 'ok') { - // refill this vat's meter, if any, accumulating its usage for stats - if (meterRecord) { - // note that refill() won't actually refill an exhausted meter - const meterUsage = meterRecord.refill(); - const exhaustionError = meterRecord.isExhausted(); - if (exhaustionError) { - status = ['error', exhaustionError.message, meterUsage]; - } else { - // We will have ['ok', null, meterUsage] - status[2] = meterUsage; - // TODO: accumulate used.allocate and used.compute into vatStats - // updateStats(status[2]); - } - } - // refill all within-vat -created meters - refillAllMeters(); - } - return harden(status); -} - -/** - * Given an async 'dispatch' function (like the return value of - * makeSupervisorDispatch), return a version that manages the - * '@agoric/tame-metering' -style meters. - * - * @param { VatDispatcher } dispatchToVat - * @param { DispatchMeteringTools } mtools - * @returns { VatDispatcher } - */ - -function makeMeteredDispatch(dispatchToVat, mtools) { - async function deliver(delivery) { - const status = await dispatchToVat(delivery); - // TODO: is there any chance the confined code could trigger a metering - // fault in a global after it loses agency but before we get to - // stopGlobalMeter() ? - return processLocalMeters(status, mtools); - } - return harden(deliver); -} -harden(makeMeteredDispatch); -export { makeMeteredDispatch }; - /** * This returns a function that is provided to liveslots as the 'syscall' * argument: an object with one method per syscall type. These methods return diff --git a/packages/SwingSet/test/metering/grandchild.js b/packages/SwingSet/test/metering/grandchild.js deleted file mode 100644 index 7cbf13875f6..00000000000 --- a/packages/SwingSet/test/metering/grandchild.js +++ /dev/null @@ -1,6 +0,0 @@ -import { meterMe } from './metered-code.js'; - -export function meterThem(explode) { - const log2 = []; - meterMe(log2, explode); -} diff --git a/packages/SwingSet/test/metering/install-global-metering.js b/packages/SwingSet/test/metering/install-global-metering.js deleted file mode 100644 index c5214107c22..00000000000 --- a/packages/SwingSet/test/metering/install-global-metering.js +++ /dev/null @@ -1,5 +0,0 @@ -// this must be imported before install-ses -import { tameMetering } from '@agoric/tame-metering'; - -const replaceGlobalMeter = tameMetering(); -export { replaceGlobalMeter }; diff --git a/packages/SwingSet/test/metering/metered-dynamic-vat.js b/packages/SwingSet/test/metering/metered-dynamic-vat.js index aecdecd113b..8bd8dcef23e 100644 --- a/packages/SwingSet/test/metering/metered-dynamic-vat.js +++ b/packages/SwingSet/test/metering/metered-dynamic-vat.js @@ -1,12 +1,8 @@ -import { assert } from '@agoric/assert'; -import { importBundle } from '@agoric/import-bundle'; import { makePromiseKit } from '@agoric/promise-kit'; import { Far } from '@agoric/marshal'; import { meterMe } from './metered-code.js'; export function buildRootObject(_dynamicVatPowers) { - let grandchildNS; - return Far('root', { never() { return makePromiseKit().promise; @@ -21,16 +17,5 @@ export function buildRootObject(_dynamicVatPowers) { meterMe([], how); return -1; }, - - async load(bundle) { - const require = harden(() => 0); - grandchildNS = await importBundle(bundle, { - endowments: { console, assert, require }, - }); - }, - - async meterThem(explode) { - grandchildNS.meterThem(explode); - }, }); } diff --git a/packages/SwingSet/test/metering/test-dynamic-vat-metered.js b/packages/SwingSet/test/metering/test-dynamic-vat-metered.js index a33fd975910..f0e17df429c 100644 --- a/packages/SwingSet/test/metering/test-dynamic-vat-metered.js +++ b/packages/SwingSet/test/metering/test-dynamic-vat-metered.js @@ -1,13 +1,12 @@ -/* global require */ -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-metering-and-ses'; +/* global __dirname */ +// eslint-disable-next-line import/order +import { test } from '../../tools/prepare-test-env-ava.js'; + +// eslint-disable-next-line import/order +import path from 'path'; import bundleSource from '@agoric/bundle-source'; -import test from 'ava'; import { provideHostStorage } from '../../src/hostStorage.js'; import { buildKernelBundles, buildVatController } from '../../src/index.js'; -import makeNextLog from '../make-nextlog.js'; function capdata(body, slots = []) { return harden({ body, slots }); @@ -22,10 +21,10 @@ async function prepare() { // we'll give this bundle to the loader vat, which will use it to create a // new (metered) dynamic vat const dynamicVatBundle = await bundleSource( - require.resolve('./metered-dynamic-vat.js'), + path.join(__dirname, 'metered-dynamic-vat.js'), ); const bootstrapBundle = await bundleSource( - require.resolve('./vat-load-dynamic.js'), + path.join(__dirname, 'vat-load-dynamic.js'), ); return { kernelBundles, dynamicVatBundle, bootstrapBundle }; } @@ -34,7 +33,17 @@ test.before(async t => { t.context.data = await prepare(); }); -async function runOneTest(t, explosion, managerType) { +function kpidRejected(t, c, kpid, message) { + t.is(c.kpStatus(kpid), 'rejected'); + const resCapdata = c.kpResolution(kpid); + t.deepEqual(resCapdata.slots, []); + const body = JSON.parse(resCapdata.body); + delete body.errorId; + t.deepEqual(body, { '@qclass': 'error', name: 'Error', message }); +} + +async function overflowCrank(t, explosion) { + const managerType = 'xs-worker'; const { kernelBundles, dynamicVatBundle, bootstrapBundle } = t.context.data; const config = { bootstrap: 'bootstrap', @@ -51,7 +60,6 @@ async function runOneTest(t, explosion, managerType) { kernelBundles, }); c.pinVatRoot('bootstrap'); - const nextLog = makeNextLog(c); // let the vatAdminService get wired up before we create any new vats await c.run(); @@ -59,9 +67,11 @@ async function runOneTest(t, explosion, managerType) { // 'createVat' will import the bundle const cvopts = { managerType, metered: true }; const cvargs = capargs([dynamicVatBundle, cvopts]); - c.queueToVatRoot('bootstrap', 'createVat', cvargs); + const kp2 = c.queueToVatRoot('bootstrap', 'createVat', cvargs); await c.run(); - t.deepEqual(nextLog(), ['created'], 'first create'); + const res2 = c.kpResolution(kp2); + t.is(JSON.parse(res2.body)[0], 'created', res2.body); + const doneKPID = res2.slots[0]; // extract the vatID for the newly-created dynamic vat const dynamicVatIDs = JSON.parse(kvStore.get('vat.dynamicIDs')); @@ -77,80 +87,51 @@ async function runOneTest(t, explosion, managerType) { const neverKPID = neverArgs.slots[0]; // First, send a message to the dynamic vat that runs normally - c.queueToVatRoot('bootstrap', 'run', capargs([])); + const kp3 = c.queueToVatRoot('bootstrap', 'run', capargs([])); await c.run(); t.is(JSON.parse(kvStore.get('vat.dynamicIDs')).length, 1); t.is(kvStore.get(`${root}.owner`), vatID); t.true(Array.from(kvStore.getKeys(`${vatID}`, `${vatID}/`)).length > 0); - // neverKPID should still be unresolved - t.is(kvStore.get(`${neverKPID}.state`), 'unresolved'); - - t.deepEqual(nextLog(), ['did run'], 'first run ok'); - - // Now send a message that makes the dynamic vat exhaust its meter. The - // message result promise should be rejected, and the control facet should - // report the vat's demise. Remnants of the killed vat should be gone - // from the kernel state store. - c.queueToVatRoot('bootstrap', 'explode', capargs([explosion])); + // neverP and doneP should still be unresolved + t.is(c.kpStatus(neverKPID), 'unresolved'); + t.is(c.kpStatus(doneKPID), 'unresolved'); + t.deepEqual(c.kpResolution(kp3), capargs(42)); + + // Now send a message that makes the dynamic vat exhaust its per-crank + // meter. The message result promise should be rejected, and the control + // facet should report the vat's demise. Remnants of the killed vat should + // be gone from the kernel state store. + const kp4 = c.queueToVatRoot('bootstrap', 'explode', capargs([explosion])); await c.run(); + kpidRejected(t, c, kp4, 'vat terminated'); t.is(JSON.parse(kvStore.get('vat.dynamicIDs')).length, 0); t.is(kvStore.get(`${root}.owner`), undefined); t.is(Array.from(kvStore.getKeys(`${vatID}`, `${vatID}/`)).length, 0); - // neverKPID should be rejected - t.is(kvStore.get(`${neverKPID}.state`), 'rejected'); - t.is( - kvStore.get(`${neverKPID}.data.body`), - JSON.stringify({ - '@qclass': 'error', - name: 'Error', - message: 'vat terminated', - }), - ); - // TODO: the rejection shouldn't reveal the reason, maybe use this instead: - // t.is(kvStore.get(`${neverKPID}.data.body`), - // JSON.stringify('vat terminated')); + // neverP should be rejected, without revealing details + kpidRejected(t, c, neverKPID, 'vat terminated'); + // but doneP gets more details const expected = { allocate: 'Allocate meter exceeded', compute: 'Compute meter exceeded', stack: 'Stack meter exceeded', }; - - t.deepEqual( - nextLog(), - [ - 'did explode: Error: vat terminated', - `terminated: Error: ${expected[explosion]}`, - ], - 'first boom', - ); + kpidRejected(t, c, doneKPID, expected[explosion]); // the dead vat should stay dead - c.queueToVatRoot('bootstrap', 'run', capargs([])); + const kp5 = c.queueToVatRoot('bootstrap', 'run', capargs([])); await c.run(); - t.deepEqual(nextLog(), ['run exploded: Error: vat terminated'], 'stay dead'); + kpidRejected(t, c, kp5, 'vat terminated'); } -test('local vat allocate overflow', t => { - return runOneTest(t, 'allocate', 'local'); -}); - -test('local vat compute overflow', t => { - return runOneTest(t, 'compute', 'local'); -}); - -test('local vat stack overflow', t => { - return runOneTest(t, 'stack', 'local'); -}); - -test('xsnap vat allocate overflow', t => { - return runOneTest(t, 'allocate', 'xs-worker'); +test('exceed allocate', t => { + return overflowCrank(t, 'allocate'); }); -test('xsnap vat compute overflow', t => { - return runOneTest(t, 'compute', 'xs-worker'); +test('exceed per-crank compute', t => { + return overflowCrank(t, 'compute'); }); -test('xsnap vat stack overflow', t => { - return runOneTest(t, 'stack', 'xs-worker'); +test('exceed stack', t => { + return overflowCrank(t, 'stack'); }); diff --git a/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js b/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js deleted file mode 100644 index 74830b61357..00000000000 --- a/packages/SwingSet/test/metering/test-dynamic-vat-subcompartment.js +++ /dev/null @@ -1,89 +0,0 @@ -/* global require */ -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-metering-and-ses'; -import bundleSource from '@agoric/bundle-source'; -import test from 'ava'; -import { buildVatController } from '../../src/index.js'; -import makeNextLog from '../make-nextlog.js'; - -function capdata(body, slots = []) { - return harden({ body, slots }); -} - -function capargs(args, slots = []) { - return capdata(JSON.stringify(args), slots); -} - -// This test checks that dynamic vats (which are metered) can import bundles, -// and that those bundles are also metered. - -test('metering dynamic vat which imports bundle', async t => { - // We first create a static vat with vat-load-dynamic.js - const config = { - bootstrap: 'bootstrap', - vats: { - bootstrap: { - sourceSpec: require.resolve('./vat-load-dynamic.js'), - }, - }, - }; - const c = await buildVatController(config, []); - c.pinVatRoot('bootstrap'); - const nextLog = makeNextLog(c); - - // let the vatAdminService get wired up before we create any new vats - await c.run(); - - // now we tell the static vat to create a dynamic vat, from - // metered-dynamic-vat.js - const dynamicVatBundle = await bundleSource( - require.resolve('./metered-dynamic-vat.js'), - ); - - // 'createVat' will import the bundle - const cvopts = { metered: true }; - const cvargs = capargs([dynamicVatBundle, cvopts]); - c.queueToVatRoot('bootstrap', 'createVat', cvargs); - await c.run(); - t.deepEqual(nextLog(), ['created'], 'first create'); - - // then we tell the dynamic vat to load the grandchild.js bundle - const grandchildBundle = await bundleSource( - require.resolve('./grandchild.js'), - ); - const r = c.queueToVatRoot('bootstrap', 'load', capargs([grandchildBundle])); - await c.run(); - t.deepEqual(c.kpResolution(r), capargs('ok')); - - // First, send a message to the grandchild that runs normally - c.queueToVatRoot('bootstrap', 'bundleRun', capargs([])); - await c.run(); - - t.deepEqual(nextLog(), ['did run'], 'first run ok'); - - // Now tell the grandchild to exhaust the dynamic vat's meter. The message - // result promise should be rejected, and the control facet should report - // the vat's demise - c.queueToVatRoot('bootstrap', 'bundleExplode', capargs(['allocate'])); - await c.run(); - - t.deepEqual( - nextLog(), - [ - 'did explode: Error: vat terminated', - 'terminated: Error: Allocate meter exceeded', - ], - 'grandchild go boom', - ); - - // the whole vat should be dead (we use 'run' instead of 'bundleRun') - c.queueToVatRoot('bootstrap', 'run', capargs([])); - await c.run(); - t.deepEqual( - nextLog(), - ['run exploded: Error: vat terminated'], - 'whole dynamic vat is dead', - ); -}); diff --git a/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js b/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js index be00625105b..9c5519db959 100644 --- a/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js +++ b/packages/SwingSet/test/metering/test-dynamic-vat-unmetered.js @@ -1,12 +1,11 @@ -/* global require */ -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-metering-and-ses'; +/* global __dirname */ +// eslint-disable-next-line import/order +import { test } from '../../tools/prepare-test-env-ava.js'; + +// eslint-disable-next-line import/order +import path from 'path'; import bundleSource from '@agoric/bundle-source'; -import test from 'ava'; import { buildVatController } from '../../src/index.js'; -import makeNextLog from '../make-nextlog.js'; function capdata(body, slots = []) { return harden({ body, slots }); @@ -16,23 +15,20 @@ function capargs(args, slots = []) { return capdata(JSON.stringify(args), slots); } -const localOnlyForNow = { defaultManagerType: 'local' }; - // Dynamic vats can be created without metering test('unmetered dynamic vat', async t => { const config = { bootstrap: 'bootstrap', - ...localOnlyForNow, + defaultManagerType: 'local', vats: { bootstrap: { - sourceSpec: require.resolve('./vat-load-dynamic.js'), + sourceSpec: path.join(__dirname, 'vat-load-dynamic.js'), }, }, }; const c = await buildVatController(config, []); c.pinVatRoot('bootstrap'); - const nextLog = makeNextLog(c); // let the vatAdminService get wired up before we create any new vats await c.run(); @@ -40,29 +36,41 @@ test('unmetered dynamic vat', async t => { // 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'), + path.join(__dirname, 'metered-dynamic-vat.js'), ); // 'createVat' will import the bundle - c.queueToVatRoot( + const kp1 = c.queueToVatRoot( 'bootstrap', 'createVat', capargs([dynamicVatBundle, { metered: false }]), 'panic', ); await c.run(); - t.deepEqual(nextLog(), ['created'], 'first create'); + const res1 = c.kpResolution(kp1); + t.is(JSON.parse(res1.body)[0], 'created', res1.body); + // const doneKPID = res1.slots[0]; - // First, send a message to the dynamic vat that runs normally - c.queueToVatRoot('bootstrap', 'run', capargs([]), 'panic'); + // Now send a message to the dynamic vat that runs normally + const kp2 = c.queueToVatRoot('bootstrap', 'run', capargs([]), 'panic'); await c.run(); + t.is(c.kpStatus(kp2), 'fulfilled'); + t.deepEqual(c.kpResolution(kp2), capargs(42)); - t.deepEqual(nextLog(), ['did run'], 'first run ok'); + // TODO: find a way to prove that the xsnap child process does not have a + // per-crank meter imposed upon it. Previously, this test only exercised + // Node.js workers, and used Array(4e9) as code that would be caught by the + // injected metering shim, but tolerated by V8 itself (because arrays are + // lazy). This was followed by a big='1234'; for (;;) { big += big }, which + // triggers a V8 catchable RangeError exception. XS is more literal about + // allocation space. - // 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.queueToVatRoot('bootstrap', 'explode', capargs(['allocate']), 'panic'); - await c.run(); - t.deepEqual(nextLog(), ['failed to explode'], 'metering disabled'); + // We no longer care about injected Node.js metering; we only care about + // metering under XS. To test that metering is really turned off on XS, we + // must perform an action that requires more than the default per-crank + // computron limit, but of course we'd prefer something that doesn't + // actually take a lot of time or memory. I don't yet know how to do this. + // For now, we're only testing that it is possible to run an XS worker + // without a 'meter:' argument, not that the resulting vat is + // unconstrained. }); diff --git a/packages/SwingSet/test/metering/test-metering.js b/packages/SwingSet/test/metering/test-metering.js deleted file mode 100644 index 4520a136fb0..00000000000 --- a/packages/SwingSet/test/metering/test-metering.js +++ /dev/null @@ -1,160 +0,0 @@ -/* global require */ -// eslint-disable-next-line import/order -import { replaceGlobalMeter } from './install-global-metering.js'; -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-ses'; -import { assert, details as X } from '@agoric/assert'; -import bundleSource from '@agoric/bundle-source'; -import { importBundle } from '@agoric/import-bundle'; -import { makeMeter, makeMeteringTransformer } from '@agoric/transform-metering'; -import re2 from 're2'; -import test from 'ava'; -import { waitUntilQuiescent } from '../../src/waitUntilQuiescent.js'; - -// Run a function under the control of a meter. The function must not have -// access to the timer queue (setImmediate or setInterval). Returns a Promise -// that fires when the function no longer has agency. The Promise rejects -// (with Error("meter exhausted") if the meter was exhausted, or resolves to -// undefined if the thunk finished normally. In either case, the thunk no -// longer has agency. The success/failure behavior of the thunk is discarded. -// Needs waitUntilQuiescent to work (which uses setImmediate itself). -async function runUnderMeter(meter, thunk) { - replaceGlobalMeter(meter); // also turn on global metering - try { - thunk(); - // eslint-disable-next-line no-empty - } catch (_e) {} - await waitUntilQuiescent(); - replaceGlobalMeter(null); // disable global metering - if (meter.isExhausted()) { - // console.log(`meter is exhausted`); - // throw Error("meter exhausted"); - return false; - } - // console.log(`meter is ok`); - return true; -} - -async function meteredImportBundle(bundle, endowments) { - const { meter, refillFacet } = makeMeter(); - function getMeter() { - return meter; - } - - // ss = mt.rewrite(ss) - // where ss is { src, endowments } - // and endowments.getMeter() works: it can be billed for the source being transformed - // but endowments.getMeter() is allowed to return falsy - - // and endowments.RegExp will be set during transformation - // also 'makeMeteringTransformer' must be imported in the start compartment, so - // it can import RE2 (which has native code). we'll have to pass 'mt' from the - // controller into the kernel, so the kernel can create dynamic vats. We can also - // pass it into static vats, so within-vat metering can happen. - - const mt = makeMeteringTransformer(); - function transform(src) { - const ss = mt.rewrite({ src, endowments: { getMeter } }); - // const newRegExp = ss.endowments.RegExp; // === re2 - return ss.src; - } - - // Must importBundle under the meter, because top-level code runs, and - // might run forever. It might also spawn off Promise callbacks, and we - // don't want to let those escape the meter either. - let ns; - // importBundle requires a 'require', even if nothing uses it - function doImport() { - const p = importBundle(bundle, { - endowments: { ...endowments, getMeter, RegExp: re2 }, - transforms: [transform], - }); - p.then(n => (ns = n)); - } - // this throws if top-level code exhausts meter - const topLevelOk = await runUnderMeter(meter, doImport); - assert(topLevelOk, X`top-level code exhausted the meter`); - assert(ns, 'bundle failed to produce namespace object before quiesence'); - - function runBundleThunkUnderMeter(thunk) { - return runUnderMeter(meter, thunk); - } - - return { - ns, - runBundleThunkUnderMeter, - refillFacet, - }; -} - -// This exercises our Node.js-based injected-metering code, which is only -// used on the 'local' vat worker. - -test('injected metering of a single bundle', async function testSingleBundle(t) { - const bundle = await bundleSource(require.resolve('./metered-code.js')); - harden(Object.getPrototypeOf(console)); - const endowments = { console, assert }; - const { - ns, - runBundleThunkUnderMeter, - refillFacet, - } = await meteredImportBundle(bundle, endowments); - - const log2 = []; - const meterMe = ns.meterMe; - // console.log(`running without explosion`); - let ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); - t.deepEqual(log2, ['started', 'done'], 'computation completed'); - log2.splice(0); - t.is(ok, true, 'meter should not be exhausted'); - - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'compute')); - t.deepEqual(log2, ['started'], 'computation started but halted'); - log2.splice(0); - t.is(ok, false, 'meter should be exhausted (compute)'); - - // Run the same code (without an infinite loop) against the old exhausted - // meter. It should halt right away. - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); - t.is(log2.length, 0, 'computation did not start'); - t.is(ok, false, 'meter should be exhausted (still compute)'); - - // Refill the meter, and the code should run again. - // refillFacet.combined(10000000); - // refillFacet.allocate(10000000); - refillFacet.compute(10000000); - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); - t.deepEqual(log2, ['started', 'done'], 'computation completed'); - log2.splice(0); - t.is(ok, true, 'meter should not be exhausted'); - - // now check that metering catches infinite stack - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'stack')); - t.deepEqual(log2, ['started'], 'computation started but halted'); - log2.splice(0); - t.is(ok, false, 'meter should be exhausted (stack)'); - - // Refill the meter, and the code should run again. - // refillFacet.combined(10000000); - // refillFacet.allocate(10000000); - refillFacet.stack(10000000); - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); - t.deepEqual(log2, ['started', 'done'], 'computation completed'); - log2.splice(0); - t.is(ok, true, 'meter should not be exhausted'); - - // metering should catch primordial allocation too - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'allocate')); - t.deepEqual(log2, ['started'], 'computation started but halted'); - log2.splice(0); - t.is(ok, false, 'meter should be exhausted (allocate)'); - - // Refill the meter, and the code should run again. - refillFacet.allocate(10000000); - ok = await runBundleThunkUnderMeter(() => meterMe(log2, 'no')); - t.deepEqual(log2, ['started', 'done'], 'computation completed'); - log2.splice(0); - t.is(ok, true, 'meter should not be exhausted'); -}); diff --git a/packages/SwingSet/test/metering/vat-load-dynamic.js b/packages/SwingSet/test/metering/vat-load-dynamic.js index 81a0b38beaf..cdb63747e04 100644 --- a/packages/SwingSet/test/metering/vat-load-dynamic.js +++ b/packages/SwingSet/test/metering/vat-load-dynamic.js @@ -13,13 +13,9 @@ export function buildRootObject(vatPowers) { async createVat(bundle, dynamicOptions) { control = await E(service).createVat(bundle, dynamicOptions); - E(control.adminNode) - .done() - .then( - () => log('finished'), - err => log(`terminated: ${err}`), - ); - log(`created`); + const done = E(control.adminNode).done(); + // the caller checks this later, but doesn't wait for it + return ['created', done]; }, getNever() { @@ -29,27 +25,12 @@ export function buildRootObject(vatPowers) { return [neverP]; }, - async run() { - try { - await E(control.root).run(); - log('did run'); - } catch (err) { - log(`run exploded: ${err}`); - } - }, - - async explode(how) { - try { - await E(control.root).explode(how); - log('failed to explode'); - } catch (err) { - log(`did explode: ${err}`); - } + run() { + return E(control.root).run(); }, - async load(grandchildBundle) { - await E(control.root).load(grandchildBundle); - return 'ok'; + explode(how) { + return E(control.root).explode(how); }, async bundleRun() { diff --git a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js index a52a6694558..9e96dce10a7 100644 --- a/packages/SwingSet/test/vat-admin/terminate/test-terminate.js +++ b/packages/SwingSet/test/vat-admin/terminate/test-terminate.js @@ -1,10 +1,8 @@ /* global __dirname */ -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-ses'; +// eslint-disable-next-line import/order +import { test } from '../../../tools/prepare-test-env-ava.js'; +// eslint-disable-next-line import/order import path from 'path'; -import test from 'ava'; import { getAllState, setAllState } from '@agoric/swing-store-simple'; import { provideHostStorage } from '../../../src/hostStorage.js'; diff --git a/packages/SwingSet/test/vat-admin/test-innerVat.js b/packages/SwingSet/test/vat-admin/test-innerVat.js index ab343491fa1..e13ba11fff6 100644 --- a/packages/SwingSet/test/vat-admin/test-innerVat.js +++ b/packages/SwingSet/test/vat-admin/test-innerVat.js @@ -1,10 +1,8 @@ /* global __dirname */ -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-metering-and-ses'; +// eslint-disable-next-line import/order +import { test } from '../../tools/prepare-test-env-ava.js'; +// eslint-disable-next-line import/order import path from 'path'; -import test from 'ava'; import bundleSource from '@agoric/bundle-source'; import { buildKernelBundles, diff --git a/packages/SwingSet/test/vat-admin/test-replay.js b/packages/SwingSet/test/vat-admin/test-replay.js index 4759ee47adf..9c4e4b92dcb 100644 --- a/packages/SwingSet/test/vat-admin/test-replay.js +++ b/packages/SwingSet/test/vat-admin/test-replay.js @@ -1,10 +1,8 @@ /* global __dirname */ -// TODO Remove babel-standalone preinitialization -// https://github.com/endojs/endo/issues/768 -import '@agoric/babel-standalone'; -import '@agoric/install-metering-and-ses'; +// eslint-disable-next-line import/order +import { test } from '../../tools/prepare-test-env-ava.js'; +// eslint-disable-next-line import/order import path from 'path'; -import test from 'ava'; import bundleSource from '@agoric/bundle-source'; import { getAllState, setAllState } from '@agoric/swing-store-simple'; import { provideHostStorage } from '../../src/hostStorage.js';