Skip to content

Commit

Permalink
fix(zoe): add zoe.installBundleID
Browse files Browse the repository at this point in the history
To transition Zoe from full contract bundles to bundlecaps, this adds a new
install API. `E(zoe).install(bundle)` is unchanged, but the new preferred
approach is `E(zoe).installBundleID(bundleID)`. This requires the
corresponding bundle to be installed with the swingset kernel's
vatAdminService, either before or after `installBundleID()` (zoe will wait
forever for the bundle to be installed).

Zoe `Installation` objects retain their `getBundle()` method to accomodate
dapp tests that have not switched to the new approach, but it throws an error
if used on a new bundleID-based installation. A new method named
`E(zoe).getBundleIDFromInstallation(allegedInstallation)` can be used to both
validate the installation and get back the bundleID, but it throws on the old
bundle-based installations.

Internally, the installationStorage.unwrapInstallation now returns either `{
bundle, installation }` or `{ bundleCap, bundleID, installation }`. ZCF's
`evaluateContract()` method accepts either a bundlecap or a full bundle.

closes #4563
  • Loading branch information
warner committed Feb 23, 2022
1 parent bfb1acc commit 5c46487
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 72 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 '@endo/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
80 changes: 58 additions & 22 deletions packages/zoe/src/zoeService/installationStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,85 @@
import { assert, details as X } from '@agoric/assert';
import { Far } from '@endo/marshal';
import { E } from '@agoric/eventual-send';
import { makeWeakStore } from '@agoric/store';

/** @typedef { import('@agoric/swingset-vat').BundleID} BundleID */

/**
*
* @param {GetBundleCapForID} getBundleCapForID
*/
export const makeInstallationStorage = () => {
/** @type {WeakSet<Installation>} */
const installations = new WeakSet();
export const makeInstallationStorage = getBundleCapForID => {
/** @type {WeakStore<Installation, { bundleCap: BundleCap, bundleID: BundleID }>} */
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`);

/** @type {InstallBundle} */
const installBundle = async bundle => {
assert.typeof(bundle, 'object', 'a bundle must be provided');
/** @type {Installation} */
const installation = Far('Installation', {
getBundle: () => bundle,
});
installations.add(installation);
installationsBundle.init(installation, bundle);
return installation;
};

const assertInstallation = installation =>
assert(
installations.has(installation),
X`${installation} was not a valid installation`,
);
/** @type {InstallBundleID} */
const installBundleID = async bundleID => {
assert.typeof(bundleID, 'string', `a bundle ID must be provided`);
// this waits until someone tells the host application to store the
// bundle into the kernel, with controller.validateAndInstallBundle()
const bundleCap = await getBundleCapForID(bundleID);
// AWAIT

/** @type {Installation} */
const installation = Far('Installation', {
getBundle: () => {
throw Error('bundleID-based Installation');
},
});
installationsBundleCap.init(installation, { bundleCap, bundleID });
return installation;
};

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

const getBundleIDFromInstallation = async allegedInstallationP => {
const { bundleID } = await unwrapInstallation(allegedInstallationP);
// AWAIT
assert(bundleID, 'installation does not have a bundle ID');
return bundleID;
};

return harden({
install,
installBundle,
installBundleID,
unwrapInstallation,
getBundleIDFromInstallation,
});
};
20 changes: 17 additions & 3 deletions packages/zoe/src/zoeService/internal-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,21 @@
* @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,
* bundle?: SourceBundle,
* bundleCap?: BundleCap,
* bundleID?: BundleID,
* installation:Installation
* }>}
*/
Expand Down Expand Up @@ -105,13 +111,21 @@
* @returns {ZoeInstanceStorageManager}
*/

/**
* @callback GetBundleCapForID
* @param {BundleID} id
* @returns {Promise<BundleCap>}
*/

/**
* @typedef ZoeStorageManager
* @property {MakeZoeInstanceStorageManager} makeZoeInstanceStorageManager
* @property {GetAssetKindByBrand} getAssetKindByBrand
* @property {DepositPayments} depositPayments
* @property {Issuer} invitationIssuer
* @property {Install} install
* @property {InstallBundle} installBundle
* @property {InstallBundleID} installBundleID
* @property {GetBundleIDFromInstallation} getBundleIDFromInstallation
* @property {GetPublicFacet} getPublicFacet
* @property {GetBrands} getBrands
* @property {GetIssuers} getIssuers
Expand Down
9 changes: 7 additions & 2 deletions packages/zoe/src/zoeService/startInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ export const makeStartInstance = (
/** @type {WeakStore<SeatHandle, ZoeSeatAdmin>} */
const seatHandleToZoeSeatAdmin = makeWeakStore('seatHandle');

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

const bundleOrBundleCap = bundle || bundleCap;
assert(bundleOrBundleCap);

if (privateArgs !== undefined) {
const passStyle = passStyleOf(privateArgs);
assert(
Expand Down Expand Up @@ -204,7 +209,7 @@ export const makeStartInstance = (
creatorInvitation: creatorInvitationP,
handleOfferObj,
} = await E(zcfRoot).executeContract(
bundle,
bundleOrBundleCap,
zoeServicePromise,
zoeInstanceStorageManager.invitationIssuer,
zoeInstanceAdminForZcf,
Expand Down
31 changes: 26 additions & 5 deletions packages/zoe/src/zoeService/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* a smart contract in particular ways.
*
* @property {Install} install
* @property {InstallBundleID} installBundleID
* @property {StartInstance} startInstance
* @property {Offer} offer
* @property {GetPublicFacet} getPublicFacet
Expand All @@ -45,6 +46,7 @@
* Deprecated. Does nothing useful but provided during transition so less old
* code breaks.
* @property {GetConfiguration} getConfiguration
* @property {GetBundleIDFromInstallation} getBundleIDFromInstallation
*/

/**
Expand Down Expand Up @@ -113,15 +115,34 @@
*/

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

/**
* @callback InstallBundleID
*
* Create an installation from a Bundle ID. Returns an installation.
*
* @param {BundleID} bundleID
* @returns {Promise<Installation>}
*/

/**
* @callback GetBundleIDFromInstallation
*
* Verify that an alleged Invitation is real, and return the Bundle ID it
* will use for contract code.
*
* @param {ERef<Installation>}
* @returns {Promise<BundleID>}
*/

/**
* @callback StartInstance
*
Expand Down Expand Up @@ -268,9 +289,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
12 changes: 10 additions & 2 deletions packages/zoe/src/zoeService/zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../../exported.js';
import '../internal-types.js';

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

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 @@ -72,8 +75,10 @@ const makeZoeKit = (
depositPayments,
getAssetKindByBrand,
makeZoeInstanceStorageManager,
install,
installBundle,
installBundleID,
unwrapInstallation,
getBundleIDFromInstallation,
getPublicFacet,
getBrands,
getIssuers,
Expand All @@ -83,6 +88,7 @@ const makeZoeKit = (
invitationIssuer,
} = makeZoeStorageManager(
createZCFVat,
getBundleCapFromID,
getFeeIssuerKit,
shutdownZoeVat,
feeIssuer,
Expand Down Expand Up @@ -117,7 +123,8 @@ const makeZoeKit = (

/** @type {ZoeService} */
const zoeService = Far('zoeService', {
install,
install: installBundle,
installBundleID,
startInstance,
offer,
/**
Expand Down Expand Up @@ -145,6 +152,7 @@ const makeZoeKit = (
getInstallation,
getInvitationDetails,
getConfiguration,
getBundleIDFromInstallation,
});

// startInstance must pass the ZoeService to the newly created ZCF
Expand Down
13 changes: 11 additions & 2 deletions 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 {GetBundleCapForID} getBundleCapForID
* @param {GetFeeIssuerKit} getFeeIssuerKit
* @param {ShutdownWithFailure} shutdownZoeVat
* @param {Issuer} feeIssuer
Expand All @@ -34,6 +35,7 @@ import { makeInstallationStorage } from './installationStorage.js';
*/
export const makeZoeStorageManager = (
createZCFVat,
getBundleCapForID,
getFeeIssuerKit,
shutdownZoeVat,
feeIssuer,
Expand Down Expand Up @@ -82,7 +84,12 @@ 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 {
installBundle,
installBundleID,
unwrapInstallation,
getBundleIDFromInstallation,
} = makeInstallationStorage(getBundleCapForID);

/** @type {MakeZoeInstanceStorageManager} */
const makeZoeInstanceStorageManager = async (
Expand Down Expand Up @@ -237,7 +244,9 @@ export const makeZoeStorageManager = (
getAssetKindByBrand: issuerStorage.getAssetKindByBrand,
depositPayments: escrowStorage.depositPayments,
invitationIssuer,
install,
installBundle,
installBundleID,
getBundleIDFromInstallation,
getPublicFacet,
getBrands,
getIssuers,
Expand Down
Loading

0 comments on commit 5c46487

Please sign in to comment.