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

feat: Zoe use watchPromise() to wait for contract finish #8453

Merged
merged 5 commits into from
Jan 4, 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
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'),
Copy link
Member

Choose a reason for hiding this comment

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

This param isn't used. I thought we could remove it, but then remembered that in the latest Endo, Exo methods fail if they are passed extra arguments. Filing for future work: #8709

).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,
});
});
Loading