Skip to content

Commit

Permalink
Merge pull request #7096 from Agoric/6878-walletFactory-upgrade
Browse files Browse the repository at this point in the history
6878 wallet factory upgrade
  • Loading branch information
mergify[bot] authored Mar 4, 2023
2 parents 0a68f77 + ca30e05 commit 6af1746
Show file tree
Hide file tree
Showing 20 changed files with 492 additions and 52 deletions.
2 changes: 1 addition & 1 deletion packages/SwingSet/src/types-external.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export {};
* @typedef { { enableDisavow?: boolean } } HasEnableDisavow
* @typedef { DynamicVatOptions & HasEnableDisavow } StaticVatOptions
*
* @typedef { { vatParameters?: object, upgradeMessage: string } } VatUpgradeOptions
* @typedef { { vatParameters?: object, upgradeMessage?: string } } VatUpgradeOptions
* @typedef { { incarnationNumber: number } } VatUpgradeResults
*
* @callback ShutdownWithFailure
Expand Down
2 changes: 1 addition & 1 deletion packages/inter-protocol/test/smartWallet/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
`${dirname}/../../../smart-wallet/src/walletFactory.js`,
'walletFactory',
);
/** @type {Promise<Installation<import('@agoric/smart-wallet/src/walletFactory.js').start>>} */
/** @type {Promise<Installation<import('@agoric/smart-wallet/src/walletFactory.js').prepare>>} */
const installation = E(zoe).install(bundle);
// #endregion

Expand Down
1 change: 1 addition & 0 deletions packages/smart-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"ava": "^5.1.0"
},
"dependencies": {
"@agoric/assert": "^0.5.1",
"@agoric/casting": "^0.3.2",
"@agoric/ertp": "^0.15.3",
"@agoric/internal": "^0.2.1",
Expand Down
40 changes: 21 additions & 19 deletions packages/smart-wallet/src/walletFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { WalletName } from '@agoric/internal';
import { observeIteration } from '@agoric/notifier';
import { M, makeExo, makeScalarMapStore, mustMatch } from '@agoric/store';
import { makeAtomicProvider } from '@agoric/store/src/stores/store-utils.js';
import { makeScalarBigMapStore } from '@agoric/vat-data';
import { prepareExo, provideDurableMapStore } from '@agoric/vat-data';
import { makeMyAddressNameAdminKit } from '@agoric/vats/src/core/basic-behaviors.js';
import { provideAll } from '@agoric/zoe/src/contractSupport/durability.js';
import { E } from '@endo/far';
import { prepareSmartWallet } from './smartWallet.js';
import { shape } from './typeGuards.js';
Expand Down Expand Up @@ -58,7 +59,7 @@ export const publishDepositFacet = async (
/**
* @param {AssetPublisher} assetPublisher
*/
const makeAssetRegistry = assetPublisher => {
export const makeAssetRegistry = assetPublisher => {
/**
* @typedef {{
* brand: Brand,
Expand Down Expand Up @@ -126,14 +127,13 @@ const makeAssetRegistry = assetPublisher => {
* }} privateArgs
* @param {import('@agoric/vat-data').Baggage} baggage
*/
export const start = async (zcf, privateArgs, baggage) => {
export const prepare = async (zcf, privateArgs, baggage) => {
const { agoricNames, board, assetPublisher } = zcf.getTerms();

const zoe = zcf.getZoeService();
const { storageNode, walletBridgeManager } = privateArgs;

/** @type {MapStore<string, import('./smartWallet').SmartWallet>} */
const walletsByAddress = makeScalarBigMapStore('walletsByAddress');
const walletsByAddress = provideDurableMapStore(baggage, 'walletsByAddress');
const provider = makeAtomicProvider(walletsByAddress);

const handleWalletAction = makeExo(
Expand Down Expand Up @@ -166,24 +166,20 @@ export const start = async (zcf, privateArgs, baggage) => {
},
);

// NOTE: both `MsgWalletAction` and `MsgWalletSpendAction` arrive as BRIDGE_ID.WALLET
// by way of performAction() in cosmic-swingset/src/launch-chain.js
await (walletBridgeManager &&
E(walletBridgeManager).setHandler(handleWalletAction));

// Resolve these first because the wallet maker must be synchronous
// Zoe is an inter-vat call and thus cannot be made during upgrade. So ensure that
// the first incarnation saves them and subsequent ones read from baggage.
const invitationIssuerP = E(zoe).getInvitationIssuer();
const [
const {
invitationIssuer,
invitationBrand,
invitationDisplayInfo,
publicMarshaller,
] = await Promise.all([
invitationIssuerP,
E(invitationIssuerP).getBrand(),
E(E(invitationIssuerP).getBrand()).getDisplayInfo(),
E(board).getReadonlyMarshaller(),
]);
} = await provideAll(baggage, {
invitationIssuer: invitationIssuerP,
invitationBrand: E(invitationIssuerP).getBrand(),
invitationDisplayInfo: E(E(invitationIssuerP).getBrand()).getDisplayInfo(),
publicMarshaller: E(board).getReadonlyMarshaller(),
});

const registry = makeAssetRegistry(assetPublisher);

Expand All @@ -204,7 +200,8 @@ export const start = async (zcf, privateArgs, baggage) => {
*/
const makeSmartWallet = prepareSmartWallet(baggage, shared);

const creatorFacet = makeExo(
const creatorFacet = prepareExo(
baggage,
'walletFactoryCreator',
M.interface('walletFactoryCreatorI', {
provideSmartWallet: M.callWhen(
Expand Down Expand Up @@ -246,6 +243,11 @@ export const start = async (zcf, privateArgs, baggage) => {
},
);

// NOTE: both `MsgWalletAction` and `MsgWalletSpendAction` arrive as BRIDGE_ID.WALLET
// by way of performAction() in cosmic-swingset/src/launch-chain.js
await (walletBridgeManager &&
E(walletBridgeManager).setHandler(handleWalletAction));

return {
creatorFacet,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/smart-wallet/test/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
`${dirname}/../src/walletFactory.js`,
'walletFactory',
);
/** @type {Promise<Installation<import('../src/walletFactory.js').start>>} */
/** @type {Promise<Installation<import('../src/walletFactory.js').prepare>>} */
const installation = E(zoe).install(bundle);
// #endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// @ts-check
import { Fail } from '@agoric/assert';
import { makeTracer } from '@agoric/internal';
import { makeFakeStorageKit } from '@agoric/internal/src/storage-test-utils.js';
import { makeSubscriptionKit } from '@agoric/notifier';
import { makeNameHubKit } from '@agoric/vats';
import { makeAgoricNamesAccess } from '@agoric/vats/src/core/utils.js';
import { makeBoard } from '@agoric/vats/src/lib-board.js';
import { makeFakeBankKit } from '@agoric/vats/tools/bank-utils.js';
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

const trace = makeTracer('BootWFUpg', false);

export const wfV1BundleName = 'walletFactoryV1';
export const wfV2BundleName = 'walletFactoryV2';

export const buildRootObject = () => {
const storageKit = makeFakeStorageKit('walletFactoryUpgradeTest');
const walletPath = 'walletFactoryUpgradeTest.agoric1whatever.current';
const board = makeBoard();
const { agoricNames } = makeAgoricNamesAccess();
const assetPublisher = Far('mockAssetPublisher', {
getAssetSubscription: () => makeSubscriptionKit().subscription,
});
const { nameAdmin: namesByAddressAdmin } = makeNameHubKit();

let vatAdmin;
/** @type {ZoeService} */
let zoe;
/** @type {AdminFacet} */
let adminFacet;
let creatorFacet;
let bank;
/** @type {import('../../../src/smartWallet.js').SmartWallet} */
let wallet;

// for startInstance
/** @type {Installation<import('../../../src/walletFactory.js').prepare>} */
let installation;
const terms = { agoricNames, board, assetPublisher };
const privateArgs = {
storageNode: storageKit.rootNode,
// omit walletBridgeManager
};

return Far('root', {
bootstrap: async (vats, devices) => {
vatAdmin = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin);
({ zoeService: zoe } = await E(vats.zoe).buildZoe(
vatAdmin,
undefined,
'zcf',
));

({ bank } = makeFakeBankKit([]));

const v1BundleId = await E(vatAdmin).getBundleIDByName(wfV1BundleName);
assert(v1BundleId, 'bundleId must not be empty');
installation = await E(zoe).installBundleID(v1BundleId);
},

buildV1: async () => {
trace(`BOOT buildV1 start`);
// build the contract vat from ZCF and the contract bundlecap

// Complete round-trip without upgrade
trace(`BOOT buildV1 startInstance`);
const facets = await E(zoe).startInstance(
installation,
{},
terms,
privateArgs,
);
({ adminFacet, creatorFacet } = facets);
const [newWallet, isNew] = await E(creatorFacet).provideSmartWallet(
'agoric1whatever',
bank,
namesByAddressAdmin,
);
isNew || Fail`wallet in buildV1 should be new`;
wallet = newWallet;

const currentStoragePath = await E.get(
E.get(E(wallet).getPublicTopics()).current,
).storagePath;
currentStoragePath === walletPath || Fail`bad storage path`;

return true;
},

nullUpgradeV1: async () => {
trace(`BOOT nullUpgradeV1 start`);

trace(`BOOT nullUpgradeV1 upgradeContract`);
const bundleId = await E(vatAdmin).getBundleIDByName(wfV1BundleName);
const upgradeResult = await E(adminFacet).upgradeContract(
bundleId,
privateArgs,
);
assert.equal(upgradeResult.incarnationNumber, 2);

const [wallet2, isNew] = await E(creatorFacet).provideSmartWallet(
'agoric1whatever',
bank,
namesByAddressAdmin,
);
!isNew || Fail`wallet in nullUpgradeV1 should not be new`;
wallet2 === wallet || Fail`must be same wallet obj`;

const currentStoragePath = await E.get(
E.get(E(wallet).getPublicTopics()).current,
).storagePath;
currentStoragePath === walletPath || Fail`bad storage path`;

return true;
},

upgradeV2: async () => {
trace(`BOOT upgradeV2 start`);
const bundleId = await E(vatAdmin).getBundleIDByName(wfV2BundleName);

const upgradeResult = await E(adminFacet).upgradeContract(
bundleId,
privateArgs,
);
assert.equal(upgradeResult.incarnationNumber, 3);
trace(`BOOT upgradeV2 startInstance`);

const [wallet2, isNew] = await E(creatorFacet).provideSmartWallet(
'agoric1whatever',
bank,
namesByAddressAdmin,
);
!isNew || Fail`wallet in nullUpgradeV1 should not be new`;
wallet2 === wallet || Fail`must be same wallet obj`;

const currentStoragePath = await E.get(
E.get(E(wallet).getPublicTopics()).current,
).storagePath;
currentStoragePath === walletPath || Fail`bad storage path`;

// verify new method is present
const result = await E(creatorFacet).sayHelloUpgrade();
result === 'hello, upgrade' || Fail`bad upgrade`;

trace('Boot finished test');
return true;
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';

import { assert } from '@agoric/assert';
import { buildVatController } from '@agoric/swingset-vat';
import {
wfV1BundleName,
wfV2BundleName,
} from './bootstrap-walletFactory-service-upgrade.js';

// so paths can be expresssed relative to this file and made absolute
const bfile = name => new URL(name, import.meta.url).pathname;

test('walletFactory service upgrade', async t => {
/** @type {SwingSetConfig} */
const config = {
defaultManagerType: 'local',
bundleCachePath: 'bundles/',
bootstrap: 'bootstrap',
vats: {
bootstrap: {
// TODO refactor to use bootstrap-relay.js
sourceSpec: bfile('bootstrap-walletFactory-service-upgrade.js'),
},
zoe: { sourceSpec: bfile('../../../../vats/src/vat-zoe.js') },
},
bundles: {
zcf: {
sourceSpec: bfile('../../../../zoe/src/contractFacet/vatRoot.js'),
},
[wfV1BundleName]: {
sourceSpec: bfile('../../../src/walletFactory.js'),
},
[wfV2BundleName]: { sourceSpec: bfile('walletFactory-V2.js') },
},
};

t.log('buildVatController');
const c = await buildVatController(config);
c.pinVatRoot('bootstrap');
t.log('run controller');
await c.run();

const run = async (name, args = []) => {
assert(Array.isArray(args));
const kpid = c.queueToVatRoot('bootstrap', name, args);
await c.run();
const status = c.kpStatus(kpid);
const capdata = c.kpResolution(kpid);
return [status, capdata];
};

t.log('create initial version');
const [v1status] = await run('buildV1', []);
t.is(v1status, 'fulfilled');

t.log('perform null upgrade');
const [null1status] = await run('nullUpgradeV1', []);
t.is(null1status, 'fulfilled');

t.log('now perform the V2 upgrade');
const [v2status] = await run('upgradeV2', []);
t.is(v2status, 'fulfilled');
});
Loading

0 comments on commit 6af1746

Please sign in to comment.