diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index b740a6f5f0f..2e17d073166 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -44,7 +44,7 @@ import { BeansPerXsnapComputron, } from './sim-params.js'; import { parseParams } from './params.js'; -import { makeQueue } from './helpers/make-queue.js'; +import { makeQueue, makeQueueStorageMock } from './helpers/make-queue.js'; import { exportStorage } from './export-storage.js'; import { parseLocatedJson } from './helpers/json.js'; @@ -202,7 +202,8 @@ export async function buildSwingset( /** * @typedef {import('@agoric/swingset-vat').RunPolicy & { * shouldRun(): boolean; - * remainingBeans(): bigint; + * remainingBeans(): bigint | undefined; + * totalBeans(): bigint; * }} ChainRunPolicy */ @@ -215,19 +216,24 @@ export async function buildSwingset( /** * @param {BeansPerUnit} beansPerUnit + * @param {boolean} [ignoreBlockLimit] * @returns {ChainRunPolicy} */ -function computronCounter({ - [BeansPerBlockComputeLimit]: blockComputeLimit, - [BeansPerVatCreation]: vatCreation, - [BeansPerXsnapComputron]: xsnapComputron, -}) { +function computronCounter( + { + [BeansPerBlockComputeLimit]: blockComputeLimit, + [BeansPerVatCreation]: vatCreation, + [BeansPerXsnapComputron]: xsnapComputron, + }, + ignoreBlockLimit = false, +) { assert.typeof(blockComputeLimit, 'bigint'); assert.typeof(vatCreation, 'bigint'); assert.typeof(xsnapComputron, 'bigint'); let totalBeans = 0n; - const shouldRun = () => totalBeans < blockComputeLimit; - const remainingBeans = () => blockComputeLimit - totalBeans; + const shouldRun = () => ignoreBlockLimit || totalBeans < blockComputeLimit; + const remainingBeans = () => + ignoreBlockLimit ? undefined : blockComputeLimit - totalBeans; const policy = harden({ vatCreated() { @@ -251,23 +257,17 @@ function computronCounter({ return shouldRun(); }, emptyCrank() { - return true; + return shouldRun(); }, shouldRun, remainingBeans, + totalBeans() { + return totalBeans; + }, }); return policy; } -function neverStop() { - return harden({ - vatCreated: () => true, - crankComplete: () => true, - crankFailed: () => true, - emptyCrank: () => true, - }); -} - export async function launch({ actionQueueStorage, highPriorityQueueStorage, @@ -316,6 +316,14 @@ export async function launch({ const actionQueue = makeQueue(actionQueueStorage); /** @type {InboundQueue} */ const highPriorityQueue = makeQueue(highPriorityQueueStorage); + /** + * In memory queue holding actions that must be consumed entirely + * during the block. If it's not drained, we open the gates to + * hangover hell. + * + * @type {InboundQueue} + */ + const runThisBlock = makeQueue(makeQueueStorageMock().storage); // Not to be confused with the gas model, this meter is for OpenTelemetry. const metricMeter = metricsProvider.getMeter('ag-chain-cosmos'); @@ -357,14 +365,55 @@ export async function launch({ inboundQueueMetrics, }); - async function bootstrapBlock(_blockHeight, blockTime) { + /** + * @param {number} blockHeight + * @param {ChainRunPolicy} runPolicy + */ + function makeRunSwingset(blockHeight, runPolicy) { + let runNum = 0; + async function runSwingset() { + const startBeans = runPolicy.totalBeans(); + controller.writeSlogObject({ + type: 'cosmic-swingset-run-start', + blockHeight, + runNum, + startBeans, + remainingBeans: runPolicy.remainingBeans(), + }); + // TODO: crankScheduler does a schedulerBlockTimeHistogram thing + // that needs to be revisited, it used to be called once per + // block, now it's once per processed inbound queue item + await crankScheduler(runPolicy); + const finishBeans = runPolicy.totalBeans(); + controller.writeSlogObject({ + type: 'kernel-stats', + stats: controller.getStats(), + }); + controller.writeSlogObject({ + type: 'cosmic-swingset-run-finish', + blockHeight, + runNum, + startBeans, + finishBeans, + usedBeans: finishBeans - startBeans, + remainingBeans: runPolicy.remainingBeans(), + }); + runNum += 1; + return runPolicy.shouldRun(); + } + return runSwingset; + } + + async function bootstrapBlock(blockHeight, blockTime, params) { // We need to let bootstrap know of the chain time. The time of the first // block may be the genesis time, or the block time of the upgrade block. timer.poll(blockTime); // This is before the initial block, we need to finish processing the // entire bootstrap before opening for business. - const policy = neverStop(); - await crankScheduler(policy); + const runPolicy = computronCounter(params.beansPerUnit, true); + const runSwingset = makeRunSwingset(blockHeight, runPolicy); + + await runSwingset(); } async function saveChainState() { @@ -511,66 +560,45 @@ export async function launch({ return p; } - async function runKernel(runPolicy, blockHeight, blockTime) { - let runNum = 0; - async function runSwingset() { - const initialBeans = runPolicy.remainingBeans(); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-start', - blockHeight, - runNum, - initialBeans, - }); - // TODO: crankScheduler does a schedulerBlockTimeHistogram thing - // that needs to be revisited, it used to be called once per - // block, now it's once per processed inbound queue item - await crankScheduler(runPolicy); - const remainingBeans = runPolicy.remainingBeans(); - controller.writeSlogObject({ - type: 'kernel-stats', - stats: controller.getStats(), - }); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-finish', - blockHeight, - runNum, - remainingBeans, - usedBeans: initialBeans - remainingBeans, - }); - runNum += 1; - return runPolicy.shouldRun(); - } - - /** - * Process as much as we can from an inbound queue, which contains - * first the old actions not previously processed, followed by actions - * newly added, running the kernel to completion after each. - * - * @param {InboundQueue} inboundQueue - */ - async function processActions(inboundQueue) { - let keepGoing = true; - await null; - for (const { action, context } of inboundQueue.consumeAll()) { - const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`; - inboundQueueMetrics.decStat(); - await performAction(action, inboundNum); - keepGoing = await runSwingset(); - if (!keepGoing) { - // any leftover actions will remain on the inbound queue for possible - // processing in the next block - break; - } + /** + * Process as much as we can from an inbound queue, which contains + * first the old actions not previously processed, followed by actions + * newly added, running the kernel to completion after each. + * + * @param {InboundQueue} inboundQueue + * @param {ReturnType} runSwingset + */ + async function processActions(inboundQueue, runSwingset) { + let keepGoing = true; + await null; + for (const { action, context } of inboundQueue.consumeAll()) { + const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`; + inboundQueueMetrics.decStat(); + await performAction(action, inboundNum); + keepGoing = await runSwingset(); + if (!keepGoing) { + // any leftover actions will remain on the inbound queue for possible + // processing in the next block + break; } - return keepGoing; } + return keepGoing; + } + async function runKernel(runSwingset, blockHeight, blockTime) { // First, complete leftover work, if any let keepGoing = await runSwingset(); 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); + return; + } + // Then, process as much as we can from the priorityQueue. - keepGoing = await processActions(highPriorityQueue); + keepGoing = await processActions(highPriorityQueue, runSwingset); if (!keepGoing) return; // Then, update the timer device with the new external time, which might @@ -587,7 +615,7 @@ export async function launch({ if (!keepGoing) return; // Finally, process as much as we can from the actionQueue. - await processActions(actionQueue); + await processActions(actionQueue, runSwingset); } async function endBlock(blockHeight, blockTime, params) { @@ -598,13 +626,18 @@ export async function launch({ // First, record new actions (bridge/mailbox/etc events that cosmos // added up for delivery to swingset) into our inboundQueue metrics inboundQueueMetrics.updateLength( - actionQueue.size() + highPriorityQueue.size(), + actionQueue.size() + highPriorityQueue.size() + runThisBlock.size(), ); + // If we have work to complete this block, it needs to run to completion. + // 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); + const runPolicy = computronCounter(params.beansPerUnit, neverStop); + const runSwingset = makeRunSwingset(blockHeight, runPolicy); - await runKernel(runPolicy, blockHeight, blockTime); + await runKernel(runSwingset, blockHeight, blockTime); if (END_BLOCK_SPIN_MS) { // Introduce a busy-wait to artificially put load on the chain. @@ -703,31 +736,21 @@ export async function launch({ // ); switch (action.type) { case ActionType.AG_COSMOS_INIT: { - const { isBootstrap, upgradePlan, blockTime } = action; + const { isBootstrap, upgradePlan, blockTime, params } = action; // This only runs for the very first block on the chain. if (isBootstrap) { verboseBlocks && blockManagerConsole.info('block bootstrap'); savedHeight === 0 || Fail`Cannot run a bootstrap block at height ${savedHeight}`; + const bootstrapBlockParams = parseParams(params); const blockHeight = 0; - const runNum = 0; controller.writeSlogObject({ type: 'cosmic-swingset-bootstrap-block-start', blockTime, }); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-start', - blockHeight, - runNum, - }); await processAction(action.type, async () => - bootstrapBlock(blockHeight, blockTime), + bootstrapBlock(blockHeight, blockTime, bootstrapBlockParams), ); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-finish', - blockHeight, - runNum, - }); controller.writeSlogObject({ type: 'cosmic-swingset-bootstrap-block-finish', blockTime, @@ -735,38 +758,36 @@ export async function launch({ } if (upgradePlan) { const blockHeight = upgradePlan.height; - if (blockNeedsExecution(blockHeight)) { - controller.writeSlogObject({ - type: 'cosmic-swingset-upgrade-start', - blockHeight, - blockTime, - upgradePlan, - }); - // Process upgrade plan - const upgradedAction = { - type: ActionType.ENACTED_UPGRADE, - upgradePlan, - blockHeight, - blockTime, - }; - await doBlockingSend(upgradedAction); - controller.writeSlogObject({ - type: 'cosmic-swingset-upgrade-finish', - blockHeight, - blockTime, - }); - } + + // Process upgrade plan + const upgradedAction = { + type: ActionType.ENACTED_UPGRADE, + upgradePlan, + blockHeight, + blockTime, + }; + await doBlockingSend(upgradedAction); } return true; } case ActionType.ENACTED_UPGRADE: { // Install and execute new core proposals. - const { - upgradePlan: { info: upgradeInfoJson = null } = {}, + const { upgradePlan, blockHeight, blockTime } = action; + + if (!blockNeedsExecution(blockHeight)) { + return undefined; + } + + controller.writeSlogObject({ + type: 'cosmic-swingset-upgrade-start', blockHeight, blockTime, - } = action; + upgradePlan, + }); + + const { info: upgradeInfoJson = null } = upgradePlan || {}; + const upgradePlanInfo = upgradeInfoJson && parseLocatedJson(upgradeInfoJson, 'ENACTED_UPGRADE upgradePlan.info'); @@ -800,9 +821,6 @@ export async function launch({ } // Now queue the code for evaluation. - // - // TODO: Once SwingSet sprouts some tools for preemption, we should use - // them to help the upgrade process finish promptly. const coreEvalAction = { type: ActionType.CORE_EVAL, blockHeight, @@ -814,7 +832,7 @@ export async function launch({ }, ], }; - highPriorityQueue.push({ + runThisBlock.push({ context: { blockHeight, txHash: 'x/upgrade', @@ -823,6 +841,12 @@ export async function launch({ action: coreEvalAction, }); + controller.writeSlogObject({ + type: 'cosmic-swingset-upgrade-finish', + blockHeight, + blockTime, + }); + return undefined; } @@ -836,6 +860,9 @@ export async function launch({ ); } + runThisBlock.size() === 0 || + Fail`We didn't process all "run this block" actions`; + controller.writeSlogObject({ type: 'cosmic-swingset-commit-block-start', blockHeight, diff --git a/packages/cosmic-swingset/src/sim-chain.js b/packages/cosmic-swingset/src/sim-chain.js index 989f97ed40f..19a0a09b4dc 100644 --- a/packages/cosmic-swingset/src/sim-chain.js +++ b/packages/cosmic-swingset/src/sim-chain.js @@ -215,6 +215,7 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) { type: 'AG_COSMOS_INIT', blockTime: scaleBlockTime(Date.now()), isBootstrap: true, + params: DEFAULT_SIM_SWINGSET_PARAMS, }); blockHeight = initialHeight; }; diff --git a/packages/deployment/upgrade-test/Dockerfile b/packages/deployment/upgrade-test/Dockerfile index 57ebcd04e99..79f69a502e9 100644 --- a/packages/deployment/upgrade-test/Dockerfile +++ b/packages/deployment/upgrade-test/Dockerfile @@ -93,9 +93,7 @@ ARG DEST_IMAGE #this is agoric-upgrade-10 upgrading to 11 #it's a separate target because agoric-upgrade-10 takes so long to test FROM ghcr.io/agoric/agoric-sdk:35 as propose-agoric-upgrade-11 -# This default UPGRADE_INFO_11 is to test core proposals like the network vat. -# TODO: Maybe replace with a Zoe core proposal, or remove when other paths test it. -ARG BOOTSTRAP_MODE UPGRADE_INFO_11='{"coreProposals":["@agoric/builders/scripts/vats/init-network.js"]}' +ARG BOOTSTRAP_MODE UPGRADE_INFO_11 ENV THIS_NAME=propose-agoric-upgrade-11 UPGRADE_TO=agoric-upgrade-11 UPGRADE_INFO=${UPGRADE_INFO_11} BOOTSTRAP_MODE=${BOOTSTRAP_MODE} WORKDIR /usr/src/agoric-sdk/ COPY ./bash_entrypoint.sh ./env_setup.sh ./start_to_to.sh ./upgrade-test-scripts/ diff --git a/packages/deployment/upgrade-test/Makefile b/packages/deployment/upgrade-test/Makefile index 50baf2d77d2..3b85db88389 100644 --- a/packages/deployment/upgrade-test/Makefile +++ b/packages/deployment/upgrade-test/Makefile @@ -61,13 +61,16 @@ DEBUG ?= SwingSet:ls,SwingSet:vat RUN = docker run --rm -it \ -p 26656:26656 -p 26657:26657 -p 1317:1317 \ -v "$${PWD}:/workspace" \ - -e "DEST=1" -e "DEBUG=$(DEBUG)" + -e "DEBUG=$(DEBUG)" run: - $(RUN) -e "TMUX_USE_CC=$(tmuxCC)" \ + $(RUN) -e "DEST=1" -e "TMUX_USE_CC=$(tmuxCC)" \ --entrypoint /usr/src/agoric-sdk/upgrade-test-scripts/start_to_to.sh \ $(REPOSITORY):$(dockerLabel) +run_test: + $(RUN) -e "DEST=0" $(REPOSITORY):$(dockerLabel) + run_bash: $(RUN) --entrypoint /bin/bash $(REPOSITORY):$(dockerLabel) diff --git a/packages/deployment/upgrade-test/upgrade-test-scripts/agoric-upgrade-12/pre_test.sh b/packages/deployment/upgrade-test/upgrade-test-scripts/agoric-upgrade-12/pre_test.sh index 334821210f7..374810706d3 100755 --- a/packages/deployment/upgrade-test/upgrade-test-scripts/agoric-upgrade-12/pre_test.sh +++ b/packages/deployment/upgrade-test/upgrade-test-scripts/agoric-upgrade-12/pre_test.sh @@ -2,12 +2,12 @@ . ./upgrade-test-scripts/env_setup.sh -echo Wait for upgrade to settle -waitForBlock 5 - # CWD is agoric-sdk upgrade12=./upgrade-test-scripts/agoric-upgrade-12 +# test that the network vat was installed (see UPGRADE_INFO_12) +test_val "$(yarn --silent node $upgrade12/tools/vat-status.mjs network)" "0" "network vat incarnation" + test_val "$(agd query vstorage children published.boardAux -o json | jq .children)" "[]" "no boardAux children yet" # zoe vat is at incarnation 0