diff --git a/a3p-integration/proposals/n:upgrade-next/initial.test.js b/a3p-integration/proposals/n:upgrade-next/initial.test.js index c68207a949b..a6fc28eeb05 100644 --- a/a3p-integration/proposals/n:upgrade-next/initial.test.js +++ b/a3p-integration/proposals/n:upgrade-next/initial.test.js @@ -11,6 +11,8 @@ const vats = { transfer: { incarnation: 1 }, walletFactory: { incarnation: 5 }, zoe: { incarnation: 3 }, + // Terminated in a future proposal. + '-ATOM-USD_price_feed-governor': { incarnation: 0 }, }; test(`vat details`, async t => { diff --git a/a3p-integration/proposals/p:upgrade-19/package.json b/a3p-integration/proposals/p:upgrade-19/package.json index 666230406a8..e222b2867c4 100644 --- a/a3p-integration/proposals/p:upgrade-19/package.json +++ b/a3p-integration/proposals/p:upgrade-19/package.json @@ -12,7 +12,8 @@ "vats/upgrade-agoricNames.js agoricNamesCoreEvals/upgradeAgoricNames", "testing/add-USD-OLIVES.js agoricNamesCoreEvals/addUsdOlives", "testing/publish-test-info.js agoricNamesCoreEvals/publishTestInfo", - "vats/upgrade-mintHolder.js upgrade-mintHolder A3P_INTEGRATION" + "vats/upgrade-mintHolder.js upgrade-mintHolder A3P_INTEGRATION", + "vats/terminate-governor-instance.js terminate-governor board02963:ATOM-USD_price_feed" ] }, "type": "module", diff --git a/a3p-integration/proposals/p:upgrade-19/terminateGovernor.test.js b/a3p-integration/proposals/p:upgrade-19/terminateGovernor.test.js new file mode 100644 index 00000000000..52e7a2d21f0 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/terminateGovernor.test.js @@ -0,0 +1,19 @@ +import test from 'ava'; +import '@endo/init/debug.js'; + +import { getDetailsMatchingVats } from './vatDetails.js'; + +test('verify governor termination', async t => { + const details = await getDetailsMatchingVats( + '-ATOM-USD_price_feed-governor', + true, + ); + t.log(details); + if (details.length === 0) { + // Cleanup already done. + t.pass(); + return; + } + t.is(details.length, 1); + t.true(details[0].terminated); +}); diff --git a/a3p-integration/proposals/p:upgrade-19/test.sh b/a3p-integration/proposals/p:upgrade-19/test.sh index 25cd9bde21f..ffd10530984 100644 --- a/a3p-integration/proposals/p:upgrade-19/test.sh +++ b/a3p-integration/proposals/p:upgrade-19/test.sh @@ -1,5 +1,6 @@ #!/bin/bash +yarn ava terminateGovernor.test.js yarn ava replaceFeeDistributor.test.js yarn ava upgradedBoard.test.js yarn ava mintHolder.test.js diff --git a/a3p-integration/proposals/p:upgrade-19/vatDetails.js b/a3p-integration/proposals/p:upgrade-19/vatDetails.js new file mode 100644 index 00000000000..9eeafdf5949 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/vatDetails.js @@ -0,0 +1,129 @@ +// Temporary fork of +// https://github.com/Agoric/agoric-3-proposals/blob/main/packages/synthetic-chain/src/lib/vat-status.js +// for deleted vat information. +// See https://github.com/Agoric/agoric-3-proposals/issues/208 +/* eslint-env node */ + +import dbOpenAmbient from 'better-sqlite3'; + +const HOME = process.env.HOME; + +/** @type {(val: T | undefined) => T} */ +export const NonNullish = val => { + if (!val) throw Error('required'); + return val; +}; + +/** + * @file look up vat incarnation from kernel DB + * @see {getIncarnation} + */ + +const swingstorePath = `${HOME}/.agoric/data/agoric/swingstore.sqlite`; + +/** + * SQL short-hand + * + * @param {import('better-sqlite3').Database} db + */ +export const dbTool = db => { + const prepare = (strings, ...params) => { + const dml = strings.join('?'); + return { stmt: db.prepare(dml), params }; + }; + const sql = (strings, ...args) => { + const { stmt, params } = prepare(strings, ...args); + return stmt.all(...params); + }; + sql.get = (strings, ...args) => { + const { stmt, params } = prepare(strings, ...args); + return stmt.get(...params); + }; + return sql; +}; + +/** + * @param {import('better-sqlite3').Database} db + */ +const makeSwingstore = db => { + const sql = dbTool(db); + + /** @param {string} key */ + // @ts-expect-error cast + const kvGet = key => sql.get`select * from kvStore where key = ${key}`.value; + /** @param {string} key */ + const kvGetJSON = key => JSON.parse(kvGet(key)); + + /** @param {string} vatID */ + const lookupVat = vatID => { + return Object.freeze({ + source: () => kvGetJSON(`${vatID}.source`), + options: () => kvGetJSON(`${vatID}.options`), + currentSpan: () => + sql.get`select * from transcriptSpans where isCurrent = 1 and vatID = ${vatID}`, + getTerminated: () => kvGetJSON('vats.terminated').includes(vatID), + }); + }; + + /** + * @param {string} vatName + * @param {boolean} [includeTerminated] + * @returns {string[]} + */ + const findDynamicVatIDs = (vatName, includeTerminated = false) => { + /** @type {string[]} */ + const terminatedVatIDs = kvGetJSON('vats.terminated'); + /** @type {string[]} */ + const allDynamicIDs = kvGetJSON('vat.dynamicIDs'); + const dynamicIDs = includeTerminated + ? allDynamicIDs + : allDynamicIDs.filter(vatID => !terminatedVatIDs.includes(vatID)); + const matchingIDs = dynamicIDs.filter(vatID => + lookupVat(vatID).options().name.includes(vatName), + ); + return matchingIDs; + }; + + return Object.freeze({ + /** + * @param {string} vatName + * @param {boolean} [includeTerminated] + * @returns {string} + */ + findVat: (vatName, includeTerminated = false) => { + /** @type {string[]} */ + const matchingIDs = findDynamicVatIDs(vatName, includeTerminated); + if (matchingIDs.length === 0) throw Error(`vat not found: ${vatName}`); + return matchingIDs[0]; + }, + findVats: findDynamicVatIDs, + lookupVat, + }); +}; + +/** + * @param {string} vatName + * @param {boolean} [includeTerminated] + */ +export const getDetailsMatchingVats = async ( + vatName, + includeTerminated = false, +) => { + const kStore = makeSwingstore( + dbOpenAmbient(swingstorePath, { readonly: true }), + ); + + const vatIDs = kStore.findVats(vatName, includeTerminated); + const infos = []; + for (const vatID of vatIDs) { + const vatInfo = kStore.lookupVat(vatID); + const name = vatInfo.options().name; + const source = vatInfo.source(); + const terminated = includeTerminated && vatInfo.getTerminated(); + // @ts-expect-error cast + const { incarnation } = vatInfo.currentSpan(); + infos.push({ vatName: name, vatID, incarnation, terminated, ...source }); + } + + return infos; +};