diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index c2211d66a4c5..865dd10f92e0 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -87,6 +87,20 @@ const parseUpgradePlanInfo = (upgradePlan, prefix = '') => { * `chainStorageEntries: [ ['c.o', '"top"'], ['c.o.i'], ['c.o.i.n', '42'], ['c.o.w', '"moo"'] ]`). */ +/** + * @typedef {'leftover' | 'forced' | 'high-priority' | 'intermission' | 'queued'} CrankerPhase + * - leftover: work from a previous block + * - forced: work that claims the entirety of the current block + * - high-priority: queued work the precedes timer advancement + * - intermission: needed to note state exports and update consistency hashes + * - queued: queued work the follows timer advancement + */ + +/** + * @typedef {(phase: CrankerPhase) => Promise} Cranker runs the kernel + * and reports if it is time to stop + */ + /** * Return the key in the reserved "host.*" section of the swing-store * @@ -259,25 +273,26 @@ export async function buildSwingset( */ /** - * @param {BeansPerUnit} beansPerUnit + * @param {object} params + * @param {BeansPerUnit} params.beansPerUnit * @param {boolean} [ignoreBlockLimit] * @returns {ChainRunPolicy} */ function computronCounter( - { + { beansPerUnit }, + ignoreBlockLimit = false, +) { + const { [BeansPerBlockComputeLimit]: blockComputeLimit, [BeansPerVatCreation]: vatCreation, [BeansPerXsnapComputron]: xsnapComputron, - }, - ignoreBlockLimit = false, -) { + } = beansPerUnit; assert.typeof(blockComputeLimit, 'bigint'); assert.typeof(vatCreation, 'bigint'); assert.typeof(xsnapComputron, 'bigint'); + let totalBeans = 0n; const shouldRun = () => ignoreBlockLimit || totalBeans < blockComputeLimit; - const remainingBeans = () => - ignoreBlockLimit ? undefined : blockComputeLimit - totalBeans; const policy = harden({ vatCreated() { @@ -303,11 +318,11 @@ function computronCounter( emptyCrank() { return shouldRun(); }, + shouldRun, - remainingBeans, - totalBeans() { - return totalBeans; - }, + remainingBeans: () => + ignoreBlockLimit ? undefined : blockComputeLimit - totalBeans, + totalBeans: () => totalBeans, }); return policy; } @@ -450,10 +465,11 @@ export async function launch({ /** * @param {number} blockHeight * @param {ChainRunPolicy} runPolicy + * @returns {Cranker} */ function makeRunSwingset(blockHeight, runPolicy) { let runNum = 0; - async function runSwingset() { + async function runSwingset(_phase) { const startBeans = runPolicy.totalBeans(); controller.writeSlogObject({ type: 'cosmic-swingset-run-start', @@ -492,10 +508,10 @@ export async function launch({ timer.poll(blockTime); // This is before the initial block, we need to finish processing the // entire bootstrap before opening for business. - const runPolicy = computronCounter(params.beansPerUnit, true); + const runPolicy = computronCounter(params, true); const runSwingset = makeRunSwingset(blockHeight, runPolicy); - await runSwingset(); + await runSwingset('queued'); } async function saveChainState() { @@ -687,15 +703,16 @@ export async function launch({ * newly added, running the kernel to completion after each. * * @param {InboundQueue} inboundQueue - * @param {ReturnType} runSwingset + * @param {Cranker} runSwingset + * @param {CrankerPhase} phase */ - async function processActions(inboundQueue, runSwingset) { + async function processActions(inboundQueue, runSwingset, phase) { let keepGoing = true; for await (const { action, context } of inboundQueue.consumeAll()) { const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`; inboundQueueMetrics.decStat(); await performAction(action, inboundNum); - keepGoing = await runSwingset(); + keepGoing = await runSwingset(phase); if (!keepGoing) { // any leftover actions will remain on the inbound queue for possible // processing in the next block @@ -705,20 +722,32 @@ export async function launch({ return keepGoing; } - async function runKernel(runSwingset, blockHeight, blockTime) { + /** + * Trigger the Swingset runs for this block, stopping when out of relevant + * work or when instructed to (whichever comes first). + * + * @param {Cranker} runSwingset + * @param {number} blockHeight + * @param {number} blockTime seconds since the POSIX epoch + */ + async function processBlockActions(runSwingset, blockHeight, blockTime) { // First, complete leftover work, if any - let keepGoing = await runSwingset(); + let keepGoing = await runSwingset('leftover'); if (!keepGoing) return; // Then, if we have anything in the special runThisBlock queue, process // it and do no further work. if (runThisBlock.size()) { - await processActions(runThisBlock, runSwingset); + await processActions(runThisBlock, runSwingset, 'forced'); return; } // Then, process as much as we can from the priorityQueue. - keepGoing = await processActions(highPriorityQueue, runSwingset); + keepGoing = await processActions( + highPriorityQueue, + runSwingset, + 'high-priority', + ); if (!keepGoing) return; // Then, update the timer device with the new external time, which might @@ -737,11 +766,11 @@ export async function launch({ // We must run the kernel even if nothing was added since the kernel // only notes state exports and updates consistency hashes when attempting // to perform a crank. - keepGoing = await runSwingset(); + keepGoing = await runSwingset('intermission'); if (!keepGoing) return; // Finally, process as much as we can from the actionQueue. - await processActions(actionQueue, runSwingset); + await processActions(actionQueue, runSwingset, 'queued'); } async function endBlock(blockHeight, blockTime, params) { @@ -759,11 +788,11 @@ export async function launch({ // It will also run to completion any work that swingset still had pending. const neverStop = runThisBlock.size() > 0; - // make a runPolicy that will be shared across all cycles - const runPolicy = computronCounter(params.beansPerUnit, neverStop); + // Process the work for this block using a dedicated Cranker with a stateful + // run policy. + const runPolicy = computronCounter(params, neverStop); const runSwingset = makeRunSwingset(blockHeight, runPolicy); - - await runKernel(runSwingset, blockHeight, blockTime); + await processBlockActions(runSwingset, blockHeight, blockTime); if (END_BLOCK_SPIN_MS) { // Introduce a busy-wait to artificially put load on the chain. @@ -950,6 +979,7 @@ export async function launch({ case ActionType.AG_COSMOS_INIT: { allowExportCallback = true; // cleared by saveOutsideState in COMMIT_BLOCK const { blockHeight, isBootstrap, upgradeDetails } = action; + // TODO: parseParams(action.params), for validation? if (!blockNeedsExecution(blockHeight)) { return true;