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

8453 release2 zoe #8802

Merged
merged 4 commits into from
Feb 8, 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
8 changes: 6 additions & 2 deletions packages/zoe/src/contractFacet/zcfZygote.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ export const makeZCFZygote = async (
instanceRecHolder = makeInstanceRecord(instanceRecordFromZoe);
instantiateIssuerStorage(issuerStorageFromZoe);
zcfBaggage.init('instanceRecHolder', instanceRecHolder);
zcfBaggage.init('repairedContractCompletionWatcher', true);

const startFn = start || prepare;
if (privateArgsShape) {
Expand Down Expand Up @@ -423,8 +424,11 @@ export const makeZCFZygote = async (
instanceRecHolder = zcfBaggage.get('instanceRecHolder');
initSeatMgrAndMintKind();

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

// restart an upgradeable contract
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 @@ -190,6 +190,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
63 changes: 52 additions & 11 deletions packages/zoe/src/zoeService/startInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ 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';

/** @typedef {import('@agoric/vat-data').Baggage} Baggage */
/** @typedef { import('@agoric/swingset-vat').BundleCap} BundleCap */
Expand Down Expand Up @@ -46,10 +53,46 @@ 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 {{ onFulfilled: (completion: any, instanceAdmin: InstanceAdmin) => void, onRejected: (reason: any, instanceAdmin: InstanceAdmin, adminNode: any) => void }} */
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) => {
// @ts-expect-error XXX unknown type?
watchPromise(E(adminNode).done(), watcher, instAdmin, adminNode);
};

const makeZoeInstanceAdmin = prepareExoClass(
zoeBaggage,
'zoeInstanceAdmin',
Expand Down Expand Up @@ -124,10 +167,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 @@ -154,6 +197,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 @@ -274,13 +321,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
25 changes: 22 additions & 3 deletions packages/zoe/test/swingsetTests/zoe/test-zoe-upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import '@agoric/swingset-vat/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/swingset-vat/src/lib/kmarshal.js';

Expand Down Expand Up @@ -52,6 +54,18 @@ test('zoe vat upgrade trauma', async t => {
const messageObject = (presence, methodName, args) =>
run('messageVatObject', [{ presence, methodName, args }]);

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 @@ -206,16 +220,21 @@ 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 run('upgradeVat', [zoeVatConfig]);
t.is(incarnationNumber, 1, 'Zoe vat must be upgraded');
const { incarnationNumber: zoeIncarnationNumber } = await run('upgradeVat', [
zoeVatConfig,
]);
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 @@ -1270,6 +1270,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 @@ -1290,6 +1329,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 @@ -1314,4 +1358,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