Skip to content

Commit

Permalink
feat(zoe): implement E(zoe).install(bundleOrBundleID)
Browse files Browse the repository at this point in the history
Previously, `E(zoe).install()` accepted a source bundle. Now it accepts
either a source bundle, or a "bundle ID". The ID is a hash-based identifier
string that refers to a bundle installed into the kernel via
`controller.validateAndInstallBundle()`, and is retrievable from
vatAdminService. Zoe exchanges the ID for a bundlecap, and retains the
bundlecap for future use (including passing to the new ZCF vat, which
converts it into a source bundle for evaluation at the last possible moment).

The kernel install step can happen either before or after `E(zoe).install`,
because the id-to-bundlecap conversion waits until the kernel install is
complete.

This begins the process of making Zoe work exclusively with (small)
bundlecaps, and not (large) source bundles. The next step is to modify all
unit tests and external callers (including deploy scripts, #4564) to
kernel-install their bundle and use a bundleID for the Zoe install()
invocation. After that is complete, #4565 will remove support for full
bundles, and `E(zoe).install(bundleID)` will be the only choice.

closes #4563
  • Loading branch information
warner committed Feb 16, 2022
1 parent b4b6ac0 commit 9c960fd
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 65 deletions.
2 changes: 1 addition & 1 deletion packages/zoe/src/contractFacet/internal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

/**
* @typedef ZCFZygote
* @property {(bundle: SourceBundle) => void} evaluateContract
* @property {(bundleOrBundlecap: SourceBundle | Bundlecap) => void} evaluateContract
* @property {(instanceAdminFromZoe: ERef<ZoeInstanceAdmin>,
* instanceRecordFromZoe: InstanceRecord,
* issuerStorageFromZoe: IssuerRecords,
Expand Down
4 changes: 2 additions & 2 deletions packages/zoe/src/contractFacet/vatRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function buildRootObject(powers, _params, testJigSetter = undefined) {

/** @type {ExecuteContract} */
const executeContract = (
bundle,
bundleOrBundlecap,
zoeService,
invitationIssuer,
zoeInstanceAdmin,
Expand All @@ -44,7 +44,7 @@ export function buildRootObject(powers, _params, testJigSetter = undefined) {
invitationIssuer,
testJigSetter,
);
zcfZygote.evaluateContract(bundle);
zcfZygote.evaluateContract(bundleOrBundlecap);
return zcfZygote.startContract(
zoeInstanceAdmin,
instanceRecordFromZoe,
Expand Down
12 changes: 10 additions & 2 deletions packages/zoe/src/contractFacet/zcfZygote.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { assert, details as X, makeAssert } from '@agoric/assert';
import { E } from '@agoric/eventual-send';
import { Far, Remotable } from '@endo/marshal';
import { Far, Remotable, passStyleOf } from '@endo/marshal';
import { AssetKind, AmountMath } from '@agoric/ertp';
import { makeNotifierKit, observeNotifier } from '@agoric/notifier';
import { makePromiseKit } from '@agoric/promise-kit';
Expand Down Expand Up @@ -340,7 +340,15 @@ export const makeZCFZygote = (
* @type {ZCFZygote}
* */
const zcfZygote = {
evaluateContract: bundle => {
evaluateContract: bundleOrBundlecap => {
let bundle;
if (passStyleOf(bundleOrBundlecap) === 'remotable') {
const bundlecap = bundleOrBundlecap;
// @ts-ignore powers is not typed correctly: https://github.com/Agoric/agoric-sdk/issues/3239s
bundle = powers.D(bundlecap).getBundle();
} else {
bundle = bundleOrBundlecap;
}
contractCode = evalContractBundle(bundle);
handlePWarning(contractCode);
},
Expand Down
76 changes: 53 additions & 23 deletions packages/zoe/src/zoeService/installationStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,74 @@
import { assert, details as X } from '@agoric/assert';
import { Far } from '@endo/marshal';
import { E } from '@agoric/eventual-send';
import { makeWeakStore } from '@agoric/store';

/**
*
* @param {GetBundlecapFromID} getBundlecapFromID
*/
export const makeInstallationStorage = () => {
/** @type {WeakSet<Installation>} */
const installations = new WeakSet();
export const makeInstallationStorage = getBundlecapFromID => {
/** @type {WeakStore<Installation, Bundlecap>} */
const installationsBundlecap = makeWeakStore('installationsBundlecap');
/** @type {WeakStore<Installation, SourceBundle>} */
const installationsBundle = makeWeakStore('installationsBundle');

/**
* Create an installation by permanently storing the bundle. The code is
* currently evaluated each time it is used to make a new instance of a
* contract. When SwingSet supports zygotes, the code will be evaluated once
* when creating a zcfZygote, then the start() function will be called each
* time an instance is started.
* Create an installation from a bundle ID or a full bundle. If we are
* given a bundle ID, wait for the corresponding code bundle to be received
* by the swingset kernel, then store its bundlecap. The code is currently
* evaluated each time it is used to make a new instance of a contract.
* When SwingSet supports zygotes, the code will be evaluated once when
* creating a zcfZygote, then the start() function will be called each time
* an instance is started.
*/
/** @type {Install} */
const install = async bundle => {
assert.typeof(bundle, 'object', X`a bundle must be provided`);
const install = async bundleOrID => {
/** @type {Installation} */
const installation = Far('Installation', {
getBundle: () => bundle,
let installation;
if (typeof bundleOrID === 'object') {
/** @type {SourceBundle} */
const bundle = bundleOrID;
installation = Far('Installation', {
getBundle: () => bundle,
getBundleID: () => {
throw Error('installation used a bundle, not ID');
},
});
installationsBundle.init(installation, bundle);
return installation;
}
assert.typeof(bundleOrID, 'string', `needs bundle or bundle ID`);
/** @type {BundleID} */
const bundleID = bundleOrID;
// this waits until someone tells the host application to store the
// bundle into the kernel, with controller.validateAndInstallBundle()
const bundlecap = await getBundlecapFromID(bundleID);
installation = Far('Installation', {
getBundle: () => {
throw Error('installation used a bundleID, not bundle');
},
getBundleID: () => bundleID,
});
installations.add(installation);
installationsBundlecap.init(installation, bundlecap);
return installation;
};

const assertInstallation = installation =>
assert(
installations.has(installation),
X`${installation} was not a valid installation`,
);

/** @type {UnwrapInstallation} */
const unwrapInstallation = installationP => {
return E.when(installationP, installation => {
assertInstallation(installation);
const bundle = installation.getBundle();
return { bundle, installation };
if (installationsBundlecap.has(installation)) {
return {
bundleOrBundlecap: installationsBundlecap.get(installation),
installation,
};
} else if (installationsBundle.has(installation)) {
return {
bundleOrBundlecap: installationsBundle.get(installation),
installation,
};
} else {
assert.fail(X`${installation} was not a valid installation`);
}
});
};

Expand Down
14 changes: 12 additions & 2 deletions packages/zoe/src/zoeService/internal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,19 @@
* @returns {void}
*/

/**
* @typedef { SourceBundle | Bundlecap } BundleOrBundlecap
*/

/**
* @callback UnwrapInstallation
*
* Assert the installation is known, and return the bundle and
* Assert the installation is known, and return the bundle/bundlecap and
* installation
*
* @param {ERef<Installation>} installationP
* @returns {Promise<{
* bundle: SourceBundle,
* bundleOrBundlecap: BundleOrBundlecap,
* installation:Installation
* }>}
*/
Expand Down Expand Up @@ -105,6 +109,12 @@
* @returns {ZoeInstanceStorageManager}
*/

/**
* @callback GetBundlecapFromID
* @param {BundleID} id
* @returns {Promise<Bundlecap>}
*/

/**
* @typedef ZoeStorageManager
* @property {MakeZoeInstanceStorageManager} makeZoeInstanceStorageManager
Expand Down
6 changes: 4 additions & 2 deletions packages/zoe/src/zoeService/startInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export const makeStartInstance = (
/** @type {WeakStore<SeatHandle, ZoeSeatAdmin>} */
const seatHandleToZoeSeatAdmin = makeWeakStore('seatHandle');

const { installation, bundle } = await unwrapInstallation(installationP);
const { installation, bundleOrBundlecap } = await unwrapInstallation(
installationP,
);
// AWAIT ///

if (privateArgs !== undefined) {
Expand Down Expand Up @@ -204,7 +206,7 @@ export const makeStartInstance = (
creatorInvitation: creatorInvitationP,
handleOfferObj,
} = await E(zcfRoot).executeContract(
bundle,
bundleOrBundlecap,
zoeServicePromise,
zoeInstanceStorageManager.invitationIssuer,
zoeInstanceAdminForZcf,
Expand Down
13 changes: 9 additions & 4 deletions packages/zoe/src/zoeService/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,17 @@
* @returns {Promise<InvitationDetails>}
*/

/**
* @typedef { SourceBundle | BundleID } BundleOrBundleID
*/

/**
* @callback Install
*
* Create an installation by safely evaluating the code and
* registering it with Zoe. Returns an installation.
*
* @param {SourceBundle} bundle
* @param {BundleOrBundleID} bundleOrBundleID
* @returns {Promise<Installation>}
*/

Expand Down Expand Up @@ -272,9 +276,9 @@

/**
* @typedef {Object} VatAdminSvc
* @property {(BundleID: id) => Bundlecap} getBundlecap
* @property {(name: string) => Bundlecap} getNamedBundlecap
* @property {(bundlecap: Bundlecap) => RootAndAdminNode} createVat
* @property {(BundleID: id) => Promise<Bundlecap>} getBundlecap
* @property {(name: string) => Promise<Bundlecap>} getNamedBundlecap
* @property {(bundlecap: Bundlecap) => Promise<RootAndAdminNode>} createVat
*/

/**
Expand Down Expand Up @@ -306,6 +310,7 @@
/**
* @typedef {Object} Installation
* @property {() => SourceBundle} getBundle
* @property {() => BundleID} getBundleID
*/

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/zoe/src/zoeService/zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import '../internal-types.js';

import { AssetKind } from '@agoric/ertp';
import { Far } from '@endo/marshal';
import { E } from '@agoric/eventual-send';
import { makePromiseKit } from '@agoric/promise-kit';

import { makeZoeStorageManager } from './zoeStorageManager.js';
Expand Down Expand Up @@ -61,6 +62,8 @@ const makeZoeKit = (
shutdownZoeVat,
);

const getBundlecapFromID = bundleID => E(vatAdminSvc).getBundlecap(bundleID);

// This method contains the power to create a new ZCF Vat, and must
// be closely held. vatAdminSvc is even more powerful - any vat can
// be created. We severely restrict access to vatAdminSvc for this reason.
Expand All @@ -83,6 +86,7 @@ const makeZoeKit = (
invitationIssuer,
} = makeZoeStorageManager(
createZCFVat,
getBundlecapFromID,
getFeeIssuerKit,
shutdownZoeVat,
feeIssuer,
Expand Down
5 changes: 4 additions & 1 deletion packages/zoe/src/zoeService/zoeStorageManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { makeInstallationStorage } from './installationStorage.js';
*
* @param {CreateZCFVat} createZCFVat - the ability to create a new
* ZCF Vat
* @param {GetBundlecapFromID} getBundlecapFromID
* @param {GetFeeIssuerKit} getFeeIssuerKit
* @param {ShutdownWithFailure} shutdownZoeVat
* @param {Issuer} feeIssuer
Expand All @@ -34,6 +35,7 @@ import { makeInstallationStorage } from './installationStorage.js';
*/
export const makeZoeStorageManager = (
createZCFVat,
getBundlecapFromID,
getFeeIssuerKit,
shutdownZoeVat,
feeIssuer,
Expand Down Expand Up @@ -82,7 +84,8 @@ export const makeZoeStorageManager = (
// Zoe stores "installations" - identifiable bundles of contract
// code that can be reused again and again to create new contract
// instances
const { install, unwrapInstallation } = makeInstallationStorage();
const { install, unwrapInstallation } =
makeInstallationStorage(getBundlecapFromID);

/** @type {MakeZoeInstanceStorageManager} */
const makeZoeInstanceStorageManager = async (
Expand Down
4 changes: 3 additions & 1 deletion packages/zoe/test/unitTests/setupBasicMints.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { makeIssuerKit, AmountMath } from '@agoric/ertp';
import { makeStore } from '@agoric/store';
import { makeZoeKit } from '../../src/zoeService/zoe.js';
import fakeVatAdmin from '../../tools/fakeVatAdmin.js';
import { makeFakeVatAdmin } from '../../tools/fakeVatAdmin.js';

const setup = () => {
const moolaBundle = makeIssuerKit('moola');
Expand All @@ -21,6 +21,7 @@ const setup = () => {
brands.init(k, allBundles[k].brand);
}

const { admin: fakeVatAdmin, vatAdminState } = makeFakeVatAdmin();
const { zoeService: zoe } = makeZoeKit(fakeVatAdmin);

/** @type {(brand: Brand) => (value: AmountValue) => Amount} */
Expand Down Expand Up @@ -66,6 +67,7 @@ const setup = () => {
simoleans: makeSimpleMake(simoleanBundle.brand),
bucks: makeSimpleMake(bucksBundle.brand),
zoe,
vatAdminState,
};
harden(result);
return result;
Expand Down
18 changes: 16 additions & 2 deletions packages/zoe/test/unitTests/test-zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ test(`E(zoe).install bad bundle`, async t => {
const { zoe } = setup();
// @ts-ignore deliberate invalid arguments for testing
await t.throwsAsync(() => E(zoe).install(), {
message: 'a bundle must be provided',
message: 'a bundle or bundle ID must be provided',
});
});

test(`E(zoe).install`, async t => {
test(`E(zoe).install(bundle)`, async t => {
const { zoe } = setup();
const contractPath = `${dirname}/../../src/contracts/atomicSwap`;
const bundle = await bundleSource(contractPath);
Expand All @@ -53,6 +53,20 @@ test(`E(zoe).install`, async t => {
t.is(await E(installation).getBundle(), bundle);
});

test(`E(zoe).install(bundleID)`, async t => {
const { zoe, vatAdminState } = setup();
const contractPath = `${dirname}/../../src/contracts/atomicSwap`;
const bundle = await bundleSource(contractPath);
vatAdminState.installBundle('b1-atomic', bundle);
const installation = await E(zoe).install('b1-atomic');
// TODO Check the integrity of the installation by its hash.
// https://github.com/Agoric/agoric-sdk/issues/3859
// const hash = await E(installation).getHash();
// assert.is(hash, 'XXX');
// NOTE: the bundle ID is now tha hash
t.is(await E(installation).getBundleID(), 'b1-atomic');
});

test(`E(zoe).startInstance bad installation`, async t => {
const { zoe } = setup();
// @ts-ignore deliberate invalid arguments for testing
Expand Down
Loading

0 comments on commit 9c960fd

Please sign in to comment.