Skip to content

Commit

Permalink
feat: Zoe use watchPromise() to wait for contract finish (#8453)
Browse files Browse the repository at this point in the history
* feat: zcf ask zoe to repair 'adminNode.done()` watcher on restart

use watchPromise() to wait for contract finish; This repairs new
contracts, but doesn't help existing contracts.

* test: add tests for userSeat.getExitSubscriber()

* test: repair broken test of upgrade

There was a bug in the part of the test that was attempting to run a
bunch of scenarios across the upgrade. Rather than resuming each run
after the upgrade, it was running the whole scenario from scratch
after the upgrade.

* test: demonstrate Zoe's survival through vatAdmmin vat upgrade

* refactor: upgrade-zoe proposal name

it's not necessarily null. it's whatever the version is that's built.

---------

Co-authored-by: Turadg Aleahmad <[email protected]>
  • Loading branch information
Chris-Hibbert and turadg authored Jan 4, 2024
1 parent 8ecb5d7 commit 6388a00
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ const checkFlow1 = async (
// restart Zoe
// /////// Upgrading ////////////////////////////////
await buildAndExecuteProposal(
'@agoric/builders/scripts/vats/null-upgrade-zoe-proposal.js',
'@agoric/builders/scripts/vats/upgrade-zoe-proposal.js',
);

await buyer.tryExitOffer(`${collateralBrandKey}-bid3`);
Expand Down
3 changes: 1 addition & 2 deletions packages/builders/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
"scripts": {
"build": "exit 0",
"build:add-STARS-proposal": "echo This command has been deprecated. Please run this instead: agoric run scripts/inter-protocol/add-STARS.js",
"build:restart-vats-proposal": "echo echo This command has been deprecated. Please run this instead: agoric run scripts/vats/restart-vats.js",
"build:restart-vats-proposal": "echo This command has been deprecated. Please run this instead: agoric run scripts/vats/restart-vats.js",
"build:zcf-proposal": "echo This command has been deprecated. Please run this instead: agoric run scripts/vats/replace-zoe.js",
"build:null-upgrade-zoe-proposal": "echo This command has been deprecated. Please run this instead: agoric run scripts/vats/replace-zoe.js",
"prepack": "tsc --build tsconfig.build.json",
"postpack": "git clean -f '*.d.ts*'",
"test": "ava",
Expand Down
4 changes: 2 additions & 2 deletions packages/builders/scripts/vats/upgrade-zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { makeHelpers } from '@agoric/deploy-script-support';
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) =>
harden({
sourceSpec: '@agoric/vats/src/proposals/null-upgrade-zoe-proposal.js',
sourceSpec: '@agoric/vats/src/proposals/upgrade-zoe-proposal.js',
getManifestCall: [
'getManifestForUpgradingZoe',
{
Expand All @@ -14,5 +14,5 @@ export const defaultProposalBuilder = async ({ publishRef, install }) =>

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('null-upgrade-zoe', defaultProposalBuilder);
await writeCoreProposal('upgrade-zoe', defaultProposalBuilder);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { E } from '@endo/far';
* @param {object} options
* @param {{ zoeRef: VatSourceRef; zcfRef: VatSourceRef }} options.options
*/
export const nullUpgradeZoe = async (
export const upgradeZoe = async (
{ consume: { vatAdminSvc, vatStore } },
options,
) => {
Expand All @@ -30,7 +30,7 @@ export const nullUpgradeZoe = async (

export const getManifestForUpgradingZoe = (_powers, { zoeRef }) => ({
manifest: {
[nullUpgradeZoe.name]: {
[upgradeZoe.name]: {
consume: {
vatAdminSvc: 'vatAdminSvc',
vatStore: 'vatStore',
Expand Down
8 changes: 8 additions & 0 deletions packages/zoe/src/contractFacet/zcfZygote.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ export const makeZCFZygote = async (
instanceRecHolder = makeInstanceRecord(instanceRecordFromZoe);
instantiateIssuerStorage(issuerStorageFromZoe);
zcfBaggage.init('instanceRecHolder', instanceRecHolder);
zcfBaggage.init('repairedContractCompletionWatcher', true);

const { privateArgsShape } = meta;
if (privateArgsShape) {
Expand Down Expand Up @@ -466,6 +467,13 @@ export const makeZCFZygote = async (
instanceRecHolder = zcfBaggage.get('instanceRecHolder');
initSeatMgrAndMintKind();

await null;
if (!zcfBaggage.has('repairedContractCompletionWatcher')) {
await E(zoeInstanceAdmin).repairContractCompletionWatcher();
console.log(`Repaired contract completion watcher`);
zcfBaggage.init('repairedContractCompletionWatcher', true);
}

const { privateArgsShape } = meta;
if (privateArgsShape) {
mustMatch(privateArgs, privateArgsShape, 'privateArgs');
Expand Down
1 change: 1 addition & 0 deletions packages/zoe/src/internal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
* @property {(strings: Array<string>) => void} setOfferFilter
* @property {() => Array<string>} getOfferFilter
* @property {(seatHandle: SeatHandle) => Subscriber<AmountKeywordRecord>} getExitSubscriber
* @property {() => void} repairContractCompletionWatcher
*/

/**
Expand Down
1 change: 1 addition & 0 deletions packages/zoe/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export const InstanceAdminI = M.interface('InstanceAdmin', {
getOfferFilter: M.call().returns(M.arrayOf(M.string())),
getExitSubscriber: M.call(SeatShape).returns(SubscriberShape),
isBlocked: M.call(M.string()).returns(M.boolean()),
repairContractCompletionWatcher: M.call().returns(),
});

export const InstanceStorageManagerIKit = harden({
Expand Down
64 changes: 53 additions & 11 deletions packages/zoe/src/zoeService/startInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ import {
makeScalarBigMapStore,
provideDurableWeakMapStore,
prepareExoClass,
prepareExo,
watchPromise,
} from '@agoric/vat-data';
import { initEmpty } from '@agoric/store';
import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';

import { defineDurableHandle } from '../makeHandle.js';
import { makeInstanceAdminMaker } from './instanceAdminStorage.js';
import { AdminFacetI, InstanceAdminI } from '../typeGuards.js';
import {
AdminFacetI,
InstanceAdminI,
InstanceAdminShape,
} from '../typeGuards.js';

// import '../internal-types.js';

/** @typedef {import('@agoric/vat-data').Baggage} Baggage */
/** @typedef { import('@agoric/swingset-vat').BundleCap} BundleCap */
Expand Down Expand Up @@ -53,10 +62,45 @@ export const makeStartInstance = (
const InstanceAdminStateShape = harden({
instanceStorage: M.remotable('ZoeInstanceStorageManager'),
instanceAdmin: M.remotable('InstanceAdmin'),
seatHandleToSeatAdmin: M.remotable(),
seatHandleToSeatAdmin: M.remotable(), // seatHandleToSeatAdmin, but putting that string here is backwards-incompatible
adminNode: M.remotable('adminNode'),
});

/** @type {import('@agoric/swingset-liveslots').PromiseWatcher<unknown, [InstanceAdmin, Handle<'adminNode'>]>} */
const watcher = prepareExo(
zoeBaggage,
'InstanceCompletionWatcher',
M.interface('InstanceCompletionWatcher', {
onFulfilled: M.call(
M.any(),
InstanceAdminShape,
M.remotable('adminNode'),
).returns(),
onRejected: M.call(
M.any(),
InstanceAdminShape,
M.remotable('adminNode'),
).returns(),
}),
{
onFulfilled: (completion, instanceAdmin) =>
instanceAdmin.exitAllSeats(completion),
onRejected: (/** @type {Error} */ reason, instanceAdmin, adminNode) => {
if (isUpgradeDisconnection(reason)) {
console.log(`resetting promise watcher after upgrade`, reason);
// eslint-disable-next-line no-use-before-define
watchForAdminNodeDone(adminNode, instanceAdmin);
} else {
instanceAdmin.failAllSeats(reason);
}
},
},
);

const watchForAdminNodeDone = (adminNode, instAdmin) => {
watchPromise(E(adminNode).done(), watcher, instAdmin, adminNode);
};

const makeZoeInstanceAdmin = prepareExoClass(
zoeBaggage,
'zoeInstanceAdmin',
Expand Down Expand Up @@ -131,10 +175,10 @@ export const makeStartInstance = (
replaceAllocations(seatHandleAllocations) {
const { state } = this;
try {
seatHandleAllocations.forEach(({ seatHandle, allocation }) => {
for (const { seatHandle, allocation } of seatHandleAllocations) {
const zoeSeatAdmin = state.seatHandleToSeatAdmin.get(seatHandle);
zoeSeatAdmin.replaceAllocation(allocation);
});
}
} catch (err) {
// nothing for Zoe to do if the termination fails
void E(state.adminNode).terminateWithFailure(err);
Expand All @@ -161,6 +205,10 @@ export const makeStartInstance = (
const { state } = this;
return state.instanceAdmin.isBlocked(string);
},
repairContractCompletionWatcher() {
const { state, self } = this;
void watchForAdminNodeDone(state.adminNode, self);
},
},
{
stateShape: InstanceAdminStateShape,
Expand Down Expand Up @@ -278,13 +326,7 @@ export const makeStartInstance = (
);
zoeInstanceStorageManager.initInstanceAdmin(instanceHandle, instanceAdmin);

E.when(
E(adminNode).done(),
completion => {
instanceAdmin.exitAllSeats(completion);
},
reason => instanceAdmin.failAllSeats(reason),
);
void watchForAdminNodeDone(adminNode, instanceAdmin);

/** @type {ZoeInstanceAdmin} */
const zoeInstanceAdminForZcf = makeZoeInstanceAdmin(
Expand Down
23 changes: 20 additions & 3 deletions packages/zoe/test/swingsetTests/zoe/test-zoe-upgrade.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import '@agoric/swingset-liveslots/tools/prepare-test-env.js';
import test from 'ava';

import bundleSource from '@endo/bundle-source';
import { buildVatController } from '@agoric/swingset-vat';
import { kunser } from '@agoric/kmarshal';

Expand Down Expand Up @@ -53,6 +55,18 @@ test('zoe vat upgrade trauma', async t => {
return awaitRun(kpid);
};

const restartVatAdminVat = async controller => {
const vaBundle = await bundleSource(
new URL(
'../../../../SwingSet/src/vats/vat-admin/vat-vat-admin.js',
import.meta.url,
).pathname,
);
const bundleID = await controller.validateAndInstallBundle(vaBundle);
controller.upgradeStaticVat('vatAdmin', true, bundleID, {});
await controller.run();
};

/**
* @see {@link ../upgradeCoveredCall/bootstrap-coveredCall-service-upgrade.js}
*/
Expand Down Expand Up @@ -227,20 +241,23 @@ test('zoe vat upgrade trauma', async t => {
pausedFlows.push({ result, remainingSteps: flow.slice(i) });
}

// Null-upgrade vatAdmin.
await restartVatAdminVat(c);

// Null-upgrade Zoe.
const { incarnationNumber } = await messageToVat(
const { incarnationNumber: zoeIncarnationNumber } = await messageToVat(
'bootstrap',
'upgradeVat',
zoeVatConfig,
);
t.is(incarnationNumber, 1, 'Zoe vat must be upgraded');
t.is(zoeIncarnationNumber, 1, 'Zoe vat must be upgraded');

// Verify a complete run in the new Zoe.
await doSteps('post-upgrade', flow);

// Verify completion of each paused flow.
for (const { result, remainingSteps } of pausedFlows) {
const [beforeStepName] = remainingSteps[0];
await doSteps(`resumed-${beforeStepName}`, flow, result);
await doSteps(`resumed-${beforeStepName}`, remainingSteps, result);
}
});
49 changes: 49 additions & 0 deletions packages/zoe/test/unitTests/zcf/test-zcf.js
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,45 @@ test('numWantsSatisfied: no', async t => {

await zcfSeat.exit();
t.is(await E(userSeat).numWantsSatisfied(), 0);

t.deepEqual(await E(E(userSeat).getExitSubscriber()).getUpdateSince(), {
updateCount: undefined,
value: undefined,
});
});

test('numWantsSatisfied: fail', async t => {
const { zcf } = await setupZCFTest();
const doubloonMint = await zcf.makeZCFMint('Doubloons');
const yenMint = await zcf.makeZCFMint('Yen');
const { brand: doubloonBrand } = doubloonMint.getIssuerRecord();
const { brand: yenBrand } = yenMint.getIssuerRecord();
const yenAmount = AmountMath.make(yenBrand, 100n);
const proposal = harden({
give: { DownPayment: yenAmount },
want: { Bonus: AmountMath.make(doubloonBrand, 1_000_000n) },
});

const { zcfSeat: mintSeat, userSeat: payoutSeat } = zcf.makeEmptySeatKit();
yenMint.mintGains(harden({ Cost: yenAmount }), mintSeat);
mintSeat.exit();
const payout = await E(payoutSeat).getPayout('Cost');
const payment = { DownPayment: payout };

const { zcfSeat, userSeat } = await makeOffer(
zcf.getZoeService(),
zcf,
proposal,
payment,
);

void zcfSeat.fail(Error('whatever'));
t.is(await E(userSeat).numWantsSatisfied(), 0);

await t.throwsAsync(
() => E(E(userSeat).getExitSubscriber()).getUpdateSince(),
{ message: 'whatever' },
);
});

test('numWantsSatisfied: yes', async t => {
Expand All @@ -1293,6 +1332,11 @@ test('numWantsSatisfied: yes', async t => {

await zcfSeat.exit();
t.is(await E(userSeat).numWantsSatisfied(), 1);

t.deepEqual(await E(E(userSeat).getExitSubscriber()).getUpdateSince(), {
updateCount: undefined,
value: undefined,
});
});

test('numWantsSatisfied as promise', async t => {
Expand All @@ -1317,4 +1361,9 @@ test('numWantsSatisfied as promise', async t => {

await zcfSeat.exit();
await outcome;

t.deepEqual(await E(E(userSeat).getExitSubscriber()).getUpdateSince(), {
updateCount: undefined,
value: undefined,
});
});

0 comments on commit 6388a00

Please sign in to comment.