From cccc2e6c61ab815ee89edb008d567437d90dcbd1 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 18 Oct 2024 22:22:50 -0500 Subject: [PATCH 1/5] docs: missing typeof --- packages/SwingSet/tools/run-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SwingSet/tools/run-utils.js b/packages/SwingSet/tools/run-utils.js index 5c1ed2e4b39..0bb0c5e3866 100644 --- a/packages/SwingSet/tools/run-utils.js +++ b/packages/SwingSet/tools/run-utils.js @@ -17,7 +17,7 @@ export const makeRunUtils = controller => { * Wait for exclusive access to the controller, then before relinquishing that access, * enqueue and process a delivery and return the result. * - * @param {() => ERef>} deliveryThunk + * @param {() => ERef>} deliveryThunk * function for enqueueing a delivery and returning the result kpid (if any) * @param {boolean} [voidResult] whether to ignore the result * @returns {Promise} From 84c527175827d65eddf46051582e205b5df92672 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 28 Oct 2024 12:29:36 -0500 Subject: [PATCH 2/5] chore: let makeRunUtils caller provide run policy --- packages/SwingSet/tools/run-utils.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/SwingSet/tools/run-utils.js b/packages/SwingSet/tools/run-utils.js index 0bb0c5e3866..d2676f1055b 100644 --- a/packages/SwingSet/tools/run-utils.js +++ b/packages/SwingSet/tools/run-utils.js @@ -2,12 +2,18 @@ import { Fail, q } from '@endo/errors'; import { kunser } from '@agoric/kmarshal'; import { makeQueue } from '@endo/stream'; -/** @import { ERef } from '@endo/far' */ +/** + * @import { ERef } from '@endo/far' + * @import { RunPolicy } from '../src/types-external.js' + */ + +/** @typedef {{ provideRunPolicy: () => RunPolicy | undefined }} RunPolicyMaker */ /** * @param {import('../src/controller/controller.js').SwingsetController} controller + * @param {RunPolicyMaker} [perfTool] */ -export const makeRunUtils = controller => { +export const makeRunUtils = (controller, perfTool) => { const mutex = makeQueue(); const logRunFailure = reason => console.log('controller.run() failure', reason); @@ -25,7 +31,8 @@ export const makeRunUtils = controller => { const queueAndRun = async (deliveryThunk, voidResult = false) => { await mutex.get(); const kpid = await deliveryThunk(); - const runResultP = controller.run(); + const runPolicy = perfTool && perfTool.provideRunPolicy(); + const runResultP = controller.run(runPolicy); mutex.put(runResultP.catch(logRunFailure)); await runResultP; From c5b71bc591f40be4bec0a42933876c76929f7fcf Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 28 Oct 2024 12:49:17 -0500 Subject: [PATCH 3/5] refactor: factor out computronCounter --- .../cosmic-swingset/src/computron-counter.js | 115 ++++++++++++++++++ packages/cosmic-swingset/src/launch-chain.js | 113 +---------------- 2 files changed, 116 insertions(+), 112 deletions(-) create mode 100644 packages/cosmic-swingset/src/computron-counter.js diff --git a/packages/cosmic-swingset/src/computron-counter.js b/packages/cosmic-swingset/src/computron-counter.js new file mode 100644 index 00000000000..4db024ca7a0 --- /dev/null +++ b/packages/cosmic-swingset/src/computron-counter.js @@ -0,0 +1,115 @@ +// @ts-check + +import { assert } from '@endo/errors'; +import { + BeansPerBlockComputeLimit, + BeansPerVatCreation, + BeansPerXsnapComputron, +} from './sim-params.js'; + +const { hasOwn } = Object; + +/** + * @typedef {object} BeansPerUnit + * @property {bigint} blockComputeLimit + * @property {bigint} vatCreation + * @property {bigint} xsnapComputron + */ + +/** + * Return a stateful run policy that supports two phases: first allow + * non-cleanup work (presumably deliveries) until an overrideable computron + * budget is exhausted, then (iff no work was done and at least one vat cleanup + * budget field is positive) a cleanup phase that allows cleanup work (and + * presumably nothing else) until one of those fields is exhausted. + * https://github.com/Agoric/agoric-sdk/issues/8928#issuecomment-2053357870 + * + * @param {object} params + * @param {BeansPerUnit} params.beansPerUnit + * @param {import('@agoric/swingset-vat').CleanupBudget} [params.vatCleanupBudget] + * @param {boolean} [ignoreBlockLimit] + * @returns {import('./launch-chain.js').ChainRunPolicy} + */ +export function computronCounter( + { beansPerUnit, vatCleanupBudget }, + ignoreBlockLimit = false, +) { + const { + [BeansPerBlockComputeLimit]: blockComputeLimit, + [BeansPerVatCreation]: vatCreation, + [BeansPerXsnapComputron]: xsnapComputron, + } = beansPerUnit; + assert.typeof(blockComputeLimit, 'bigint'); + assert.typeof(vatCreation, 'bigint'); + assert.typeof(xsnapComputron, 'bigint'); + + let totalBeans = 0n; + const shouldRun = () => ignoreBlockLimit || totalBeans < blockComputeLimit; + + const remainingCleanups = { default: Infinity, ...vatCleanupBudget }; + const defaultCleanupBudget = remainingCleanups.default; + let cleanupStarted = false; + let cleanupDone = false; + const cleanupPossible = + Object.values(remainingCleanups).length > 0 + ? Object.values(remainingCleanups).some(n => n > 0) + : defaultCleanupBudget > 0; + if (!cleanupPossible) cleanupDone = true; + /** @type {() => (false | import('@agoric/swingset-vat').CleanupBudget)} */ + const allowCleanup = () => + cleanupStarted && !cleanupDone && { ...remainingCleanups }; + const startCleanup = () => { + assert(!cleanupStarted); + cleanupStarted = true; + return totalBeans === 0n && !cleanupDone; + }; + const didCleanup = details => { + for (const [phase, count] of Object.entries(details.cleanups)) { + if (phase === 'total') continue; + if (!hasOwn(remainingCleanups, phase)) { + // TODO: log unknown phases? + remainingCleanups[phase] = defaultCleanupBudget; + } + remainingCleanups[phase] -= count; + if (remainingCleanups[phase] <= 0) cleanupDone = true; + } + // We return true to allow processing of any BOYD/GC prompted by cleanup, + // even if cleanup as such is now done. + return true; + }; + + const policy = harden({ + vatCreated() { + totalBeans += vatCreation; + return shouldRun(); + }, + crankComplete(details = {}) { + assert.typeof(details, 'object'); + if (details.computrons) { + assert.typeof(details.computrons, 'bigint'); + + // TODO: xsnapComputron should not be assumed here. + // Instead, SwingSet should describe the computron model it uses. + totalBeans += details.computrons * xsnapComputron; + } + return shouldRun(); + }, + crankFailed() { + const failedComputrons = 1000000n; // who knows, 1M is as good as anything + totalBeans += failedComputrons * xsnapComputron; + return shouldRun(); + }, + emptyCrank() { + return shouldRun(); + }, + allowCleanup, + didCleanup, + + shouldRun, + remainingBeans: () => + ignoreBlockLimit ? undefined : blockComputeLimit - totalBeans, + totalBeans: () => totalBeans, + startCleanup, + }); + return policy; +} diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index 9500a380a12..a483fecdb20 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -43,17 +43,11 @@ import { makeSlogCallbacks, } from './kernel-stats.js'; -import { - BeansPerBlockComputeLimit, - BeansPerVatCreation, - BeansPerXsnapComputron, -} from './sim-params.js'; import { parseParams } from './params.js'; import { makeQueue, makeQueueStorageMock } from './helpers/make-queue.js'; import { exportStorage } from './export-storage.js'; import { parseLocatedJson } from './helpers/json.js'; - -const { hasOwn } = Object; +import { computronCounter } from './computron-counter.js'; /** @import { BlockInfo } from '@agoric/internal/src/chain-utils.js' */ /** @import { Mailbox, RunPolicy, SwingSetConfig } from '@agoric/swingset-vat' */ @@ -282,111 +276,6 @@ export async function buildSwingset( * }} ChainRunPolicy */ -/** - * @typedef {object} BeansPerUnit - * @property {bigint} blockComputeLimit - * @property {bigint} vatCreation - * @property {bigint} xsnapComputron - */ - -/** - * Return a stateful run policy that supports two phases: first allow - * non-cleanup work (presumably deliveries) until an overrideable computron - * budget is exhausted, then (iff no work was done and at least one vat cleanup - * budget field is positive) a cleanup phase that allows cleanup work (and - * presumably nothing else) until one of those fields is exhausted. - * https://github.com/Agoric/agoric-sdk/issues/8928#issuecomment-2053357870 - * - * @param {object} params - * @param {BeansPerUnit} params.beansPerUnit - * @param {import('@agoric/swingset-vat').CleanupBudget} [params.vatCleanupBudget] - * @param {boolean} [ignoreBlockLimit] - * @returns {ChainRunPolicy} - */ -function computronCounter( - { beansPerUnit, vatCleanupBudget }, - ignoreBlockLimit = false, -) { - const { - [BeansPerBlockComputeLimit]: blockComputeLimit, - [BeansPerVatCreation]: vatCreation, - [BeansPerXsnapComputron]: xsnapComputron, - } = beansPerUnit; - assert.typeof(blockComputeLimit, 'bigint'); - assert.typeof(vatCreation, 'bigint'); - assert.typeof(xsnapComputron, 'bigint'); - - let totalBeans = 0n; - const shouldRun = () => ignoreBlockLimit || totalBeans < blockComputeLimit; - - const remainingCleanups = { default: Infinity, ...vatCleanupBudget }; - const defaultCleanupBudget = remainingCleanups.default; - let cleanupStarted = false; - let cleanupDone = false; - const cleanupPossible = - Object.values(remainingCleanups).length > 0 - ? Object.values(remainingCleanups).some(n => n > 0) - : defaultCleanupBudget > 0; - if (!cleanupPossible) cleanupDone = true; - /** @type {() => (false | import('@agoric/swingset-vat').CleanupBudget)} */ - const allowCleanup = () => - cleanupStarted && !cleanupDone && { ...remainingCleanups }; - const startCleanup = () => { - assert(!cleanupStarted); - cleanupStarted = true; - return totalBeans === 0n && !cleanupDone; - }; - const didCleanup = details => { - for (const [phase, count] of Object.entries(details.cleanups)) { - if (phase === 'total') continue; - if (!hasOwn(remainingCleanups, phase)) { - // TODO: log unknown phases? - remainingCleanups[phase] = defaultCleanupBudget; - } - remainingCleanups[phase] -= count; - if (remainingCleanups[phase] <= 0) cleanupDone = true; - } - // We return true to allow processing of any BOYD/GC prompted by cleanup, - // even if cleanup as such is now done. - return true; - }; - - const policy = harden({ - vatCreated() { - totalBeans += vatCreation; - return shouldRun(); - }, - crankComplete(details = {}) { - assert.typeof(details, 'object'); - if (details.computrons) { - assert.typeof(details.computrons, 'bigint'); - - // TODO: xsnapComputron should not be assumed here. - // Instead, SwingSet should describe the computron model it uses. - totalBeans += details.computrons * xsnapComputron; - } - return shouldRun(); - }, - crankFailed() { - const failedComputrons = 1000000n; // who knows, 1M is as good as anything - totalBeans += failedComputrons * xsnapComputron; - return shouldRun(); - }, - emptyCrank() { - return shouldRun(); - }, - allowCleanup, - didCleanup, - - shouldRun, - remainingBeans: () => - ignoreBlockLimit ? undefined : blockComputeLimit - totalBeans, - totalBeans: () => totalBeans, - startCleanup, - }); - return policy; -} - /** * @template [T=unknown] * @typedef {object} LaunchOptions From 0bbf63991e769960ee6445b476343aa67cc93b54 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 29 Oct 2024 13:39:08 -0500 Subject: [PATCH 4/5] test(boot): thread computron counter thru test context --- .../boot/test/bootstrapTests/walletFactory.ts | 2 + packages/boot/tools/liquidation.ts | 19 ++++++- packages/boot/tools/supports.ts | 53 ++++++++++++++++++- 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/boot/test/bootstrapTests/walletFactory.ts b/packages/boot/test/bootstrapTests/walletFactory.ts index 0b8fea676bc..ce964047233 100644 --- a/packages/boot/test/bootstrapTests/walletFactory.ts +++ b/packages/boot/test/bootstrapTests/walletFactory.ts @@ -9,9 +9,11 @@ import { makeWalletFactoryDriver } from '../../tools/drivers.js'; export const makeWalletFactoryContext = async ( t, configSpecifier = '@agoric/vm-config/decentral-main-vaults-config.json', + opts = {}, ) => { const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { configSpecifier, + ...opts, }); const { runUtils, storage } = swingsetTestKit; diff --git a/packages/boot/tools/liquidation.ts b/packages/boot/tools/liquidation.ts index 1b91c228120..04fbe9e6499 100644 --- a/packages/boot/tools/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -9,6 +9,7 @@ import { } from '@agoric/vats/tools/board-utils.js'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import type { ExecutionContext } from 'ava'; +import { insistManagerType, makeRunPolicyProvider } from './supports.js'; import { type SwingsetTestKit, makeSwingsetTestKit } from './supports.js'; import { type GovernanceDriver, @@ -305,13 +306,28 @@ export const makeLiquidationTestKit = async ({ }; }; +// asserts x is type doesn't work when using arrow functions +// https://github.com/microsoft/TypeScript/issues/34523 +function assertManagerType(specimen: string): asserts specimen is ManagerType { + insistManagerType(specimen); +} + export const makeLiquidationTestContext = async ( t, io: { env?: Record } = {}, ) => { const { env = {} } = io; + const { + SLOGFILE: slogFile, + SWINGSET_WORKER_TYPE: defaultManagerType = 'local', + } = env; + assertManagerType(defaultManagerType); + const perfTool = + defaultManagerType === 'xsnap' ? makeRunPolicyProvider() : undefined; const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { - slogFile: env.SLOGFILE, + slogFile, + defaultManagerType, + perfTool, }); console.time('DefaultTestContext'); @@ -369,6 +385,7 @@ export const makeLiquidationTestContext = async ( refreshAgoricNamesRemotes, walletFactoryDriver, governanceDriver, + perfTool, }; }; diff --git a/packages/boot/tools/supports.ts b/packages/boot/tools/supports.ts index 7addf5619a5..967fe12d257 100644 --- a/packages/boot/tools/supports.ts +++ b/packages/boot/tools/supports.ts @@ -32,6 +32,7 @@ import { Fail } from '@endo/errors'; import { makeRunUtils, type RunUtils, + type RunPolicyMaker, } from '@agoric/swingset-vat/tools/run-utils.js'; import { boardSlottingMarshaller, @@ -47,6 +48,11 @@ import type { BridgeHandler, IBCMethod } from '@agoric/vats'; import type { BootstrapRootObject } from '@agoric/vats/src/core/lib-boot.js'; import type { EProxy } from '@endo/eventual-send'; import type { FastUSDCCorePowers } from '@agoric/fast-usdc/src/fast-usdc.start.js'; +import { + defaultBeansPerVatCreation, + defaultBeansPerXsnapComputron, +} from '@agoric/cosmic-swingset/src/sim-params.js'; +import { computronCounter } from '@agoric/cosmic-swingset/src/computron-counter.js'; import { icaMocks, protoMsgMockMap, protoMsgMocks } from './ibc/mocks.js'; const trace = makeTracer('BSTSupport', false); @@ -77,6 +83,7 @@ type BootstrapEV = EProxy & { const makeBootstrapRunUtils = makeRunUtils as ( controller: SwingsetController, + perfTool?: RunPolicyMaker, ) => Omit & { EV: BootstrapEV }; const keysToObject = ( @@ -308,6 +315,7 @@ export const matchIter = (t: AvaT, iter, valueRef) => { * @param [options.profileVats] * @param [options.debugVats] * @param [options.defaultManagerType] + * @param [options.perfTool] */ export const makeSwingsetTestKit = async ( log: (..._: any[]) => void, @@ -321,6 +329,7 @@ export const makeSwingsetTestKit = async ( profileVats = [] as string[], debugVats = [] as string[], defaultManagerType = 'local' as ManagerType, + perfTool = undefined as RunPolicyMaker | undefined, } = {}, ) => { console.time('makeBaseSwingsetTestKit'); @@ -538,7 +547,7 @@ export const makeSwingsetTestKit = async ( console.timeLog('makeBaseSwingsetTestKit', 'buildSwingset'); - const runUtils = makeBootstrapRunUtils(controller); + const runUtils = makeBootstrapRunUtils(controller, perfTool); const buildProposal = makeProposalExtractor({ childProcess: childProcessAmbient, @@ -660,3 +669,45 @@ export const makeSwingsetTestKit = async ( }; }; export type SwingsetTestKit = Awaited>; + +export const makeRunPolicyProvider = () => { + const c2b = defaultBeansPerXsnapComputron; + const beansPerUnit = { + // see https://cosgov.org/agoric?msgType=parameterChangeProposal&network=main + blockComputeLimit: 65_000_000n * c2b, + vatCreation: defaultBeansPerVatCreation, + xsnapComputron: c2b, + }; + + /** @type {ReturnType | undefined} */ + let policy; + let counting = false; + + const meter = harden({ + provideRunPolicy: () => { + if (counting && !policy) { + policy = computronCounter({ beansPerUnit }); + } + return policy; + }, + /** @param {boolean} x */ + usePolicy: x => { + counting = x; + if (!counting) { + policy = undefined; + } + }, + totalCount: () => (policy?.totalBeans() || 0n) / c2b, + resetPolicy: () => (policy = undefined), + }); + return meter; +}; + +/** + * + * @param {string} mt + * @returns {asserts mt is ManagerType} + */ +export function insistManagerType(mt) { + assert(['local', 'node-subprocess', 'xsnap', 'xs-worker'].includes(mt)); +} From 19ae5daae92f25837ff51498876f049c46df8754 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 29 Oct 2024 13:39:24 -0500 Subject: [PATCH 5/5] test: demo SWINGSET_WORKER_TYPE, computron count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in a couple tests: $ cd agoric-sdk/packages/boot $ SWINGSET_WORKER_TYPE=xsnap yarn test test/bootstrapTests/price-feed-replace.test.ts ... ✔ setupVaults; run updatePriceFeeds proposals (1m 27.6s) ℹ setPrice computrons 65536531n $ SWINGSET_WORKER_TYPE=xsnap yarn test test/bootstrapTests/orchestration.test.ts ... ✔ stakeAtom - smart wallet (13.8s) ℹ makeAccount computrons 15231491n --- .../test/bootstrapTests/orchestration.test.ts | 28 +++++++++++++++++-- .../bootstrapTests/price-feed-replace.test.ts | 4 +++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index f18260cc417..ec95a0c95ef 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -14,8 +14,16 @@ import { makeWalletFactoryContext, type WalletFactoryTestContext, } from './walletFactory.js'; +import { + insistManagerType, + makeRunPolicyProvider, +} from '../../tools/supports.js'; -const test: TestFn = anyTest; +const test: TestFn< + WalletFactoryTestContext & { + perfTool?: ReturnType; + } +> = anyTest; const validatorAddress: CosmosValidatorAddress = { value: 'cosmosvaloper1test', @@ -25,11 +33,21 @@ const validatorAddress: CosmosValidatorAddress = { const ATOM_DENOM = 'uatom'; +const { + SLOGFILE: slogFile, + SWINGSET_WORKER_TYPE: defaultManagerType = 'local', +} = process.env; + test.before(async t => { - t.context = await makeWalletFactoryContext( + insistManagerType(defaultManagerType); + const perfTool = + defaultManagerType === 'xsnap' ? makeRunPolicyProvider() : undefined; + const ctx = await makeWalletFactoryContext( t, '@agoric/vm-config/decentral-itest-orchestration-config.json', + { slogFile, defaultManagerType, perfTool }, ); + t.context = { ...ctx, perfTool }; }); test.after.always(t => t.context.shutdown?.()); @@ -105,6 +123,7 @@ test.skip('stakeOsmo - queries', async t => { buildProposal, evalProposal, runUtils: { EV }, + perfTool, } = t.context; await evalProposal( buildProposal('@agoric/builders/scripts/orchestration/init-stakeOsmo.js'), @@ -143,6 +162,7 @@ test.serial('stakeAtom - smart wallet', async t => { agoricNamesRemotes, bridgeUtils: { flushInboundQueue }, readPublished, + perfTool, } = t.context; await evalProposal( @@ -153,6 +173,7 @@ test.serial('stakeAtom - smart wallet', async t => { 'agoric1testStakAtom', ); + perfTool?.usePolicy(true); await wd.sendOffer({ id: 'request-account', invitationSpec: { @@ -162,6 +183,9 @@ test.serial('stakeAtom - smart wallet', async t => { }, proposal: {}, }); + perfTool && t.log('makeAccount computrons', perfTool.totalCount()); + perfTool?.usePolicy(false); + await flushInboundQueue(); t.like(wd.getCurrentWalletRecord(), { offerToPublicSubscriberPaths: [ diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts index 4e583fce1da..3e7876b694c 100644 --- a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -64,6 +64,7 @@ test.serial('setupVaults; run updatePriceFeeds proposals', async t => { setupVaults, governanceDriver: gd, readPublished, + perfTool, } = t.context; await setupVaults(collateralBrandKey, managerIndex, setup); @@ -74,7 +75,10 @@ test.serial('setupVaults; run updatePriceFeeds proposals', async t => { roundId: 1n, }); + perfTool && perfTool.usePolicy(true); await priceFeedDrivers[collateralBrandKey].setPrice(15.99); + perfTool && t.log('setPrice computrons', perfTool.totalCount()); + perfTool && perfTool.usePolicy(false); t.like(readPublished('priceFeed.ATOM-USD_price_feed.latestRound'), { roundId: 2n,