diff --git a/packages/cosmic-swingset/economy-template.json b/packages/cosmic-swingset/economy-template.json index 330eccc82759..50612b15f015 100644 --- a/packages/cosmic-swingset/economy-template.json +++ b/packages/cosmic-swingset/economy-template.json @@ -1,138 +1,146 @@ -[ - "@agoric/vats/scripts/init-core.js", - "@agoric/vats/scripts/init-network.js", - { - "module": "@agoric/inter-protocol/scripts/init-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ +{ + "steps": [ + [ + "@agoric/vats/scripts/init-core.js" + ], + [ + "@agoric/vats/scripts/init-network.js", + "@agoric/pegasus/scripts/init-core.js" + ], + [ { - "econCommitteeOptions": { - "committeeSize": 1 - }, - "minInitialPoolLiquidity": "0" - } - ] - }, - "@agoric/pegasus/scripts/init-core.js", - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "module": "@agoric/builders/scripts/inter-protocol/init-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "econCommitteeOptions": { + "committeeSize": 1 + }, + "minInitialPoolLiquidity": "0" + } + ] + }, { - "interchainAssetOptions": { - "denom": "ibc/toyatom", - "decimalPlaces": 4, - "keyword": "ATOM", - "oracleBrand": "ATOM", - "proposedName": "ATOM" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "interchainAssetOptions": { + "denom": "ibc/toyatom", + "decimalPlaces": 4, + "keyword": "ATOM", + "oracleBrand": "ATOM", + "proposedName": "ATOM" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/toyusdc", - "decimalPlaces": 6, - "keyword": "USDC_axl", - "proposedName": "USD Coin" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyusdc", + "decimalPlaces": 6, + "keyword": "USDC_axl", + "proposedName": "USD Coin" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/usdc5678", - "decimalPlaces": 6, - "keyword": "USDC_grv", - "proposedName": "USC Coin" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/usdc5678", + "decimalPlaces": 6, + "keyword": "USDC_grv", + "proposedName": "USC Coin" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/usdt1234", - "decimalPlaces": 6, - "keyword": "USDT_axl", - "proposedName": "Tether USD" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/usdt1234", + "decimalPlaces": 6, + "keyword": "USDT_axl", + "proposedName": "Tether USD" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/toyollie", - "decimalPlaces": 6, - "keyword": "USDT_grv", - "proposedName": "Tether USD" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyollie", + "decimalPlaces": 6, + "keyword": "USDT_grv", + "proposedName": "Tether USD" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/toyellie", - "decimalPlaces": 6, - "keyword": "AUSD", - "proposedName": "Anchor USD" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/price-feed-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyellie", + "decimalPlaces": 6, + "keyword": "AUSD", + "proposedName": "Anchor USD" + } + } + ] + }, { - "AGORIC_INSTANCE_NAME": "ATOM-USD price feed", - "oracleAddresses": [ - "@PRIMARY_ADDRESS@", - "agoric1dy0yegdsev4xvce3dx7zrz2ad9pesf5svzud6y" - ], - "IN_BRAND_LOOKUP": [ - "agoricNames", - "oracleBrand", - "ATOM" - ], - "IN_BRAND_DECIMALS": 6, - "OUT_BRAND_LOOKUP": [ - "agoricNames", - "oracleBrand", - "USD" - ], - "OUT_BRAND_DECIMALS": 4 - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/invite-committee-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/price-feed-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "AGORIC_INSTANCE_NAME": "ATOM-USD price feed", + "oracleAddresses": [ + "@PRIMARY_ADDRESS@", + "agoric1dy0yegdsev4xvce3dx7zrz2ad9pesf5svzud6y" + ], + "IN_BRAND_LOOKUP": [ + "agoricNames", + "oracleBrand", + "ATOM" + ], + "IN_BRAND_DECIMALS": 6, + "OUT_BRAND_LOOKUP": [ + "agoricNames", + "oracleBrand", + "USD" + ], + "OUT_BRAND_DECIMALS": 4 + } + ] + }, { - "voterAddresses": { - "someone": "@PRIMARY_ADDRESS@" - } + "module": "@agoric/inter-protocol/scripts/invite-committee-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "voterAddresses": { + "someone": "@PRIMARY_ADDRESS@" + } + } + ] } ] - } -] + ] +} diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index ec695710aa9a..fda1ce6d0023 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -24,7 +24,10 @@ import { BridgeId as BRIDGE_ID } from '@agoric/internal'; import { makeWithQueue } from '@agoric/internal/src/queue.js'; import * as ActionType from '@agoric/internal/src/action-types.js'; -import { extractCoreProposalBundles } from '@agoric/deploy-script-support/src/extract-proposal.js'; +import { + extractCoreProposalBundles, + mergeCoreProposals, +} from '@agoric/deploy-script-support/src/extract-proposal.js'; import { fileURLToPath } from 'url'; import { @@ -47,6 +50,23 @@ import { parseLocatedJson } from './helpers/json.js'; const console = anylogger('launch-chain'); const blockManagerConsole = anylogger('block-manager'); +/** + * @param {{ info: string } | null | undefined} upgradePlan + * @param {string} prefix + */ +const parseUpgradePlanInfo = (upgradePlan, prefix = '') => { + const { info: upgradeInfoJson = null } = upgradePlan || {}; + + const upgradePlanInfo = + upgradeInfoJson && + parseLocatedJson( + upgradeInfoJson, + `${prefix && `${prefix} `}upgradePlan.info`, + ); + + return harden(upgradePlanInfo || {}); +}; + /** @typedef {import('@agoric/swingset-vat').SwingSetConfig} SwingSetConfig */ /** @@ -84,7 +104,12 @@ export async function buildSwingset( vatconfig, bootstrapArgs, env, - { debugName = undefined, slogCallbacks, slogSender }, + { + callerWillEvaluateCoreProposals = !!bridgeOutbound, + debugName = undefined, + slogCallbacks, + slogSender, + }, ) { const debugPrefix = debugName === undefined ? '' : `${debugName}:`; const mbs = buildMailboxStateMap(mailboxStorage); @@ -141,18 +166,6 @@ export async function buildSwingset( const bootVat = swingsetConfig.vats[swingsetConfig.bootstrap || 'bootstrap']; - // Find the entrypoints for all the core proposals. - if (coreProposals) { - const { bundles, code } = await extractCoreProposalBundles( - coreProposals, - configLocation, // for path resolution - ); - swingsetConfig.bundles = { ...swingsetConfig.bundles, ...bundles }; - - // Tell the bootstrap code how to run the core proposals. - bootVat.parameters = { ...bootVat.parameters, coreProposalCode: code }; - } - if (bridgeOutbound) { const batchChainStorage = (method, args) => bridgeOutbound(BRIDGE_ID.STORAGE, { method, args }); @@ -166,14 +179,36 @@ export async function buildSwingset( bootVat.parameters = { ...bootVat.parameters, chainStorageEntries }; } + // Since only on-chain swingsets like `agd` have a bridge (and thereby + // `CORE_EVAL` support), things like `ag-solo` will need to do the + // coreProposals in the bootstrap vat. + let bridgedCoreProposals; + if (callerWillEvaluateCoreProposals) { + // We have a bridge to run the coreProposals, so do it in the caller. + bridgedCoreProposals = coreProposals; + } else if (coreProposals) { + // We don't have a bridge to run the coreProposals, so do it in the bootVat. + const { bundles, codeSteps } = await extractCoreProposalBundles( + coreProposals, + configLocation, // for path resolution + ); + swingsetConfig.bundles = { ...swingsetConfig.bundles, ...bundles }; + bootVat.parameters = { + ...bootVat.parameters, + coreProposalCodeSteps: codeSteps, + }; + } + swingsetConfig.pinBootstrapRoot = true; await initializeSwingset(swingsetConfig, bootstrapArgs, kernelStorage, { // @ts-expect-error debugPrefix? what's that? debugPrefix, }); + // Let our caller schedule our core proposals. + return bridgedCoreProposals; } - await ensureSwingsetInitialized(); + const coreProposals = await ensureSwingsetInitialized(); const controller = await makeSwingsetController( kernelStorage, deviceEndowments, @@ -188,6 +223,7 @@ export async function buildSwingset( // (either on bootstrap block (0) or in endBlock). return { + coreProposals, controller, mb: mailboxDevice, bridgeInbound: bridgeDevice && bridgeDevice.deliverInbound, @@ -328,7 +364,13 @@ export async function launch({ }); console.debug(`buildSwingset`); - const { controller, mb, bridgeInbound, timer } = await buildSwingset( + const { + coreProposals: bootstrapCoreProposals, + controller, + mb, + bridgeInbound, + timer, + } = await buildSwingset( mailboxStorage, bridgeOutbound, kernelStorage, @@ -569,7 +611,7 @@ export async function launch({ */ async function processActions(inboundQueue, runSwingset) { let keepGoing = true; - for (const { action, context } of inboundQueue.consumeAll()) { + for await (const { action, context } of inboundQueue.consumeAll()) { const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`; inboundQueueMetrics.decStat(); // eslint-disable-next-line no-await-in-loop @@ -728,112 +770,71 @@ export async function launch({ }); } - // Handle block related actions - // Some actions that are integration specific may be handled by the caller - // For example SWING_STORE_EXPORT is handled in chain-main.js - async function doBlockingSend(action) { - await null; - // blockManagerConsole.warn( - // 'FIGME: blockHeight', - // action.blockHeight, - // 'received', - // action.type, - // ); - switch (action.type) { - case ActionType.AG_COSMOS_INIT: { - 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 && savedBeginHeight === 0) || - Fail`Cannot run a bootstrap block at height ${savedHeight}`; - const bootstrapBlockParams = parseParams(params); - const blockHeight = 0; - controller.writeSlogObject({ - type: 'cosmic-swingset-bootstrap-block-start', - blockTime, - }); - // Start a block transaction, but without changing state - // for the upcoming begin block check - saveBeginHeight(savedBeginHeight); - await processAction(action.type, async () => - bootstrapBlock(blockHeight, blockTime, bootstrapBlockParams), - ); - controller.writeSlogObject({ - type: 'cosmic-swingset-bootstrap-block-finish', - blockTime, - }); - } - if (upgradePlan) { - const blockHeight = upgradePlan.height; - - // 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, blockHeight, blockTime } = action; + const doBootstrap = async action => { + const { blockTime, blockHeight, params } = action; + controller.writeSlogObject({ + type: 'cosmic-swingset-bootstrap-block-start', + blockTime, + }); - if (!blockNeedsExecution(blockHeight)) { - return undefined; - } + await null; + try { + verboseBlocks && blockManagerConsole.info('block bootstrap'); + (savedHeight === 0 && savedBeginHeight === 0) || + Fail`Cannot run a bootstrap block at height ${savedHeight}`; + const bootstrapBlockParams = parseParams(params); + + // Start a block transaction, but without changing state + // for the upcoming begin block check + saveBeginHeight(savedBeginHeight); + await processAction(action.type, async () => + bootstrapBlock(blockHeight, blockTime, bootstrapBlockParams), + ); + } finally { + controller.writeSlogObject({ + type: 'cosmic-swingset-bootstrap-block-finish', + blockTime, + }); + } + }; - // Start a block transaction, but without changing state - // for the upcoming begin block check - saveBeginHeight(savedBeginHeight); + const doCoreProposals = async ({ blockHeight, blockTime }, coreProposals) => { + controller.writeSlogObject({ + type: 'cosmic-swingset-upgrade-start', + blockHeight, + blockTime, + coreProposals, + }); - controller.writeSlogObject({ - type: 'cosmic-swingset-upgrade-start', - blockHeight, - blockTime, - upgradePlan, + await null; + try { + // Start a block transaction, but without changing state + // for the upcoming begin block check + saveBeginHeight(savedBeginHeight); + + // Find scripts relative to our location. + const myFilename = fileURLToPath(import.meta.url); + const { bundles, codeSteps: coreEvalCodeSteps } = + await extractCoreProposalBundles(coreProposals, myFilename, { + handleToBundleSpec: async (handle, source, _sequence, _piece) => { + const bundle = await bundleSource(source); + const { endoZipBase64Sha512: hash } = bundle; + const bundleID = `b1-${hash}`; + handle.bundleID = bundleID; + harden(handle); + return harden([`${bundleID}: ${source}`, bundle]); + }, }); - const { info: upgradeInfoJson = null } = upgradePlan || {}; - - const upgradePlanInfo = - upgradeInfoJson && - parseLocatedJson(upgradeInfoJson, 'ENACTED_UPGRADE upgradePlan.info'); - - // Handle the planned core proposals as just another action. - const { coreProposals = [] } = upgradePlanInfo || {}; - - if (!coreProposals.length) { - // Nothing to do. - return undefined; - } - - // Find scripts relative to our location. - const myFilename = fileURLToPath(import.meta.url); - const { bundles, code: coreEvalCode } = - await extractCoreProposalBundles(coreProposals, myFilename, { - handleToBundleSpec: async (handle, source, _sequence, _piece) => { - const bundle = await bundleSource(source); - const { endoZipBase64Sha512: hash } = bundle; - const bundleID = `b1-${hash}`; - handle.bundleID = bundleID; - harden(handle); - return harden([`${bundleID}: ${source}`, bundle]); - }, - }); - - for (const [meta, bundle] of Object.entries(bundles)) { - // eslint-disable-next-line no-await-in-loop - await controller - .validateAndInstallBundle(bundle) - .catch(e => Fail`Cannot validate and install ${meta}: ${e}`); - } + for (const [meta, bundle] of Object.entries(bundles)) { + // eslint-disable-next-line no-await-in-loop + await controller + .validateAndInstallBundle(bundle) + .catch(e => Fail`Cannot validate and install ${meta}: ${e}`); + } - // Now queue the code for evaluation. + // Now queue each step's code for evaluation. + for (const [key, coreEvalCode] of Object.entries(coreEvalCodeSteps)) { const coreEvalAction = { type: ActionType.CORE_EVAL, blockHeight, @@ -849,18 +850,60 @@ export async function launch({ context: { blockHeight, txHash: 'x/upgrade', - msgIdx: 0, + msgIdx: key, }, action: coreEvalAction, }); + } + } finally { + controller.writeSlogObject({ + type: 'cosmic-swingset-upgrade-finish', + blockHeight, + blockTime, + }); + } + }; - controller.writeSlogObject({ - type: 'cosmic-swingset-upgrade-finish', - blockHeight, - blockTime, - }); + // Handle block related actions + // Some actions that are integration specific may be handled by the caller + // For example SWING_STORE_EXPORT is handled in chain-main.js + async function doBlockingSend(action) { + await null; + // blockManagerConsole.warn( + // 'FIGME: blockHeight', + // action.blockHeight, + // 'received', + // action.type, + // ); + switch (action.type) { + case ActionType.AG_COSMOS_INIT: { + const { blockHeight, isBootstrap, upgradePlan } = action; - return undefined; + if (!blockNeedsExecution(blockHeight)) { + return true; + } + + let { coreProposals } = parseUpgradePlanInfo( + upgradePlan, + ActionType.AG_COSMOS_INIT, + ); + + if (isBootstrap) { + // This only runs for the very first block on the chain. + await doBootstrap(action); + + // Merge the core proposals from the bootstrap block with the + // ones from the upgrade plan. + coreProposals = mergeCoreProposals( + bootstrapCoreProposals, + coreProposals, + ); + } + + if (coreProposals) { + await doCoreProposals(action, coreProposals); + } + return true; } case ActionType.COMMIT_BLOCK: { diff --git a/packages/deploy-script-support/src/extract-proposal.js b/packages/deploy-script-support/src/extract-proposal.js index 1fb77e553a2e..185396ef2299 100644 --- a/packages/deploy-script-support/src/extract-proposal.js +++ b/packages/deploy-script-support/src/extract-proposal.js @@ -11,13 +11,38 @@ import { } from './coreProposalBehavior.js'; /** - * @typedef {string | { module: string, entrypoint: string, args?: Array }} ConfigProposal + * @typedef {string | {module: string, entrypoint?: string, args?: Array}} ConfigProposal + */ + +/** + * @typedef {ConfigProposal[] | {steps: ConfigProposal[][]}} CoreProposals */ const { Fail } = assert; const req = createRequire(import.meta.url); +/** + * @param {...(CoreProposals | undefined | null)} args + * @returns {CoreProposals} + */ +export const mergeCoreProposals = (...args) => { + /** @type {ConfigProposal[][]} */ + const steps = []; + for (const coreProposal of args) { + if (!coreProposal) { + continue; + } + if ('steps' in coreProposal) { + steps.push(...coreProposal.steps); + } else { + steps.push(coreProposal); + } + } + return harden({ steps }); +}; +harden(mergeCoreProposals); + /** * @param {(ModuleSpecifier | FilePath)[]} paths * @typedef {string} ModuleSpecifier @@ -43,12 +68,12 @@ const findModule = (initDir, srcSpec) => /** * @param {{ bundleID?: string, bundleName?: string }} handle - mutated then hardened * @param {string} sourceSpec - the specifier of a module to load - * @param {number} sequence - the sequence number of the proposal + * @param {string} key - the key of the proposal * @param {string} piece - the piece of the proposal * @returns {Promise<[string, any]>} */ -const namedHandleToBundleSpec = async (handle, sourceSpec, sequence, piece) => { - handle.bundleName = `coreProposal${sequence}_${piece}`; +const namedHandleToBundleSpec = async (handle, sourceSpec, key, piece) => { + handle.bundleName = `coreProposal${String(key)}_${piece}`; harden(handle); return harden([handle.bundleName, { sourceSpec }]); }; @@ -64,12 +89,12 @@ const namedHandleToBundleSpec = async (handle, sourceSpec, sequence, piece) => { * but for sim-chain and such, they can be declared statically in * the chain configuration, in which case they are run at bootstrap. * - * @param {ConfigProposal[]} coreProposals - governance + * @param {CoreProposals} coreProposals - governance * proposals to run at chain bootstrap for scenarios such as sim-chain. * @param {FilePath} [dirname] * @param {object} [opts] * @param {typeof makeEnactCoreProposalsFromBundleRef} [opts.makeEnactCoreProposals] - * @param {(i: number) => number} [opts.getSequenceForProposal] + * @param {(key: PropertyKey) => PropertyKey} [opts.getSequenceForProposal] * @param {typeof namedHandleToBundleSpec} [opts.handleToBundleSpec] */ export const extractCoreProposalBundles = async ( @@ -79,7 +104,7 @@ export const extractCoreProposalBundles = async ( ) => { const { makeEnactCoreProposals = makeEnactCoreProposalsFromBundleRef, - getSequenceForProposal = i => i, + getSequenceForProposal = key => key, handleToBundleSpec = namedHandleToBundleSpec, } = opts || {}; @@ -91,159 +116,177 @@ export const extractCoreProposalBundles = async ( /** @type {Map<{ bundleID?: string, bundleName?: string }, { source: string, bundle?: string }>} */ const bundleHandleToAbsolutePaths = new Map(); + const proposalSteps = + 'steps' in coreProposals ? coreProposals.steps : [coreProposals]; const bundleToSource = new Map(); - const extracted = await Promise.all( - coreProposals.map(async (coreProposal, i) => { - // console.debug(`Parsing core proposal:`, coreProposal); + const extractedSteps = await Promise.all( + proposalSteps.map((proposalStep, i) => + Promise.all( + proposalStep.map(async (coreProposal, j) => { + const key = `${i}.${j}`; + // console.debug(`Parsing core proposal:`, coreProposal); - /** @type {string} */ - let entrypoint; - /** @type {unknown[]} */ - let args; - /** @type {string} */ - let module; - if (typeof coreProposal === 'string') { - module = coreProposal; - entrypoint = 'defaultProposalBuilder'; - args = []; - } else { - ({ module, entrypoint, args = [] } = coreProposal); - } - - typeof module === 'string' || - Fail`coreProposal module ${module} must be string`; - typeof entrypoint === 'string' || - Fail`coreProposal entrypoint ${entrypoint} must be string`; - Array.isArray(args) || Fail`coreProposal args ${args} must be array`; - - const thisProposalBundleHandles = new Set(); - assert(getSequenceForProposal); - const thisProposalSequence = getSequenceForProposal(i); - const initPath = findModule(dirname, module); - const initDir = path.dirname(initPath); - /** @type {Record} */ - const ns = await import(initPath); - const install = (srcSpec, bundlePath) => { - const absoluteSrc = findModule(initDir, srcSpec); - const bundleHandle = {}; - const absolutePaths = { source: absoluteSrc }; - if (bundlePath) { - const absoluteBundle = pathResolve(initDir, bundlePath); - absolutePaths.bundle = absoluteBundle; - const oldSource = bundleToSource.get(absoluteBundle); - if (oldSource) { - oldSource === absoluteSrc || - Fail`${bundlePath} already installed from ${oldSource}, now ${absoluteSrc}`; + /** @type {string} */ + let entrypoint; + /** @type {unknown[]} */ + let args; + /** @type {string} */ + let module; + if (typeof coreProposal === 'string') { + module = coreProposal; + entrypoint = 'defaultProposalBuilder'; + args = []; } else { - bundleToSource.set(absoluteBundle, absoluteSrc); + ({ + module, + entrypoint = 'defaultProposalBuilder', + args = [], + } = coreProposal); } - } - // Don't harden the bundleHandle since we need to set the bundleName on - // its unique identity later. - thisProposalBundleHandles.add(bundleHandle); - bundleHandleToAbsolutePaths.set(bundleHandle, harden(absolutePaths)); - return bundleHandle; - }; - /** @type {import('./externalTypes.js').PublishBundleRef} */ - const publishRef = async handleP => { - const handle = await handleP; - // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */ - // @ts-ignore xxx types - bundleHandleToAbsolutePaths.has(handle) || - Fail`${handle} not in installed bundles`; - return handle; - }; - const proposal = await ns[entrypoint]( - { - publishRef, - // @ts-expect-error not statically verified to return a full obj - install, - }, - ...args, - ); - // Add the proposal bundle handles in sorted order. - const bundleSpecEntries = await Promise.all( - [...thisProposalBundleHandles.keys()] - .map(handle => [handle, bundleHandleToAbsolutePaths.get(handle)]) - .sort(([_hnda, { source: a }], [_hndb, { source: b }]) => { - if (a < b) { - return -1; - } - if (a > b) { - return 1; + typeof module === 'string' || + Fail`coreProposal module ${module} must be string`; + typeof entrypoint === 'string' || + Fail`coreProposal entrypoint ${entrypoint} must be string`; + Array.isArray(args) || Fail`coreProposal args ${args} must be array`; + + const thisProposalBundleHandles = new Set(); + assert(getSequenceForProposal); + const thisProposalSequence = getSequenceForProposal(key); + const initPath = findModule(dirname, module); + const initDir = path.dirname(initPath); + /** @type {Record} */ + const ns = await import(initPath); + const install = (srcSpec, bundlePath) => { + const absoluteSrc = findModule(initDir, srcSpec); + const bundleHandle = {}; + const absolutePaths = { source: absoluteSrc }; + if (bundlePath) { + const absoluteBundle = pathResolve(initDir, bundlePath); + absolutePaths.bundle = absoluteBundle; + const oldSource = bundleToSource.get(absoluteBundle); + if (oldSource) { + oldSource === absoluteSrc || + Fail`${bundlePath} already installed from ${oldSource}, now ${absoluteSrc}`; + } else { + bundleToSource.set(absoluteBundle, absoluteSrc); + } } - return 0; - }) - .map(async ([handle, absolutePaths], j) => { - // Transform the bundle handle identity into a bundleName reference. - const specEntry = await handleToBundleSpec( - handle, - absolutePaths.source, - thisProposalSequence, - String(j), + // Don't harden the bundleHandle since we need to set the bundleName on + // its unique identity later. + thisProposalBundleHandles.add(bundleHandle); + bundleHandleToAbsolutePaths.set( + bundleHandle, + harden(absolutePaths), ); - harden(handle); - return specEntry; - }), - ); + return bundleHandle; + }; + /** @type {import('./externalTypes.js').PublishBundleRef} */ + const publishRef = async handleP => { + const handle = await handleP; + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */ + // @ts-ignore xxx types + bundleHandleToAbsolutePaths.has(handle) || + Fail`${handle} not in installed bundles`; + return handle; + }; + const proposal = await ns[entrypoint]( + { + publishRef, + // @ts-expect-error not statically verified to return a full obj + install, + }, + ...args, + ); - // Now that we've assigned all the bundleNames and hardened the - // handles, we can extract the behavior bundle. - const { sourceSpec, getManifestCall } = await deeplyFulfilledObject( - harden(proposal), - ); + // Add the proposal bundle handles in sorted order. + const bundleSpecEntries = await Promise.all( + [...thisProposalBundleHandles.keys()] + .map(handle => [handle, bundleHandleToAbsolutePaths.get(handle)]) + .sort(([_hnda, { source: a }], [_hndb, { source: b }]) => { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }) + .map(async ([handle, absolutePaths], k) => { + // Transform the bundle handle identity into a bundleName reference. + const specEntry = await handleToBundleSpec( + handle, + absolutePaths.source, + thisProposalSequence, + String(k), + ); + harden(handle); + return specEntry; + }), + ); - const proposalSource = pathResolve(initDir, sourceSpec); - const proposalNS = await import(proposalSource); - const [manifestGetterName, ...manifestGetterArgs] = getManifestCall; - manifestGetterName in proposalNS || - Fail`proposal ${proposalSource} missing export ${manifestGetterName}`; - const { manifest: customManifest } = await proposalNS[manifestGetterName]( - harden({ restoreRef: () => null }), - ...manifestGetterArgs, - ); + // Now that we've assigned all the bundleNames and hardened the + // handles, we can extract the behavior bundle. + const { sourceSpec, getManifestCall } = await deeplyFulfilledObject( + harden(proposal), + ); - const behaviorBundleHandle = {}; - const specEntry = await handleToBundleSpec( - behaviorBundleHandle, - proposalSource, - thisProposalSequence, - 'behaviors', - ); - bundleSpecEntries.unshift(specEntry); + const proposalSource = pathResolve(initDir, sourceSpec); + const proposalNS = await import(proposalSource); + const [manifestGetterName, ...manifestGetterArgs] = getManifestCall; + manifestGetterName in proposalNS || + Fail`proposal ${proposalSource} missing export ${manifestGetterName}`; + const { manifest: customManifest } = await proposalNS[ + manifestGetterName + ](harden({ restoreRef: () => null }), ...manifestGetterArgs); - bundleHandleToAbsolutePaths.set( - behaviorBundleHandle, - harden({ - source: proposalSource, - }), - ); + const behaviorBundleHandle = {}; + const specEntry = await handleToBundleSpec( + behaviorBundleHandle, + proposalSource, + thisProposalSequence, + 'proposalNS', + ); + bundleSpecEntries.unshift(specEntry); + + bundleHandleToAbsolutePaths.set( + behaviorBundleHandle, + harden({ + source: proposalSource, + }), + ); - return harden({ - ref: behaviorBundleHandle, - call: getManifestCall, - customManifest, - bundleSpecs: bundleSpecEntries, - }); - }), + return /** @type {const} */ ([ + key, + { + ref: behaviorBundleHandle, + call: getManifestCall, + customManifest, + bundleSpecs: bundleSpecEntries, + }, + ]); + }), + ), + ), ); // Extract all the bundle specs in already-sorted order. const bundles = Object.fromEntries( - extracted.flatMap(({ bundleSpecs }) => bundleSpecs), + extractedSteps.flatMap(step => + step.flatMap(([_key, { bundleSpecs }]) => bundleSpecs), + ), ); harden(bundles); - // Extract the manifest references and calls. - const metadataRecords = extracted.map(({ ref, call, customManifest }) => ({ - ref, - call, - customManifest, - })); - harden(metadataRecords); + const codeSteps = extractedSteps.map(extractedStep => { + // Extract the manifest references and calls. + const metadataRecords = extractedStep.map(([_key, extractedSpec]) => { + const { ref, call, customManifest } = extractedSpec; + return { ref, call, customManifest }; + }); + harden(metadataRecords); - const code = `\ + const code = `\ // This is generated by @agoric/deploy-script-support/src/extract-proposal.js - DO NOT EDIT /* eslint-disable */ @@ -260,11 +303,13 @@ const enactCoreProposals = (( ) => makeEnactCoreProposals({ metadataRecords, E }))(); enactCoreProposals; `; + return defangAndTrim(code); + }); // console.debug('created bundles from proposals:', coreProposals, bundles); - return { + return harden({ bundles, - code: defangAndTrim(code), + codeSteps, bundleHandleToAbsolutePaths, - }; + }); }; diff --git a/packages/vats/decentral-devnet-config.json b/packages/vats/decentral-devnet-config.json index 5a3f72a01617..3de1d481206f 100644 --- a/packages/vats/decentral-devnet-config.json +++ b/packages/vats/decentral-devnet-config.json @@ -2,159 +2,167 @@ "$comment": "This SwingSet config file (see loadSwingsetConfigFile) includes non-production facilities such as a faucet. Pending #5819, it includes vaults in coreProposals; once #5819 is done, vaults are expected to be added by devnet governance.", "bootstrap": "bootstrap", "defaultReapInterval": 1000, - "coreProposals": [ - "@agoric/vats/scripts/init-core.js", - "@agoric/vats/scripts/init-network.js", - { - "module": "@agoric/inter-protocol/scripts/init-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "coreProposals": { + "steps": [ + [ + "@agoric/vats/scripts/init-core.js" + ], + [ + "@agoric/vats/scripts/init-network.js" + ], + [ { - "econCommitteeOptions": { - "committeeSize": 3 - }, - "referencedUi": "bafybeidvpbtlgefi3ptuqzr2fwfyfjqfj6onmye63ij7qkrb4yjxekdh3e", - "minInitialPoolLiquidity": "0" - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/init-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "econCommitteeOptions": { + "committeeSize": 3 + }, + "referencedUi": "bafybeidvpbtlgefi3ptuqzr2fwfyfjqfj6onmye63ij7qkrb4yjxekdh3e", + "minInitialPoolLiquidity": "0" + } + ] + }, { - "interchainAssetOptions": { - "denom": "ibc/toyatom", - "decimalPlaces": 6, - "initialPrice": 12.34, - "keyword": "ATOM", - "oracleBrand": "ATOM", - "proposedName": "ATOM" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "interchainAssetOptions": { + "denom": "ibc/toyatom", + "decimalPlaces": 6, + "initialPrice": 12.34, + "keyword": "ATOM", + "oracleBrand": "ATOM", + "proposedName": "ATOM" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/toyusdc", - "decimalPlaces": 6, - "keyword": "USDC_axl", - "proposedName": "USD Coin" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyusdc", + "decimalPlaces": 6, + "keyword": "USDC_axl", + "proposedName": "USD Coin" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/usdc5678", - "decimalPlaces": 6, - "keyword": "USDC_grv", - "proposedName": "USC Coin" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/usdc5678", + "decimalPlaces": 6, + "keyword": "USDC_grv", + "proposedName": "USC Coin" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/usdt1234", - "decimalPlaces": 6, - "keyword": "USDT_axl", - "proposedName": "Tether USD" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/usdt1234", + "decimalPlaces": 6, + "keyword": "USDT_axl", + "proposedName": "Tether USD" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/toyollie", - "decimalPlaces": 6, - "keyword": "USDT_grv", - "proposedName": "Tether USD" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", - "entrypoint": "psmProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyollie", + "decimalPlaces": 6, + "keyword": "USDT_grv", + "proposedName": "Tether USD" + } + } + ] + }, { - "anchorOptions": { - "denom": "ibc/toyellie", - "decimalPlaces": 6, - "keyword": "AUSD", - "proposedName": "Anchor USD" - } - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/price-feed-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyellie", + "decimalPlaces": 6, + "keyword": "AUSD", + "proposedName": "Anchor USD" + } + } + ] + }, { - "contractTerms": { - "POLL_INTERVAL": 30, - "maxSubmissionCount": 1000, - "minSubmissionCount": 3, - "restartDelay": 1, - "timeout": 10, - "minSubmissionValue": 1, - "maxSubmissionValue": 9007199254740991 - }, - "AGORIC_INSTANCE_NAME": "ATOM-USD price feed", - "oracleAddresses": [ - "agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk", - "agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf", - "agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft", - "agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4", - "agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82" - ], - "IN_BRAND_LOOKUP": [ - "agoricNames", - "oracleBrand", - "ATOM" - ], - "IN_BRAND_DECIMALS": 6, - "OUT_BRAND_LOOKUP": [ - "agoricNames", - "oracleBrand", - "USD" - ], - "OUT_BRAND_DECIMALS": 4 - } - ] - }, - { - "module": "@agoric/inter-protocol/scripts/invite-committee-core.js", - "entrypoint": "defaultProposalBuilder", - "args": [ + "module": "@agoric/inter-protocol/scripts/price-feed-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "contractTerms": { + "POLL_INTERVAL": 30, + "maxSubmissionCount": 1000, + "minSubmissionCount": 3, + "restartDelay": 1, + "timeout": 10, + "minSubmissionValue": 1, + "maxSubmissionValue": 9007199254740991 + }, + "AGORIC_INSTANCE_NAME": "ATOM-USD price feed", + "oracleAddresses": [ + "agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk", + "agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf", + "agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft", + "agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4", + "agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82" + ], + "IN_BRAND_LOOKUP": [ + "agoricNames", + "oracleBrand", + "ATOM" + ], + "IN_BRAND_DECIMALS": 6, + "OUT_BRAND_LOOKUP": [ + "agoricNames", + "oracleBrand", + "USD" + ], + "OUT_BRAND_DECIMALS": 4 + } + ] + }, { - "voterAddresses": { - "gov1": "agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce", - "gov2": "agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang", - "gov3": "agoric1w8wktaur4zf8qmmtn3n7x3r0jhsjkjntcm3u6h" - } + "module": "@agoric/inter-protocol/scripts/invite-committee-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "voterAddresses": { + "gov1": "agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce", + "gov2": "agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang", + "gov3": "agoric1w8wktaur4zf8qmmtn3n7x3r0jhsjkjntcm3u6h" + } + } + ] } ] - } - ], + ] + }, "vats": { "bootstrap": { "sourceSpec": "@agoric/vats/src/core/boot-chain.js", diff --git a/packages/vats/src/core/boot-chain.js b/packages/vats/src/core/boot-chain.js index e63a8ac255f1..a5b963c28bec 100644 --- a/packages/vats/src/core/boot-chain.js +++ b/packages/vats/src/core/boot-chain.js @@ -29,7 +29,7 @@ export const MANIFEST = CHAIN_BOOTSTRAP_MANIFEST; * logger: (msg) => void, * }} vatPowers * @param {{ - * coreProposalCode?: string, + * coreProposalCodeSteps?: string[]; * }} vatParameters * @param {import('@agoric/vat-data').Baggage} baggage */ diff --git a/packages/vats/src/core/boot-sim.js b/packages/vats/src/core/boot-sim.js index e5de1d9032a7..3350b77c793f 100644 --- a/packages/vats/src/core/boot-sim.js +++ b/packages/vats/src/core/boot-sim.js @@ -31,7 +31,7 @@ const modules = harden({ behaviors: { ...behaviors }, utils: { ...utils } }); * logger: (msg) => void, * }} vatPowers * @param {{ - * coreProposalCode?: string, + * coreProposalCodeSteps?: string[]; * }} vatParameters */ export const buildRootObject = (vatPowers, vatParameters) => { diff --git a/packages/vats/src/core/boot-solo.js b/packages/vats/src/core/boot-solo.js index 5528618b5cb5..d6edc93723b3 100644 --- a/packages/vats/src/core/boot-solo.js +++ b/packages/vats/src/core/boot-solo.js @@ -35,8 +35,8 @@ const modules = harden({ * logger: (msg) => void, * }} vatPowers * @param {{ - * bootstrapManifest?: Record>, - * coreProposalCode?: string, + * bootstrapManifest?: Record>; + * coreProposalCodeSteps?: string[]; * }} vatParameters */ export const buildRootObject = (vatPowers, vatParameters) => { diff --git a/packages/vats/src/core/lib-boot.js b/packages/vats/src/core/lib-boot.js index 505dbd3979a5..7997850e5c7e 100644 --- a/packages/vats/src/core/lib-boot.js +++ b/packages/vats/src/core/lib-boot.js @@ -93,9 +93,8 @@ export const makeBootstrap = ( ); const namesVat = namedVat.consume.agoricNames; - const { nameHub: agoricNames, nameAdmin: agoricNamesAdmin } = await E( - namesVat, - ).getNameHubKit(); + const nameHubKit = await E(namesVat).getNameHubKit(); + const { nameHub: agoricNames, nameAdmin: agoricNamesAdmin } = nameHubKit; const spaces = await makeWellKnownSpaces(agoricNamesAdmin, log); produce.agoricNames.resolve(agoricNames); produce.agoricNamesAdmin.resolve(agoricNamesAdmin); @@ -132,25 +131,33 @@ export const makeBootstrap = ( await runBehaviors(bootManifest); - const { coreProposalCode } = vatParameters; - if (!coreProposalCode) { + /** @type {{ coreProposalCodeSteps?: string[] }} */ + const { coreProposalCodeSteps } = vatParameters; + if (!coreProposalCodeSteps) { return; } - // Start the governance from the core proposals. - const coreEvalMessage = { - type: 'CORE_EVAL', - evals: [ - { - json_permits: 'true', - js_code: coreProposalCode, - }, - ], - }; - /** @type {{coreEvalBridgeHandler: Promise}} */ + /** + * @type {{ + * coreEvalBridgeHandler: Promise; + * }} + */ // @ts-expect-error cast const { coreEvalBridgeHandler } = consume; - await E(coreEvalBridgeHandler).fromBridge(coreEvalMessage); + + // Start the governance from the core proposals. + for await (const coreProposalCode of coreProposalCodeSteps) { + const coreEvalMessage = { + type: 'CORE_EVAL', + evals: [ + { + json_permits: 'true', + js_code: coreProposalCode, + }, + ], + }; + await E(coreEvalBridgeHandler).fromBridge(coreEvalMessage); + } }; // For testing supports diff --git a/packages/vats/test/bootstrapTests/supports.js b/packages/vats/test/bootstrapTests/supports.js index 56a7726d5749..9b64e00b3c64 100644 --- a/packages/vats/test/bootstrapTests/supports.js +++ b/packages/vats/test/bootstrapTests/supports.js @@ -322,7 +322,10 @@ export const makeSwingsetTestKit = async ( configPath, {}, {}, - { debugName: 'TESTBOOT' }, + { + callerWillEvaluateCoreProposals: false, + debugName: 'TESTBOOT', + }, ); console.timeLog('makeSwingsetTestKit', 'buildSwingset');