From b9f677cdf3a33d71dd3368487371812ce4304a57 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 11 May 2023 07:26:05 -0700 Subject: [PATCH] test(bootstrap): restart all contracts --- .../src/proposals/core-proposal.js | 1 + .../src/proposals/econ-behaviors.js | 25 +- .../src/proposals/startEconCommittee.js | 20 +- .../inter-protocol/src/proposals/startPSM.js | 1 + packages/vats/package.json | 1 + packages/vats/scripts/restart-vats.js | 23 ++ .../src/proposals/restart-vats-proposal.js | 133 +++++++++ .../test/bootstrapTests/test-vats-restart.js | 258 ++++++++++++++++++ .../bootstrapTests/test-vaults-upgrade.js | 1 + 9 files changed, 448 insertions(+), 15 deletions(-) create mode 100644 packages/vats/scripts/restart-vats.js create mode 100644 packages/vats/src/proposals/restart-vats-proposal.js create mode 100644 packages/vats/test/bootstrapTests/test-vats-restart.js diff --git a/packages/inter-protocol/src/proposals/core-proposal.js b/packages/inter-protocol/src/proposals/core-proposal.js index c949b7626b4..017b9b6bf2f 100644 --- a/packages/inter-protocol/src/proposals/core-proposal.js +++ b/packages/inter-protocol/src/proposals/core-proposal.js @@ -100,6 +100,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 87276d92978..0bdbd6926b5 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); @@ -454,7 +459,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 e684109f783..ef658cebd09 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 6d4b02e50d9..034fad7e341 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/package.json b/packages/vats/package.json index 88cd91fe4a9..0b919d89bc3 100644 --- a/packages/vats/package.json +++ b/packages/vats/package.json @@ -13,6 +13,7 @@ "build:boot-viz-gov": "node src/authorityViz.js --gov>docs/boot-gov.dot && dot -Tsvg docs/boot-gov.dot >docs/boot-gov.dot.svg", "build:boot-viz-sim": "node src/authorityViz.js --sim-chain >docs/boot-sim.dot && dot -Tsvg docs/boot-sim.dot >docs/boot-sim.dot.svg", "build:boot-viz-sim-gov": "node src/authorityViz.js --sim-chain --gov >docs/boot-sim-gov.dot && dot -Tsvg docs/boot-sim-gov.dot >docs/boot-sim-gov.dot.svg", + "build:restart-vats-proposal": "agoric run scripts/restart-vats.js", "prepack": "tsc --build jsconfig.build.json", "postpack": "git clean -f '*.d.ts*'", "test": "ava", diff --git a/packages/vats/scripts/restart-vats.js b/packages/vats/scripts/restart-vats.js new file mode 100644 index 00000000000..0e46b92ca47 --- /dev/null +++ b/packages/vats/scripts/restart-vats.js @@ -0,0 +1,23 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const defaultProposalBuilder = async () => { + // An includelist 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', + // skip so vaultManager can have prices upon restart; these have been tested as restartable + 'scaledPriceAuthority-ATOM', + ]; + + return harden({ + sourceSpec: '../src/proposals/restart-vats-proposal.js', + getManifestCall: ['getManifestForRestart', harden({ 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 00000000000..5ea51b36f94 --- /dev/null +++ b/packages/vats/src/proposals/restart-vats-proposal.js @@ -0,0 +1,133 @@ +/* eslint-disable no-await-in-loop */ +// @ts-check + +import { Fail } from '@agoric/assert'; +import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; +import { M, mustMatch } from '@agoric/store'; +import { E, getInterfaceOf } from '@endo/far'; + +const trace = makeTracer('RV'); + +const HR = '----------------'; + +/** + * TODO cover each of these collections + * - [x] contractKits + * - [ ] psmKit + * - [x] governedContractKits + * - [ ] vatStore + */ + +/** + * @param {BootstrapPowers & import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapSpace} space + * @param {object} config + * @param {{ skip: Array}} config.options + */ +export const restartVats = async ({ consume }, { options }) => { + console.log(HR); + console.log(HR); + trace('restartVats start', options); + mustMatch(options, harden({ skip: M.arrayOf(M.string()) })); + + 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; + + 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 (options.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, options) => ({ + manifest: { + [restartVats.name]: { + consume: { + contractKits: true, + diagnostics: true, + governedContractKits: true, + loadCriticalVat: true, + zoe: 'zoe', + provisioning: 'provisioning', + vaultFactoryKit: true, + }, + produce: {}, + }, + }, + options, +}); +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 00000000000..1468f01475c --- /dev/null +++ b/packages/vats/test/bootstrapTests/test-vats-restart.js @@ -0,0 +1,258 @@ +// @ts-check +/* global process */ +/** + * @file Bootstrap test of restarting (almost) all vats + */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import * as processAmbient from 'child_process'; +import * as fsAmbient from 'fs'; +import { Fail } from '@agoric/assert'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; +import { makeAgoricNamesRemotesFromFakeStorage } from '../../tools/board-utils.js'; +import { makeSwingsetTestKit, makeWalletFactoryDriver } from './supports.js'; + +/** + * @type {import('ava').TestFn>>} + */ +const test = anyTest; + +const PLATFORM_CONFIG = '@agoric/vats/decentral-devnet-config.json'; + +// presently all these tests use one collateral manager +const collateralBrandKey = 'ATOM'; + +/** + * @param {object} powers + * @param {Pick} powers.childProcess + * @param {typeof import('node:fs/promises')} powers.fs + */ +const makeProposalExtractor = ({ childProcess, fs }) => { + const getPkgPath = (pkg, fileName = '') => + new URL(`../../../${pkg}/${fileName}`, import.meta.url).pathname; + + const runPackageScript = async (pkg, name, env) => { + console.warn(pkg, 'running package script:', name); + const pkgPath = getPkgPath(pkg); + return childProcess.execFileSync('yarn', ['run', name], { + cwd: pkgPath, + env, + }); + }; + + const loadJSON = async filePath => + harden(JSON.parse(await fs.readFile(filePath, 'utf8'))); + + // XXX parses the output to find the files but could write them to a path that can be traversed + /** @param {string} txt */ + const parseProposalParts = txt => { + const evals = [ + ...txt.matchAll(/swingset-core-eval (?\S+) (?