From 5c0ac62bef79ad519398477479957f03541cf6dc Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 8 Jan 2025 15:12:51 +0000 Subject: [PATCH] feat(cosmic-swingset): introduce GovernorExecutor #10725 refs: #10725 Initial set of changes to get auctioneer governors terminated. --- golang/cosmos/app/upgrade.go | 20 ++ .../scripts/vats/upgrade-governor-instance.js | 206 ++++++++++++++++++ .../src/contractGovernorExecutor.js | 26 +++ 3 files changed, 252 insertions(+) create mode 100644 packages/builders/scripts/vats/upgrade-governor-instance.js create mode 100644 packages/governance/src/contractGovernorExecutor.js diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index da3407ecd40..657d76ce85e 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -192,6 +192,26 @@ func terminateGovernorCoreProposal(upgradeName string) (vm.CoreProposalStep, err ) } +func upgradeGovernorExecutorCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { + // targets is a slice of "$boardID:$instanceKitLabel" strings. + var targets []string + switch getVariantFromUpgradeName(upgradeName) { + case "EMERYNET": + targets = []string{"board04149:auctioneer"} // v38: governor for v39 + //"boardXXXYYY:autioneer", + // fixme: need some targets here + default: + return nil, nil + } + + return buildProposalStepWithArgs( + "@agoric/builders/scripts/vats/upgrade-governor-instance.js", + // Request `defaultProposalBuilder(powers, targets)`. + "defaultProposalBuilder", + []any{targets}, + ) +} + // func upgradeMintHolderCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { // variant := getVariantFromUpgradeName(upgradeName) diff --git a/packages/builders/scripts/vats/upgrade-governor-instance.js b/packages/builders/scripts/vats/upgrade-governor-instance.js new file mode 100644 index 00000000000..88d1a0d4050 --- /dev/null +++ b/packages/builders/scripts/vats/upgrade-governor-instance.js @@ -0,0 +1,206 @@ +/** + * @file Upgrade price-feed governor instances such as emerynet v664. + * Functions as both an off-chain builder and an on-chain core-eval. + */ + +/// + +import { E } from '@endo/far'; + +const SELF = '@agoric/builders/scripts/vats/terminate-governor-instance.js'; +const USAGE = `Usage: agoric run /path/to/terminate-governor-instance.js \\ + <$governorInstanceHandleBoardID:$instanceKitLabel>...`; + +const repr = val => + typeof val === 'string' || (typeof val === 'object' && val !== null) + ? JSON.stringify(val) + : String(val); +const defaultMakeError = (strings, ...subs) => + Error( + strings.map((s, i) => `${i === 0 ? '' : repr(subs[i - 1])}${s}`).join(''), + ); +const makeUsageError = (strings, ...subs) => { + const err = defaultMakeError(strings, ...subs); + console.error(err.message); + console.error(USAGE); + return err; +}; + +const rtarget = /^(?board[0-9]+):(?.+)$/; +/** + * @param {string[]} args + * @param {(strings: TemplateStringsArray | string[], ...subs: unknown[]) => Error} [makeError] + * @returns {Array<{boardID: string, instanceKitLabel: string}>} + */ +const parseTargets = (args = [], makeError = defaultMakeError) => { + if (!Array.isArray(args)) throw makeError`invalid targets: ${args}`; + /** @type {Array<{boardID: string, instanceKitLabel: string}>} */ + const targets = []; + const badTargets = []; + for (const arg of args) { + const m = typeof arg === 'string' && arg.match(rtarget); + if (!m) { + badTargets.push(arg); + } else { + // @ts-expect-error cast + targets.push(m.groups); + } + } + if (badTargets.length !== 0) { + throw makeError`malformed target(s): ${badTargets}`; + } else if (targets.length === 0) { + throw makeError`no target(s)`; + } + return targets; +}; + +/** + * @param {BootstrapPowers} powers + * @param {{ options: { governorExecutorBundle: any, targetSpecifiers: string[] } }} config + */ +export const upgradeGovernors = async ( + { consume: { board, governedContractKits } }, + { options: { governorExecutorBundle, targetSpecifiers } }, +) => { + const { Fail, quote: q } = assert; + const targets = parseTargets(targetSpecifiers, Fail); + const doneP = Promise.allSettled( + targets.map(async ({ boardID, instanceKitLabel }) => { + const logLabel = [boardID, instanceKitLabel]; + const contractInstanceHandle = await E(board).getValue(boardID); + const instanceKit = await E(governedContractKits).get( + // @ts-expect-error TS2345 Property '[tag]' is missing + contractInstanceHandle, + ); + console.log( + `${q(logLabel)} alleged governor contract instance kit`, + instanceKit, + ); + const { label, governorAdminFacet, adminFacet } = instanceKit; + label === instanceKitLabel || + Fail`${q(logLabel)} unexpected instanceKit label, got ${label} but wanted ${q(instanceKitLabel)}`; + (adminFacet && adminFacet !== governorAdminFacet) || + Fail`${q(logLabel)} instanceKit adminFacet should have been present and different from governorAdminFacet but was ${adminFacet}`; + const reason = harden(Error(`core-eval terminating ${label} governor`)); + await E(governorAdminFacet).upgradeContract(governorExecutorBundle, undefined); + console.log(`${q(logLabel)} terminated governor`); + }), + ); + const results = await doneP; + const problems = targets.flatMap(({ boardID, instanceKitLabel }, i) => { + if (results[i].status === 'fulfilled') return []; + return [[boardID, instanceKitLabel, results[i].reason]]; + }); + if (problems.length !== 0) { + console.error('governor termination(s) failed', problems); + Fail`governor termination(s) failed: ${problems}`; + } +}; +harden(upgradeGovernors); + +/* +export const getManifest = (_powers, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return { + manifest: { + [upgradeGovernors.name]: { + consume: { board: true, governedContractKits: true }, + }, + }, + // Provide `terminateGovernors` a second argument like + // `{ options: { targetSpecifiers } }`. + options: { targetSpecifiers }, + }; +}; +*/ + +const uG = 'upgradeGovernors'; +/** + * Return the manifest, installations, and options for upgrading Vaults. + * + * @param {object} utils + * @param {any} utils.restoreRef + * @param {any} vaultUpgradeOptions + */ +export const getManifest = async ( + { restoreRef }, + { bundleRef, targetSpecifiers }, +) => { + return { + manifest: { + [upgradeGovernors.name]: { + consume: { + board: uG, + zoe: uG, + governedContractKits: uG, + }, + }, + }, + installations: { governorExecutor: restoreRef(bundleRef) }, // do we need installations ??? + options: { governorExecutorBundle: bundleRef, targetSpecifiers }, + }; +}; + +/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +/* +export const defaultProposalBuilder = async (_utils, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return harden({ + sourceSpec: SELF, + getManifestCall: ['getManifest', targetSpecifiers], + }); +};*/ + +/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +/* +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + parseTargets(scriptArgs, makeUsageError); + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(upgradeGovernors.name, utils => + defaultProposalBuilder(utils, scriptArgs), + ); +}; +*/ +// import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return harden({ + sourceSpec: SELF, + getManifestCall: [ + getManifest.name, + { + bundleRef: publishRef( + install( + '@agoric/governance/bundles/contractGovernorExecutor.js', + ), + ), + targetSpecifiers, + }, + ], + }); +} + +/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +/* +export default async (homeP, endowments) => { + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval('upgrade-governor-instance', defaultProposalBuilder); +}; +*/ + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + parseTargets(scriptArgs, makeUsageError); + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(upgradeGovernors.name, utils => + defaultProposalBuilder(utils, scriptArgs), + ); +}; \ No newline at end of file diff --git a/packages/governance/src/contractGovernorExecutor.js b/packages/governance/src/contractGovernorExecutor.js new file mode 100644 index 00000000000..5ca9c73617a --- /dev/null +++ b/packages/governance/src/contractGovernorExecutor.js @@ -0,0 +1,26 @@ +import { E } from '@endo/eventual-send'; +import { makeTracer } from '@agoric/internal'; + +const trace = makeTracer('CGExec', false); + +/** @type {ContractMeta} */ +export const meta = { + upgradability: 'canUpgrade', +}; +harden(meta); + +/** + * Start an instance of a governor, governing a "governed" contract specified in terms. + * + * @param {ZCF<{}>} zcf + * @param {{}} _privateArgs + * @param {import('@agoric/vat-data').Baggage} baggage + */ +export const start = async (zcf, _privateArgs, baggage) => { + trace('start'); + const contractInstanceAdminFacet = baggage.get('creatorFacet'); + const terminationData = harden(Error(`termination of contract by executor governor`)); + await E(contractInstanceAdminFacet).terminateContract(terminationData); + zcf.shutdown(`self-termination of executor governor`); +}; +harden(start);