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

chore(vat-upgrade): upgrade agoricNames, test old values are preserved #10616

Merged
merged 3 commits into from
Dec 9, 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
3 changes: 3 additions & 0 deletions a3p-integration/proposals/p:upgrade-19/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
replaceFeeDistributor/
testUpgradedBoard/
addUsdLemons/
addUsdOlives/
upgradeProvisionPool/
upgradeAgoricNames/
publishTestInfo/
236 changes: 236 additions & 0 deletions a3p-integration/proposals/p:upgrade-19/agoricNames.test.js
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could you please take a look at this file and let me know the test coverage (including the skipped one) here makes sense and is enough? @Chris-Hibbert

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this only tests reading via vstorage. I'd like to ensure that objects (issuers, brands, etc.) retrieved from it after upgrade are still valid unbroken objects. I presume this can be done from inside a proposal.

Is there anything here that tests onUpdate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this only tests reading via vstorage. I'd like to ensure that objects (issuers, brands, etc.) retrieved from it after upgrade are still valid unbroken objects. I presume this can be done from inside a proposal.

Yeah we check the vstorage but the way nameHub works is that when a hub is written new data, ALL of its entries are published to vstorage. See;

const updated = (updateCallback, hub, _newValue = undefined) => {
if (!updateCallback) {
return;
}
// wait for values to settle before writing
return E.when(deeplyFulfilledObject(hub.entries()), settledEntries =>
E(updateCallback).write(settledEntries),
);
};

So what we do is;

  • we read the vstorage before writing new data to agoricNames' children (brand, issuer, instance...)
  • write data to agoricNames via a core-eval
  • expect that the child hubs of agoricNames publish ALL their entries to vstorage along with the new data
  • read vstorage again and make sure the agoricNames read from the vstorage BEFORE the core-eval is the same as the one read AFTER the core-eval (with the addition of newly written data of course).

...still valid unbroken objects

My plan for making sure the objects are unbroken is to go over the contracts that use agoricNames and make sure they still function as expected. For example, in order to open a vault there needs to be a valid instance object in published.agoricNames.instance for VaultFactory.

Is there anything here that tests onUpdate?

Test named check we can add new chains appends a new chain to published.agoricNames.chain and published.agoricNames.chainConnection. Both of these paths have their onUpdate callback registered here:

await E(nameAdmin).onUpdate(
// XXX will live on the heap in the bootstrap vat. When we upgrade or kill
// that this handler will sever and vat-agoricNames will need to be upgraded
// to allow changing the handler, or to use pubsub mechanics instead.
Far('chain info writer', {
write(entries) {
// chainInfo has no cap data but we need to marshal bigints
const marshalData = makeMarshal(_val => Fail`data only`);
for (const [chainName, info] of entries) {
const value = JSON.stringify(marshalData.toCapData(info));
if (prev[chainName] === value) {
continue;
}
const chainNode = E(chainNamesNode).makeChildNode(chainName);
prev[chainName] = value;
void E(chainNode)
.setValue(value)
.catch(() => delete prev[chainName]);
}
},
}),
);

The fact that we are able to append and observe new chain info after an upgrade shows that onUpdate callback above works.

Copy link
Contributor

Choose a reason for hiding this comment

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

in order to open a vault there needs to be a valid instance object in published.agoricNames.instance for VaultFactory.

sounds good.

The fact that we are able to append and observe new chain info after an upgrade shows that onUpdate callback above works.

Great!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Looks like 2195ace disables init-chain-info.js. So made this commit 47c9309 to test ephemeral onUpdate calls.

Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/* eslint-env node */

/**
* @file The goal of this file is to test different aspects of agoricNames to make sure
* everything works after an upgrade. Here's the test plan;
* 1. publish a new node called 'testInfo' under agoricNames
* CONTEXT: onUpdate callback of testInfo nameAdmin is registered in a core-eval. Which means it is
* both ephemeral and lives in bootstrap vat. We create a scenario like this to make sure any ephemeral
* onUpdate keeps working after an agoricNames upgrade.
* 2. upgrade agoricNames
* 3. send a core-eval that writes into children of agoricNames (brand, issuer, instance...)
* 3b. expect a child nameHub of agoricNames will publish ALL its entries when a new item is written to it
* 3c. check the values in the vstorage match before and after the upgrade
* 3d. also check that new items are in the vstorage as well
* 4. append new chain
* CONTEXT: there are two new children introduced to agoricNames by orchestration and their
* onUpdate callback isn't durable. So we must check that if we write a new chain info to those child
* nameHubs, we should observe the new value in vstorage.
* 4b. send a core-eval that writes new chain info to published.agoricNames.chain and published.agoricNames.chainConnection
* 4c. wait until the expected data observed in vstorage
*
*
* TESTING CODE THAT HOLDS ONTO 'agoricNames': smartWallet is one of the vats that depend on agoricNames to work properly the most.
* smartWallet uses agoricNames to;
* - create purses for known brands
* - looks for the brand in agoricNames.brand
* - creates a purse for the brand using the issuer in agoricNames.issuer
* - create invitations for a from the publicFacet of a given instance (agoricNames.instance)
*
* So the fact that a user can complete an offer successfully means;
* - smartWallet can find the instance on agoricNames.instance (for invitationSource = 'agoricContract')
* - smartWallet can find, if not present create, a purse for known brand, agoricNames.brand
* and agoricNames.issuer returned correct values
*
*
* 5. add a new PSM and swap against it
* 5b. adding the new PSM requires introducing a new asset to the chain and writing
* the PSM instance to agoricNames.instance
* 5c. being able to deposit the new asset to a user means that smartWallet created a purse
* for the new brand
* 5d. being able to send the offer to the PSM instance means smartWallet can find the instance
* in agoricNames.instance
*
* 6. we want to make sure objects that were already in agoricNames works as well, so open a vault
* in an existing collateralManager
* 6a. fund GOV1 with ATOM
* 6b. open a vault
* 6c. check the vault is opened successfully
*
*/

import '@endo/init';
import test from 'ava';
import {
agoric,
ATOM_DENOM,
evalBundles,
getIncarnation,
GOV1ADDR,
openVault,
} from '@agoric/synthetic-chain';
import { makeVstorageKit, retryUntilCondition } from '@agoric/client-utils';
import {
bankSend,
extractBalance,
psmSwap,
tryISTBalances,
} from './test-lib/psm-lib.js';
import { getBalances, listVaults } from './test-lib/utils.js';
import { walletUtils } from './test-lib/index.js';

const AGORIC_NAMES_UPGRADE_DIR = 'agoricNamesCoreEvals/upgradeAgoricNames';
const WRITE_AGORIC_NAMES_DIR = 'agoricNamesCoreEvals/writeToAgoricNames';
const ADD_USD_OLIVES_DIR = 'agoricNamesCoreEvals/addUsdOlives';
const DEPOSIT_USD_OLIVES_DIR = 'agoricNamesCoreEvals/depositUsdOlives';
const PUBLISH_TEST_INFO_DIR = 'agoricNamesCoreEvals/publishTestInfo';
const WRITE_TEST_INFO_DIR = 'agoricNamesCoreEvals/writeToTestInfo';

const makeWaitUntilKeyFound = (keyFinder, vstorage) => (path, targetKey) =>
retryUntilCondition(
() => vstorage.keys(path),
keys => keyFinder(keys, targetKey),
'Key not found.',
{ maxRetries: 5, retryIntervalMs: 2000, log: console.log, setTimeout },
);

test.before(async t => {
const vstorageKit = await makeVstorageKit(
{ fetch },
{ rpcAddrs: ['http://localhost:26657'], chainName: 'agoriclocal' },
);

t.context = {
vstorageKit,
};
});

test.serial('publish test info', async t => {
// @ts-expect-error casting
const { vstorageKit } = t.context;

const waitUntilKeyFound = makeWaitUntilKeyFound(
(keys, targetKey) => keys.includes(targetKey),
vstorageKit.vstorage,
);

await evalBundles(PUBLISH_TEST_INFO_DIR);
await waitUntilKeyFound('published.agoricNames', 'testInfo');

const testInfo = await vstorageKit.readLatestHead(
'published.agoricNames.testInfo',
);
t.deepEqual(Object.fromEntries(testInfo), {
agoric: {
isAwesome: 'yes',
tech: ['HardenedJs', 'Orchestration', 'Async_Execution'],
},
});
});

test.serial('upgrade agoricNames', async t => {
await evalBundles(AGORIC_NAMES_UPGRADE_DIR);

const incarnation = await getIncarnation('agoricNames');
t.is(incarnation, 1, 'incorrect incarnation');
});

test.serial('check all existing values are preserved', async t => {
// @ts-expect-error casting
const { vstorageKit } = t.context;
const agoricNamesChildren = [
'brand',
'installation',
'instance',
'issuer',
'oracleBrand',
'vbankAsset',
];

const getAgoricNames = () =>
Promise.all(
agoricNamesChildren.map(async child => {
const content = await vstorageKit.readLatestHead(
`published.agoricNames.${child}`,
);
return [child, Object.fromEntries(content)];
}),
).then(rawAgoricNames => Object.fromEntries(rawAgoricNames));

const agoricNamesBefore = await getAgoricNames();
console.log('AGORIC_NAMES_BEFORE', agoricNamesBefore);

await evalBundles(WRITE_AGORIC_NAMES_DIR);

const agoricNamesAfter = await getAgoricNames();
t.like(agoricNamesAfter, agoricNamesBefore);

agoricNamesChildren.forEach(child =>

Check warning on line 158 in a3p-integration/proposals/p:upgrade-19/agoricNames.test.js

View workflow job for this annotation

GitHub Actions / lint-rest

Prefer for...of instead of Array.forEach
assert(
agoricNamesAfter[child][`test${child}`],
'we should be able to add new value',
),
);
});

test.serial('check testInfo still works', async t => {
// @ts-expect-error casting
const { vstorageKit } = t.context;
await evalBundles(WRITE_TEST_INFO_DIR);

const testInfo = await vstorageKit.readLatestHead(
'published.agoricNames.testInfo',
);
t.deepEqual(Object.fromEntries(testInfo), {
agoric: {
isAwesome: 'yes',
tech: ['HardenedJs', 'Orchestration', 'Async_Execution'],
},
ethereum: {
isAwesome: 'yes',
tech: ['Solidity', 'EVM'],
},
});
});

test.serial('check contracts depend on agoricNames are not broken', async t => {
await evalBundles(ADD_USD_OLIVES_DIR);
await evalBundles(DEPOSIT_USD_OLIVES_DIR);

const psmSwapIo = {
now: Date.now,
follow: agoric.follow,
setTimeout,
log: console.log,
};

const balancesBefore = await getBalances([GOV1ADDR]);

await psmSwap(
GOV1ADDR,
['swap', '--pair', 'IST.USD_OLIVES', '--wantMinted', 1],
psmSwapIo,
);

const balancesAfter = await getBalances([GOV1ADDR]);
await tryISTBalances(
t,
extractBalance(balancesAfter, 'uist'),
extractBalance(balancesBefore, 'uist') + 1000000, // in uist
);
});

test.serial('open a vault', async t => {
await bankSend(GOV1ADDR, `200000000000000000${ATOM_DENOM}`);
const istBalanceBefore = await getBalances([GOV1ADDR]);
const activeVaultsBefore = await listVaults(GOV1ADDR, walletUtils);

const mint = '5.0';
const collateral = '10.0';
await openVault(GOV1ADDR, mint, collateral);

const istBalanceAfter = await getBalances([GOV1ADDR]);
const activeVaultsAfter = await listVaults(GOV1ADDR, walletUtils);

await tryISTBalances(
t,
extractBalance(istBalanceAfter, 'uist'),
extractBalance(istBalanceBefore, 'uist') + 5000000,
);

t.is(
activeVaultsAfter.length,
activeVaultsBefore.length + 1,
`The number of active vaults should increase after opening a new vault.`,
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"consume": {
"contractKits": true,
"namesByAddressAdmin": true,
"agoricNames": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @ts-nocheck
/* eslint-disable no-undef */
const GOV_ONE_ADDR = 'agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q';

const depositUsdOlives = async powers => {
const {
consume: {
contractKits: contractKitsP,
namesByAddressAdmin: namesByAddressAdminP,
agoricNames,
},
} = powers;

const namesByAddressAdmin = await namesByAddressAdminP;

const getDepositFacet = async address => {
const hub = E(E(namesByAddressAdmin).lookupAdmin(address)).readonly();
return E(hub).lookup('depositFacet');
};

const [contractKits, usdOlivesIssuer, usdOlivesBrand, ppDepositFacet] =
await Promise.all([
contractKitsP,
E(agoricNames).lookup('issuer', 'USD_OLIVES'),
E(agoricNames).lookup('brand', 'USD_OLIVES'),
getDepositFacet(GOV_ONE_ADDR),
]);

console.log('[CONTRACT_KITS]', contractKits);
console.log('[ISSUER]', usdOlivesIssuer);

let usdOlivesMint;
for (const { publicFacet, creatorFacet: mint } of contractKits.values()) {
if (publicFacet === usdOlivesIssuer) {
usdOlivesMint = mint;
console.log('BINGO', mint);
break;
}
}

console.log('Minting USD_OLIVES');
const helloPayment = await E(usdOlivesMint).mintPayment(
harden({ brand: usdOlivesBrand, value: 1_000_000n }),
);

console.log('Funding provision pool...');
await E(ppDepositFacet).receive(helloPayment);

console.log('Done.');
};

depositUsdOlives;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"consume": {
"agoricNamesAdmin": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-nocheck
/* eslint-disable no-undef */
const writeToAgoricNames = async powers => {
const {
consume: { agoricNamesAdmin },
} = powers;

console.log('writing to agoricNames...');
const agoricNamesChildren = [
'brand',
'installation',
'instance',
'issuer',
'oracleBrand',
'vbankAsset',
];

await Promise.all(
agoricNamesChildren.map(async (child, index) =>
E(E(agoricNamesAdmin).lookupAdmin(child)).update(
`test${child}`,
Far(`test${child}`, { getBoardId: () => `board${index}` }),
),
),
);

console.log('DONE');
};

writeToAgoricNames;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"consume": {
"agoricNamesAdmin": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @ts-nocheck
/* eslint-disable no-undef */
const writeToTestInfo = async powers => {
const {
consume: { agoricNamesAdmin },
} = powers;

console.log('writing to testInfo...');

E(E(agoricNamesAdmin).lookupAdmin('testInfo')).update('ethereum', {
isAwesome: 'yes',
tech: ['Solidity', 'EVM'],
});

console.log('DONE');
};

writeToTestInfo;
6 changes: 5 additions & 1 deletion a3p-integration/proposals/p:upgrade-19/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"vats/upgrade-provisionPool.js upgradeProvisionPool",
"vats/upgrade-paRegistry.js",
"vats/upgrade-board.js",
"testing/test-upgraded-board.js testUpgradedBoard"
"testing/test-upgraded-board.js testUpgradedBoard",
"vats/upgrade-agoricNames.js agoricNamesCoreEvals/upgradeAgoricNames",
"testing/add-USD-OLIVES.js agoricNamesCoreEvals/addUsdOlives",
"testing/publish-test-info.js agoricNamesCoreEvals/publishTestInfo"
]
},
"type": "module",
Expand All @@ -21,6 +24,7 @@
"@endo/errors": "1.2.7",
"@endo/init": "^1.1.5",
"@endo/marshal": "^1.5.4",
"agoric": "dev",
"ava": "^5.3.1",
"better-sqlite3": "^9.6.0",
"execa": "9.1.0"
Expand Down
Loading
Loading