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

Fix upgrade behaviors #9526

Merged
merged 5 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions a3p-integration/proposals/a:upgrade-next/agd-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,30 +48,63 @@ export const checkForOracle = async (t, name) => {
t.truthy(instance);
};

export const addOraclesForBrand = async (brandIn, oraclesByBrand) => {
export const registerOraclesForBrand = async (brandIn, oraclesByBrand) => {
await null;
const promiseArray = [];

const oraclesWithID = [];
// newOfferId() waits 1 second
const offerIdBase = await newOfferId();
for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) {
const oracleAddress = ORACLE_ADDRESSES[i];
const offerId = `${offerIdBase}.${i}`;
oraclesWithID.push({ address: oracleAddress, offerId });

const oraclesWithID = oraclesByBrand.get(brandIn);
for (const oracle of oraclesWithID) {
const { address, offerId } = oracle;
promiseArray.push(
executeOffer(
oracleAddress,
address,
agops.oracle('accept', '--offerId', offerId, `--pair ${brandIn}.USD`),
),
);
}
oraclesByBrand.set(brandIn, oraclesWithID);

return Promise.all(promiseArray);
};

/**
* Generate a consistent map of oracleIDs for a brand that can be used to
* register oracles or to push prices. The baseID changes each time new
* invitations are sent/accepted, and need to be maintained as constants in
* scripts that use the oracles. Each oracleAddress and brand needs a unique
* offerId, so we create recoverable IDs using the brandName and oracle id,
* mixed with the upgrade at which the invitations were accepted.
*
* @param {string} baseId
* @param {string} brandName
*/
const addOraclesForBrand = (baseId, brandName) => {
const oraclesWithID = [];
for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) {
const oracleAddress = ORACLE_ADDRESSES[i];
const offerId = `${brandName}.${baseId}.${i}`;
oraclesWithID.push({ address: oracleAddress, offerId });
}
return oraclesWithID;
};

/**
* Generate a consistent map of oracleIDs and brands that can be used to
* register oracles or to push prices. The baseID changes each time new
* invitations are sent/accepted, and need to be maintained as constants in
* scripts that use these records to push prices.
*
* @param {string} baseId
* @param {string[]} brandNames
*/
export const generateOracleMap = (baseId, brandNames) => {
const oraclesByBrand = new Map();
for (const brandName of brandNames) {
const oraclesWithID = addOraclesForBrand(baseId, brandName);
oraclesByBrand.set(brandName, oraclesWithID);
}
return oraclesByBrand;
};

export const pushPrices = (price, brandIn, oraclesByBrand) => {
const promiseArray = [];

Expand Down
2 changes: 1 addition & 1 deletion a3p-integration/proposals/a:upgrade-next/initial.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const vats = {
ibc: { incarnation: 0 },
localchain: { incarnation: 0 },
walletFactory: { incarnation: 3 },
zoe: { incarnation: 1 },
zoe: { incarnation: 2 },
};

test(`vat details`, async t => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const SUBMISSION_DIR = 'probe-submission';

test('upgrade Zoe to verify ZcfBundleCap endures', async t => {
await null;
t.assert((await getIncarnation('zoe')) === 1, 'zoe incarnation must be one');
t.assert((await getIncarnation('zoe')) === 2, 'zoe incarnation must be one');

// Before the test, the Wallet Factory should be using the legacy ZCF
const detailsBefore = await getVatDetails('walletFactory');
Expand All @@ -24,4 +24,13 @@ test('upgrade Zoe to verify ZcfBundleCap endures', async t => {
detailsBefore.incarnation + 2,
'wf incarnation must increase by 2',
);

// The test restarts the WalletFactory, so it'll use the recently assigned
// ZCF bundle. It then restarts Zoe, so it'll revert to whichever ZCF bundle
// made it to persistent store. We then restart the Wallet Factory and see if
// it's gone back to the ZCF that Zoe initially knew about. If we could get the
// ZCF bundleID here from the probe, we'd explicitly check for that. Instead,
// we have to be content that it did indeed use the newly assigned bundle in
// manual tests.
t.not(detailsAfter.bundleID, detailsBefore.bundleID);
});
30 changes: 30 additions & 0 deletions a3p-integration/proposals/a:upgrade-next/upgradeVaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env node

import assert from 'node:assert/strict';
import {
generateOracleMap,
getPriceQuote,
pushPrices,
registerOraclesForBrand,
} from './agd-tools.js';

const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM'];
const oraclesByBrand = generateOracleMap('u16', BRANDNAMES);

// There are no old prices for the other currencies.
const atomOutPre = await getPriceQuote('ATOM');
assert.equal(atomOutPre, '+12010000');

console.log('adding oracle for each brand');
await registerOraclesForBrand('ATOM', oraclesByBrand);
await registerOraclesForBrand('stATOM', oraclesByBrand);
await registerOraclesForBrand('stTIA', oraclesByBrand);
await registerOraclesForBrand('stOSMO', oraclesByBrand);
await registerOraclesForBrand('stkATOM', oraclesByBrand);

console.log('pushing new prices');
await pushPrices(11.2, 'ATOM', oraclesByBrand);
await pushPrices(11.3, 'stTIA', oraclesByBrand);
await pushPrices(11.4, 'stATOM', oraclesByBrand);
await pushPrices(11.5, 'stOSMO', oraclesByBrand);
await pushPrices(11.6, 'stkATOM', oraclesByBrand);
33 changes: 8 additions & 25 deletions a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
} from '@agoric/synthetic-chain';

import {
addOraclesForBrand,
bankSend,
BID_OFFER_ID,
checkForOracle,
createBid,
generateOracleMap,
getLiveOffers,
getPriceQuote,
pushPrices,
Expand All @@ -40,27 +40,10 @@ const checkPriceFeedVatsUpdated = async t => {
]);
};

const oraclesByBrand = new Map();

const tryPushPrices = async t => {
// There are no old prices for the other currencies.
const atomOutPre = await getPriceQuote('ATOM');
t.is(atomOutPre, '+12010000');

t.log('adding oracle for each brand');
await addOraclesForBrand('ATOM', oraclesByBrand);
await addOraclesForBrand('stATOM', oraclesByBrand);
await addOraclesForBrand('stTIA', oraclesByBrand);
await addOraclesForBrand('stOSMO', oraclesByBrand);
await addOraclesForBrand('stkATOM', oraclesByBrand);

t.log('pushing new prices');
await pushPrices(11.2, 'ATOM', oraclesByBrand);
await pushPrices(11.3, 'stTIA', oraclesByBrand);
await pushPrices(11.4, 'stATOM', oraclesByBrand);
await pushPrices(11.5, 'stOSMO', oraclesByBrand);
await pushPrices(11.6, 'stkATOM', oraclesByBrand);
const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM'];
const oraclesByBrand = generateOracleMap('u16', BRANDNAMES);

const checkNewQuotes = async t => {
t.log('awaiting new quotes');
const atomOut = await getPriceQuote('ATOM');
t.is(atomOut, '+11200000');
Expand Down Expand Up @@ -106,7 +89,7 @@ const triggerAuction = async t => {
t.is(atomOut, '+5200000');
};

const makeNewAuctionVat = async t => {
const checkAuctionVat = async t => {
const details = await getDetailsMatchingVats('auctioneer');
// This query matches both the auction and its governor, so double the count
t.true(Object.keys(details).length > 2);
Expand All @@ -117,8 +100,8 @@ test('liquidation post upgrade', async t => {
t.log('starting upgrade vaults test');
await checkPriceFeedVatsUpdated(t);

t.log('starting pushPrices');
await tryPushPrices(t);
t.log('check new price quotes');
await checkNewQuotes(t);

t.log('create a new Bid for the auction');
await createNewBid(t);
Expand All @@ -130,5 +113,5 @@ test('liquidation post upgrade', async t => {
await triggerAuction(t);

t.log('make new auction');
await makeNewAuctionVat(t);
await checkAuctionVat(t);
});
2 changes: 2 additions & 0 deletions a3p-integration/proposals/a:upgrade-next/use.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
# actions are executed in the upgraded chain software and the effects are
# persisted in the generated image for the upgrade, so they can be used in
# later steps, such as the "test" step, or further proposal layers.

./upgradeVaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const vats = {
orchestration: { incarnation: 0 },
transfer: { incarnation: 0 },
walletFactory: { incarnation: 3 },
zoe: { incarnation: 1 },
zoe: { incarnation: 2 },
};

test(`vat details`, async t => {
Expand Down
19 changes: 19 additions & 0 deletions a3p-integration/proposals/z:acceptance/create-kread-item-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -euo pipefail

source /usr/src/upgrade-test-scripts/env_setup.sh

OFFER=$(mktemp -t agops.XXX)
agops vaults open --wantMinted 6.00 --giveCollateral 9.0 >| "$OFFER"
agoric wallet send --offer "$OFFER" --from $GOV1ADDR --keyring-backend="test"

govamount="200000000ubld"
provisionSmartWallet $GOV1ADDR $govamount

KREAD_ITEM_OFFER=$(mktemp -t kreadItem.XXX)
node ./generate-kread-item-request.mjs > $KREAD_ITEM_OFFER
agops perf satisfaction --from $GOV1ADDR --executeOffer $KREAD_ITEM_OFFER --keyring-backend=test

agd query vstorage data published.wallet.$GOV1ADDR.current -o json >&gov1.out
name=$(jq '.value | fromjson | .values[2] | fromjson | .body[1:] | fromjson | .purses[1].balance.value.payload[0][0].name ' gov1.out)
test_val $name \"ephemeral_Ace\" "found KREAd character"
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* global process */
import assert from 'assert';
import { execFile } from 'child_process';

const ISTunit = 1_000_000n; // aka displayInfo: { decimalPlaces: 6 }

const id = `KREAd-test-${Date.now()}`;

// poor-man's zx
const $ = cmd => {
const [file, ...args] = cmd.split(' ');

return new Promise((resolve, reject) => {
execFile(file, args, { encoding: 'utf8' }, (err, out) => {
if (err) return reject(err);
resolve(out);
});
});
};

const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]);

const fromSmallCapsEntries = txt => {
const { body, slots } = JSON.parse(txt);
const theEntries = zip(JSON.parse(body.slice(1)), slots).map(
([[name, ref], boardID]) => {
const iface = ref.replace(/^\$\d+\./, '');
return [name, { iface, boardID }];
},
);
return Object.fromEntries(theEntries);
};

const brand = fromSmallCapsEntries(
await $('agoric follow -lF :published.agoricNames.brand -o text'),
);
assert(brand.IST);

const slots = []; // XXX global mutable state

const smallCaps = {
Nat: n => `+${n}`,
// XXX mutates obj
ref: obj => {
if (obj.ix) return obj.ix;
const ix = slots.length;
slots.push(obj.boardID);
obj.ix = `$${ix}.Alleged: ${obj.iface}`;
return obj.ix;
},
};

const body = {
method: 'executeOffer',
offer: {
id,
invitationSpec: {
source: 'agoricContract',
instancePath: ['kread'],
callPipe: [['makeMintCharacterInvitation', []]],
},
offerArgs: { name: 'ephemeral_Ace' },
proposal: {
give: {
Price: {
brand: smallCaps.ref(brand.IST),
value: smallCaps.Nat(5n * ISTunit) }
},
},
},
};

const capData = { body: `#${JSON.stringify(body)}`, slots };
const action = JSON.stringify(capData);

console.log(action);
2 changes: 2 additions & 0 deletions a3p-integration/proposals/z:acceptance/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ yarn ava initial.test.js
# test more, in ways that change system state
GLOBIGNORE=initial.test.js
yarn ava ./*.test.js

./create-kread-item-test.sh
6 changes: 4 additions & 2 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -934,8 +934,10 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte
// Each CoreProposalStep runs sequentially, and can be constructed from
// one or more modules executing in parallel within the step.
CoreProposalSteps = []vm.CoreProposalStep{
// Upgrade ZCF only
vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/upgrade-zcf.js"),
// Upgrade Zoe + ZCF
vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-zoe.js"),
// Revive KREAd characters
vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/revive-kread.js"),
Copy link
Member

Choose a reason for hiding this comment

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

I'm not privy to the plan so I'm just assuming this is supposed to be done

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, Zoe needs to be updated, and that forces us to repair KREAd


// upgrade the provisioning vat
vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-provisioning.js"),
Expand Down
13 changes: 13 additions & 0 deletions packages/builders/scripts/vats/revive-kread.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
export const defaultProposalBuilder = async () =>
harden({
sourceSpec: '@agoric/vats/src/proposals/kread-proposal.js',
getManifestCall: ['getManifestForKread'],
});

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('revive-kread', defaultProposalBuilder);
};
24 changes: 24 additions & 0 deletions packages/vats/src/proposals/kread-proposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { E } from '@endo/far';

/**
* @param {BootstrapPowers & {
* consume: { kreadKit: any };
* }} powers
*/
export const repairKread = async ({ consume: { kreadKit: kreadKitP } }) => {
console.log('repairSubscribers');

const kreadKit = await kreadKitP;
const creatorFacet = kreadKit.creatorFacet;
Copy link
Member

Choose a reason for hiding this comment

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

Consider destrucuring. Could be directly from the promise as 'kreadKit' is unused

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a direct checkout of the release-mainnet1B file

console.log(`KREAd creatorFacet`, creatorFacet);
await E(creatorFacet).reviveMarketExitSubscribers();
console.log('KREAd subscribers were revived!');
};

export const getManifestForKread = _powers => ({
manifest: {
[repairKread.name]: {
consume: { kreadKit: true },
},
},
});
4 changes: 1 addition & 3 deletions packages/vats/src/proposals/probeZcfBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ export const probeZcfBundleCap = async (
await E(adminNode).upgrade(zoeBundleCap, {});

// STEP 4: restart WF ////////////////////////
// Need to use `upgradeContract` instead of `restartContract`
// See https://github.com/Agoric/agoric-sdk/issues/9249
await E(walletAdminFacet).upgradeContract(walletRef.bundleID, privateArgs);
await E(walletAdminFacet).restartContract(privateArgs);

// ////// See which zcf bundle was used //////////
};
Expand Down
Loading