From 8bd495ce64ab20a4f7e78999846afe1f9bce96a4 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sun, 23 Feb 2020 10:27:12 -0800 Subject: [PATCH] feat(spawner): implement basic metering --- packages/SwingSet/src/controller.js | 1 - packages/spawner/package.json | 3 +- packages/spawner/src/contractHost.js | 26 ++++++++++++- .../swingsetTests/contractHost/bootstrap.js | 39 +++++++++++++++++++ .../contractHost/test-contractHost.js | 14 +++++++ packages/zoe/src/evalContractCode.js | 4 -- 6 files changed, 80 insertions(+), 7 deletions(-) diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index df3ebac52da..cbf4dcc5f2e 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -137,7 +137,6 @@ function makeEvaluate(e) { }); } - function makeSESEvaluator() { const evaluateOptions = makeDefaultEvaluateOptions(); const { transforms = [], shims = [], ...otherOptions } = evaluateOptions; diff --git a/packages/spawner/package.json b/packages/spawner/package.json index ed65af4825d..7a9e6560302 100644 --- a/packages/spawner/package.json +++ b/packages/spawner/package.json @@ -35,7 +35,8 @@ "@agoric/make-promise": "^0.0.1", "@agoric/nat": "^2.0.1", "@agoric/same-structure": "^0.0.1", - "@agoric/store": "^0.0.1" + "@agoric/store": "^0.0.1", + "@agoric/transform-metering": "^1.1.0" }, "devDependencies": { "@agoric/swingset-vat": "^0.3.0", diff --git a/packages/spawner/src/contractHost.js b/packages/spawner/src/contractHost.js index 32028016987..f76c3192616 100644 --- a/packages/spawner/src/contractHost.js +++ b/packages/spawner/src/contractHost.js @@ -1,3 +1,4 @@ +/* global replaceGlobalMeter */ // Copyright (C) 2019 Agoric, under Apache License 2.0 import Nat from '@agoric/nat'; @@ -12,6 +13,7 @@ import { import { inviteConfig } from '@agoric/ertp/src/config/inviteConfig'; import { makeMint } from '@agoric/ertp'; import makePromise from '@agoric/make-promise'; +import { makeMeter } from '@agoric/transform-metering/src/meter'; import { allSettled } from './allSettled'; @@ -68,7 +70,29 @@ function makeContractHost(E, evaluate, additionalEndowments = {}) { typeof functionSrcString === 'string', `"${functionSrcString}" must be a string, but was ${typeof functionSrcString}`, ); - const fn = evaluate(functionSrcString, fullEndowments); + + // Refill a meter each time. + // NOTE: We need 1e7 or the autoswap contract exhausts + // the compute meter. + const { meter, refillFacet } = makeMeter({ budgetCombined: 1e7 }); + const doRefill = () => { + // Refill the meter, since we're leaving a crank. + Object.values(refillFacet, r => r()); + }; + + // Make an endowment to get our meter. + const getGlobalMeter = m => { + if (m !== true && typeof replaceGlobalMeter !== 'undefined') { + // Replace the global meter and register our refiller. + replaceGlobalMeter(meter, doRefill); + } + return meter; + }; + + const fn = evaluate(functionSrcString, { + ...fullEndowments, + getGlobalMeter, + }); assert( typeof fn === 'function', `"${functionSrcString}" must be a string for a function, but produced ${typeof fn}`, diff --git a/packages/spawner/test/swingsetTests/contractHost/bootstrap.js b/packages/spawner/test/swingsetTests/contractHost/bootstrap.js index 4073a399a28..ca12b0f4407 100644 --- a/packages/spawner/test/swingsetTests/contractHost/bootstrap.js +++ b/packages/spawner/test/swingsetTests/contractHost/bootstrap.js @@ -113,6 +113,41 @@ function build(E, log) { }); } + function exhaustedContractTest(host) { + log('starting exhaustedContractTest'); + + const exhContract = harden({ + start: (terms, _inviteMaker) => { + if (terms === 'loop forever') { + for (;;) { + // Do nothing. + } + } else { + return 123; + } + }, + }); + const contractSrcs = harden({ start: `${exhContract.start} ` }); + + const installationP = E(host).install(contractSrcs); + + return E(host) + .getInstallationSourceCode(installationP) + .then(src => { + log('Does source match? ', src.start === contractSrcs.start); + + return E(installationP) + .spawn('loop forever') + .catch(e => log('spawn rejected: ', e.message)); + }) + .then(_ => E(host).install(contractSrcs)) + .then(installation2P => E(installation2P).spawn('bar terms')) + .then( + ret => log('got bar ret: ', ret), + err => log('got bar err: ', err.message), + ); + } + function betterContractTestAliceFirst(host, mint, aliceMaker, bobMaker) { const escrowExchangeInstallationP = E(host).install(escrowExchangeSrcs); const coveredCallInstallationP = E(host).install(coveredCallSrcs); @@ -321,6 +356,10 @@ function build(E, log) { const host = await E(vats.host).makeHost(); return trivialContractTest(host); } + case 'exhaust': { + const host = await E(vats.host).makeHost(); + return exhaustedContractTest(host); + } case 'alice-first': { const host = await E(vats.host).makeHost(); const aliceMaker = await E(vats.alice).makeAliceMaker(host); diff --git a/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js b/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js index 10b9900d802..30e94221536 100644 --- a/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js +++ b/packages/spawner/test/swingsetTests/contractHost/test-contractHost.js @@ -58,6 +58,20 @@ test('run contractHost Demo --trivial without SES', async t => { t.end(); }); +const contractExhaustedGolden = [ + '=> setup called', + 'starting exhaustedContractTest', + 'Does source match? true', + 'spawn rejected: Compute meter exceeded', + 'got bar ret: 123', +]; + +test('run contractHost Demo -- exhaust with SES', async t => { + const dump = await main(true, 'contractHost', ['exhaust']); + t.deepEquals(dump.log, contractExhaustedGolden); + t.end(); +}); + const contractAliceFirstGolden = [ '=> setup called', '++ alice.payBobWell starting', diff --git a/packages/zoe/src/evalContractCode.js b/packages/zoe/src/evalContractCode.js index 84621d3368d..ce0a6117f06 100644 --- a/packages/zoe/src/evalContractCode.js +++ b/packages/zoe/src/evalContractCode.js @@ -40,10 +40,6 @@ const evalContractCode = (code, additionalEndowments) => { // the compute meter. const { meter, refillFacet } = makeMeter({ budgetCombined: 1e7 }); const doRefill = () => { - if (meter.isExhausted()) { - // Don't refill. - return; - } // Refill the meter, since we're leaving a crank. Object.values(refillFacet, r => r()); };