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 18, 2022
1 parent 1f74b20 commit bc01c08
Show file tree
Hide file tree
Showing 13 changed files with 249 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 '@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
75 changes: 53 additions & 22 deletions packages/zoe/src/zoeService/installationStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,80 @@
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 {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);
/** @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);
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 @@ -272,9 +293,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 @@ -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 @@ -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 bc01c08

Please sign in to comment.