diff --git a/packages/inter-protocol/src/price/fluxAggregatorContract.js b/packages/inter-protocol/src/price/fluxAggregatorContract.js index f9e791c2a6e9..fa422e4432ae 100644 --- a/packages/inter-protocol/src/price/fluxAggregatorContract.js +++ b/packages/inter-protocol/src/price/fluxAggregatorContract.js @@ -57,7 +57,13 @@ export const prepare = async (zcf, privateArgs, baggage) => { namesByAddressAdmin, storageNode, } = privateArgs; - assertAllDefined({ initialPoserInvitation, marshaller, storageNode }); + // XXX omit initialPoserInvitation to simplify testing of restart + assertAllDefined({ + highPrioritySendersManager, + marshaller, + namesByAddressAdmin, + storageNode, + }); const { description, timer } = zcf.getTerms(); diff --git a/packages/inter-protocol/src/proposals/core-proposal.js b/packages/inter-protocol/src/proposals/core-proposal.js index e66caa3a646e..ea06514a5323 100644 --- a/packages/inter-protocol/src/proposals/core-proposal.js +++ b/packages/inter-protocol/src/proposals/core-proposal.js @@ -101,6 +101,7 @@ const SHARED_MAIN_MANIFEST = harden({ consume: { board: 'board', chainStorage: true, + diagnostics: true, feeMintAccess: 'zoe', chainTimerService: 'timer', zoe: 'zoe', diff --git a/packages/inter-protocol/src/proposals/econ-behaviors.js b/packages/inter-protocol/src/proposals/econ-behaviors.js index 42c9c205cfbf..6a9fc63b347f 100644 --- a/packages/inter-protocol/src/proposals/econ-behaviors.js +++ b/packages/inter-protocol/src/proposals/econ-behaviors.js @@ -76,6 +76,7 @@ export const setupReserve = async ({ feeMintAccess: feeMintAccessP, chainStorage, chainTimerService, + diagnostics, zoe, economicCommitteeCreatorFacet: committeeCreator, }, @@ -120,19 +121,20 @@ export const setupReserve = async ({ }, }), ); + const privateArgs = { + governed: { + feeMintAccess, + initialPoserInvitation: poserInvitation, + marshaller, + storageNode, + }, + }; /** @type {GovernorStartedInstallationKit} */ const g = await E(zoe).startInstance( governorInstallation, {}, reserveGovernorTerms, - { - governed: { - feeMintAccess, - initialPoserInvitation: poserInvitation, - marshaller, - storageNode, - }, - }, + privateArgs, 'reserve.governor', ); @@ -155,6 +157,9 @@ export const setupReserve = async ({ governorAdminFacet: g.adminFacet, }), ); + const { instancePrivateArgs } = await diagnostics; + instancePrivateArgs.set(instance, privateArgs.governed); + instancePrivateArgs.set(g.instance, privateArgs); reserveInstanceProducer.resolve(instance); reserveGovernor.resolve(g.instance); @@ -459,7 +464,9 @@ export const startRewardDistributor = async ({ ), }); - feeDistributorKit.resolve(instanceKit); + feeDistributorKit.resolve( + harden({ ...instanceKit, label: 'feeDistributor' }), + ); feeDistributorP.resolve(instanceKit.instance); // Initialize the periodic collectors list if we don't have one. diff --git a/packages/inter-protocol/src/proposals/startEconCommittee.js b/packages/inter-protocol/src/proposals/startEconCommittee.js index e684109f7834..ef658cebd091 100644 --- a/packages/inter-protocol/src/proposals/startEconCommittee.js +++ b/packages/inter-protocol/src/proposals/startEconCommittee.js @@ -28,7 +28,7 @@ const sanitizePathSegment = name => { */ export const startEconomicCommittee = async ( { - consume: { board, chainStorage, zoe }, + consume: { board, chainStorage, diagnostics, zoe }, produce: { economicCommitteeKit, economicCommitteeCreatorFacet }, installation: { consume: { committee }, @@ -59,19 +59,26 @@ export const startEconomicCommittee = async ( // NB: committee must only publish what it intended to be public const marshaller = await E(board).getPublishingMarshaller(); + const privateArgs = { + storageNode, + marshaller, + }; const startResult = await E(zoe).startInstance( committee, {}, { committeeName, committeeSize, ...rest }, - { - storageNode, - marshaller, - }, + privateArgs, 'economicCommittee', ); const { creatorFacet, instance } = startResult; - economicCommitteeKit.resolve(startResult); + economicCommitteeKit.resolve( + // XXX should startInstance return its label? + harden({ ...startResult, label: 'economicCommittee' }), + ); + const { instancePrivateArgs } = await diagnostics; + instancePrivateArgs.set(startResult.instance, privateArgs); + economicCommitteeCreatorFacet.resolve(creatorFacet); economicCommittee.resolve(instance); }; @@ -82,6 +89,7 @@ export const ECON_COMMITTEE_MANIFEST = harden({ consume: { board: true, chainStorage: true, + diagnostics: true, zoe: true, }, produce: { diff --git a/packages/inter-protocol/src/proposals/startPSM.js b/packages/inter-protocol/src/proposals/startPSM.js index 6d4b02e50d96..034fad7e3419 100644 --- a/packages/inter-protocol/src/proposals/startPSM.js +++ b/packages/inter-protocol/src/proposals/startPSM.js @@ -259,6 +259,7 @@ export const startPSM = async ( /** @type {MapStore} */ const psmKitMap = await psmKit; + // TODO init into governedContractKits too to simplify testing psmKitMap.init(anchorBrand, newpsmKit); const instanceAdmin = E(agoricNamesAdmin).lookupAdmin('instance'); diff --git a/packages/vats/decentral-test-vaults-config.json b/packages/vats/decentral-test-vaults-config.json index 918b54b22de5..d19a5db8bcec 100644 --- a/packages/vats/decentral-test-vaults-config.json +++ b/packages/vats/decentral-test-vaults-config.json @@ -149,6 +149,15 @@ } } ] + }, + { + "module": "@agoric/vats/scripts/restart-vats.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "skip": {} + } + ] } ], "vats": { diff --git a/packages/vats/scripts/restart-vats.js b/packages/vats/scripts/restart-vats.js new file mode 100644 index 000000000000..c51ff3ba780f --- /dev/null +++ b/packages/vats/scripts/restart-vats.js @@ -0,0 +1,15 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const defaultProposalBuilder = async () => { + const skip = []; + return harden({ + sourceSpec: '../src/proposals/restart-vats-proposal.js', + getManifestCall: ['getManifestForRestart', { skip }], + }); +}; + +export default async (homeP, endowments) => { + const { writeCoreProposal } = await makeHelpers(homeP, endowments); + await writeCoreProposal('restart-vats', defaultProposalBuilder); +}; diff --git a/packages/vats/src/proposals/restart-vats-proposal.js b/packages/vats/src/proposals/restart-vats-proposal.js new file mode 100644 index 000000000000..448c819ab7f8 --- /dev/null +++ b/packages/vats/src/proposals/restart-vats-proposal.js @@ -0,0 +1,138 @@ +/* eslint-disable no-await-in-loop */ +// @ts-check + +import { Fail } from '@agoric/assert'; +import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; +import { E, getInterfaceOf } from '@endo/far'; + +const trace = makeTracer('RV'); + +const HR = '----------------'; + +// FIXME a skip for each +// A YESlist isn't necessary because the collections are known to be complete (tested in test-vaults-upgrade.js) +const SKIP = [ + // can be replaced instead of upgraded + 'auctioneer', + 'feeDistributor', + // below are failing, burn these down +]; + +// FIXME cover each of these collections +// contractKits, psmKit, governedContractKits, vatStore + +// TODO get the privateArgs from the space +// a simple JS Map keyed by Instance like the contractKits are (for join) + +/** + * @param {BootstrapPowers & import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapSpace} space + * @param {{ skip: Array}} opts + */ +export const restartVats = async ({ consume }, opts) => { + console.log(HR); + console.log(HR); + trace('restartVats start', opts); + + trace('awaiting VaultFactorKit as a proxy for "bootstrap done"'); + await consume.vaultFactoryKit; + + trace('testing restarts'); + const { contractKits, governedContractKits } = await deeplyFulfilledObject( + harden({ + contractKits: consume.contractKits, + governedContractKits: consume.governedContractKits, + }), + ); + + const { instancePrivateArgs } = await consume.diagnostics; + + // TODO prepare appropriate privateArgs (potentially by saving with startUpgradable) + + const failures = []; + /** + * + * @param {string} debugName + * @param {Instance} instance + * @param {ERef} adminFacet + */ + const tryRestart = async (debugName, instance, adminFacet) => { + // TODO document that privateArgs cannot contain promises + // TODO try making all the contract starts take resolved values + const privateArgs = await deeplyFulfilledObject( + // @ts-expect-error cast + harden(instancePrivateArgs.get(instance) || {}), + ); + + console.log(HR); + console.log(HR); + console.log(HR); + console.log(HR); + trace('tryRestart', debugName, privateArgs); + + if (SKIP.includes(debugName)) { + trace('SKIPPED', debugName); + return; + } + try { + await E(adminFacet).restartContract(privateArgs); + trace('RESTARTED', debugName); + } catch (err) { + trace('🚨 RESTART FAILED', debugName, err); + failures.push(debugName); + } + }; + + // iterate over the two contractKits and use the adminFacet to restartContract + for (const kit of contractKits.values()) { + const debugName = + kit.label || getInterfaceOf(kit.publicFacet) || 'UNLABELED'; + if (debugName !== kit.label) { + console.warn('MISSING LABEL:', kit); + } + await tryRestart(debugName, kit.instance, kit.adminFacet); + } + + for (const kit of governedContractKits.values()) { + const debugName = + kit.label || getInterfaceOf(kit.publicFacet) || 'UNLABELED'; + if (debugName !== kit.label) { + console.warn('MISSING LABEL:', kit); + } + + trace('restarting governed', debugName); + await tryRestart(debugName, kit.instance, kit.adminFacet); + + trace('restarting governor of', debugName); + await tryRestart( + `${debugName} [Governor]`, + kit.governor, + kit.governorAdminFacet, + ); + } + + trace('restartVats done with ', failures.length, 'failures'); + console.log(HR); + if (failures.length) { + Fail`restart failed for ${failures.join(',')}`; + } + console.log(HR); +}; +harden(restartVats); + +export const getManifestForRestart = _powers => ({ + manifest: { + [restartVats.name]: { + consume: { + contractKits: true, + diagnostics: true, + governedContractKits: true, + loadCriticalVat: true, + zoe: 'zoe', + provisioning: 'provisioning', + vaultFactoryKit: true, + }, + produce: {}, + }, + }, +}); +harden(getManifestForRestart); diff --git a/packages/vats/test/bootstrapTests/test-vats-restart.js b/packages/vats/test/bootstrapTests/test-vats-restart.js new file mode 100644 index 000000000000..fc58a7e54088 --- /dev/null +++ b/packages/vats/test/bootstrapTests/test-vats-restart.js @@ -0,0 +1,23 @@ +// @ts-check +/** + * @file Bootstrap test of restarting (almost) all vats + */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +/** + * @type {import('ava').TestFn>>} + */ +const test = anyTest; + +const makeDefaultTestContext = t => { + t.log('TODO'); +}; + +test.before(async t => { + t.context = await makeDefaultTestContext(t); +}); + +// TODO add an ability to bootstrap tests to execute a proposal +// Until then, the proposal is part of decentral-test-vaults-config.json so it's being +// run with every bootstrapTest +test.todo('restart-vats proposal'); diff --git a/packages/vats/test/bootstrapTests/test-vaults-upgrade.js b/packages/vats/test/bootstrapTests/test-vaults-upgrade.js index 27bb2a85e721..2e91df35d028 100644 --- a/packages/vats/test/bootstrapTests/test-vaults-upgrade.js +++ b/packages/vats/test/bootstrapTests/test-vaults-upgrade.js @@ -442,6 +442,7 @@ test.serial( EV.vat('bootstrap').snapshotStore(await EV(powerStore).get(name)); const contractKits = await getStoreSnapshot('contractKits'); + // TODO refactor the entries to go into governedContractKits too (so the latter is sufficient to test) const psmKit = await getStoreSnapshot('psmKit'); const governedContractKits = await getStoreSnapshot('governedContractKits'); const vatStore = await getStoreSnapshot('vatStore');