Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unbork Emerynet: Cleanup Auctioneers #10725 #10861

Open
wants to merge 9 commits into
base: dev-upgrade-18-emerynet-fix
Choose a base branch
from
28 changes: 28 additions & 0 deletions golang/cosmos/app/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,27 @@ func terminateGovernorCoreProposal(upgradeName string) (vm.CoreProposalStep, err
)
}

func terminateAuctioneersCoreProposal(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", // auctioneer v39, governor v38
"board0501277:auctioneer", // auctioneer v665, governor v664
}
siarhei-agoric marked this conversation as resolved.
Show resolved Hide resolved
default:
return nil, nil
}

return buildProposalStepWithArgs(
"@agoric/builders/scripts/vats/terminate-governed-instance.js",
// Request `defaultProposalBuilder(powers, targets)`.
"defaultProposalBuilder",
[]any{targets},
)
}

// func upgradeMintHolderCoreProposal(upgradeName string) (vm.CoreProposalStep, error) {
// variant := getVariantFromUpgradeName(upgradeName)

Expand Down Expand Up @@ -313,6 +334,13 @@ func upgrade18Handler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgr
} else if terminateOldGovernor != nil {
CoreProposalSteps = append(CoreProposalSteps, terminateOldGovernor)
}

terminateAuctioneers, err := terminateAuctioneersCoreProposal(targetUpgrade)
if err != nil {
return nil, err
} else if terminateAuctioneers != nil {
CoreProposalSteps = append(CoreProposalSteps, terminateAuctioneers)
}
}

app.upgradeDetails = &upgradeDetails{
Expand Down
69 changes: 69 additions & 0 deletions packages/boot/test/bootstrapTests/terminate-governed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
import type { TestFn } from 'ava';
import { makeSwingsetTestKit } from '../../tools/supports.js';

// A more minimal set would be better. We need governance, but not econ vats.
const PLATFORM_CONFIG = '@agoric/vm-config/decentral-main-vaults-config.json';

const makeDefaultTestContext = async t => {
const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, {
configSpecifier: PLATFORM_CONFIG,
});
const { runUtils } = swingsetTestKit;
const { EV } = runUtils;

// We need to poke at bootstrap vat and wait for results to allow
// SwingSet to finish its boot process before we start the test
const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe');

return { ...swingsetTestKit };
};

const test = anyTest as TestFn<
Awaited<ReturnType<typeof makeDefaultTestContext>>
>;

test.before(async t => {
t.context = await makeDefaultTestContext(t);
});

test.after.always(t => {
return t.context.shutdown && t.context.shutdown();
});

test(`Create a contract via core-eval and kill it via core-eval by boardID `, async t => {
const TEST_CONTRACT_LABEL = 'testContractLabel';
const { runUtils, buildProposal, evalProposal } = t.context;
const { EV } = runUtils;

// create a contract via core-eval
const testContractProposalArgs = [TEST_CONTRACT_LABEL];
const creatorProposalMaterials = buildProposal(
'@agoric/governance/test/swingsetTests/contractGovernor/add-governedContract.js',
testContractProposalArgs,
);
await evalProposal(creatorProposalMaterials);

const { boardID, publicFacet } =
await EV.vat('bootstrap').consumeItem(TEST_CONTRACT_LABEL);
console.log({ boardID });

// confirming the contract actually works
const num = await EV(publicFacet).getNum();
t.is(num, 602214090000000000000000n);

// killing via terminate-governed-instance
const targets = [`${boardID}:${TEST_CONTRACT_LABEL}`];
console.log({ targets });

const terminatorProposalMaterials = buildProposal(
'@agoric/builders/scripts/vats/terminate-governed-instance.js',
targets,
);
await evalProposal(terminatorProposalMaterials);

// confirm the contract is no longer there
await t.throwsAsync(() => EV(publicFacet).getNum(), {
message: 'vat terminated',
});
Comment on lines +65 to +68
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add something to prove that the governor has been terminated as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The governor's public facet has getElectorate(), for example.

});
147 changes: 147 additions & 0 deletions packages/builders/scripts/vats/terminate-governed-instance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* @file Terminate price-feed governor instances such as mainnet v110.
* Functions as both an off-chain builder and an on-chain core-eval.
*/

/// <reference types="@agoric/vats/src/core/types-ambient"/>

import { E } from '@endo/far';
Comment on lines +1 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @file Terminate price-feed governor instances such as mainnet v110.
* Functions as both an off-chain builder and an on-chain core-eval.
*/
/// <reference types="@agoric/vats/src/core/types-ambient"/>
import { E } from '@endo/far';
/**
* @file Source for a core-eval to terminate governed contract instances along
* with their governors by mapping a board ID to a key in the Bootstrap Powers
* governedContractKits collection.
* Also functions as an off-chain builder script for such core-evals:
* agoric run /path/to/$0 <$instanceHandleBoardID:$instanceKitLabel>...
*/
/// <reference types="@agoric/vats/src/core/types-ambient"/>
// dynamic import { makeHelpers } from '@agoric/deploy-script-support';
// dynamic import { getSpecifier } from '@agoric/internal/src/module-utils.js';
import { E } from '@endo/far';

And I think this file should move out of builders/scripts/vats for the same reason that add-governedContract.js moved to packages/governance/test/swingsetTests/contractGovernor/. Perhaps it should live in the cosmic-swingset package?


const SELF = '@agoric/builders/scripts/vats/terminate-governed-instance.js';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const SELF = '@agoric/builders/scripts/vats/terminate-governed-instance.js';

const USAGE = `Usage: agoric run /path/to/terminate-governed-instance.js \\
<$governorInstanceHandleBoardID:$instanceKitLabel>...`;
Comment on lines +11 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const USAGE = `Usage: agoric run /path/to/terminate-governed-instance.js \\
<$governorInstanceHandleBoardID:$instanceKitLabel>...`;
const USAGE = `Usage: agoric run /path/to/terminate-governed-instance.js \\
<$instanceHandleBoardID:$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 = /^(?<boardID>board[0-9]+):(?<instanceKitLabel>.+)$/;
/**
* @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);
}
Comment on lines +42 to +47
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent-reduction suggestion:

Suggested change
if (!m) {
badTargets.push(arg);
} else {
// @ts-expect-error cast
targets.push(m.groups);
}
if (!m) {
badTargets.push(arg);
continue;
}
// @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)`;
}
Comment on lines +49 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (badTargets.length !== 0) {
throw makeError`malformed target(s): ${badTargets}`;
} else if (targets.length === 0) {
throw makeError`no target(s)`;
}
if (badTargets.length) throw makeError`malformed target(s): ${badTargets}`;
if (!targets.length) throw makeError`no target(s)`;

return targets;
};

/**
* @param {BootstrapPowers} powers
* @param {{ options: { targetSpecifiers: string[] } }} config
*/
Comment on lines +57 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @param {BootstrapPowers} powers
* @param {{ options: { targetSpecifiers: string[] } }} config
*/
/**
* Terminate one or more governed contract instances and their governors.
*
* @param {BootstrapPowers} powers
* @param {object} config
* @param {object} config.options
* @param {string[]} config.options.targetSpecifiers An array of
* "$instanceHandleBoardID:$instanceKitLabel" strings corresponding with vats
* to terminate.
*/

export const terminateGoverned = async (
{ consume: { board, governedContractKits } },
{ options: { 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 instanceAdminFacet = await E(
instanceKit.governorCreatorFacet,
).getAdminFacet();

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}`;

console.log({ instanceAdminFacet });

await E(instanceAdminFacet).terminateContract(
harden(Error('Governed contract terminated by core-eval')),
);
console.log('Governed contract terminated by core-eval.');

const reason = harden(Error(`core-eval terminating ${label} governor`));
await E(governorAdminFacet).terminateContract(reason);
console.log(`${q(logLabel)} terminated governor`);
}),
);
Comment on lines +67 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The targeted logging in this function should probably be updated with a top-level

import { makeTracer } from '@agoric/internal';
Suggested change
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 instanceAdminFacet = await E(
instanceKit.governorCreatorFacet,
).getAdminFacet();
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}`;
console.log({ instanceAdminFacet });
await E(instanceAdminFacet).terminateContract(
harden(Error('Governed contract terminated by core-eval')),
);
console.log('Governed contract terminated by core-eval.');
const reason = harden(Error(`core-eval terminating ${label} governor`));
await E(governorAdminFacet).terminateContract(reason);
console.log(`${q(logLabel)} terminated governor`);
}),
);
const doneP = Promise.allSettled(
targets.map(async ({ boardID, instanceKitLabel }) => {
const targetData = [boardID, instanceKitLabel];
const logLabel = q(targetData);
const trace = makeTracer(`terminate-governed-instance ${logLabel}`, true);
const instanceHandle = await E(board).getValue(boardID);
const instanceKit = await E(governedContractKits).get(
// @ts-expect-error TS2345 Property '[tag]' is missing
instanceHandle,
);
trace('alleged governor contract instance kit', instanceKit);
// Validate the instance kit.
const { label, governorCreatorFacet, governorAdminFacet } = instanceKit;
label === instanceKitLabel ||
Fail`${logLabel} unexpected instanceKit label, got ${label} but wanted ${q(instanceKitLabel)}`;
// Terminate the governed contract.
const governedAdminFacet = await E(governorCreatorFacet).getAdminFacet();
trace({ governedAdminFacet });
const terminateInstanceMessage = `governed contract ${logLabel} terminated by terminate-governed-instance`;
await E(governedAdminFacet).terminateContract(
harden(Error(terminateInstanceMessage)),
);
trace('terminated governed instance');
// Then terminate the governor.
const terminateGovernorMessage = `governor ${logLabel} terminated by terminate-governed-instance`;
await E(governorAdminFacet).terminateContract(
harden(Error(terminateGovernorMessage)),
);
trace('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(terminateGoverned);

export const getManifest = (_powers, targetSpecifiers) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const getManifest = (_powers, targetSpecifiers) => {
/**
* Return a manifest for terminating one or more governed contract instances and
* their governors.
*
* @param {object} _utils
* @param {string[]} targetSpecifiers
*/
export const getManifest = (_powers, targetSpecifiers) => {

parseTargets(targetSpecifiers);
return {
manifest: {
[terminateGoverned.name]: {
consume: { board: true, governedContractKits: true },
},
},
// Provide `terminateGovernors` a second argument like
// `{ options: { targetSpecifiers } }`.
options: { 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],
});
Comment on lines +131 to +134
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return harden({
sourceSpec: SELF,
getManifestCall: ['getManifest', targetSpecifiers],
});
// Dynamic import to avoid inclusion in the proposal bundle.
const { getSpecifier } = await import('@agoric/internal/src/module-utils.js');
const SELF = await getSpecifier(import.meta.url);
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;
Comment on lines +141 to +142
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const dspModule = await import('@agoric/deploy-script-support');
const { makeHelpers } = dspModule;
// Dynamic import to avoid inclusion in the proposal bundle.
const { makeHelpers } = await import('@agoric/deploy-script-support');

const { writeCoreEval } = await makeHelpers(homeP, endowments);
await writeCoreEval(terminateGoverned.name, utils =>
defaultProposalBuilder(utils, scriptArgs),
);
};
2 changes: 1 addition & 1 deletion packages/governance/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@
"typeCoverage": {
"atLeast": 89.49
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is json happy with files that don't end with a linefeed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but git and editor software definitely prefer its presence.

Loading
Loading