Skip to content

Commit

Permalink
refactor: make zcf durable
Browse files Browse the repository at this point in the history
unnest defineDurableKind
relax for promises too
use seatHandle rather than zoeSeatAdmin
notifiers not stored
make the zcfSeatMint durable
pass bundleCap to createZcfVat and rename to zcfBundleCapP
  • Loading branch information
Chris-Hibbert committed Jun 28, 2022
1 parent 3a856b6 commit bf46bdb
Show file tree
Hide file tree
Showing 24 changed files with 496 additions and 266 deletions.
24 changes: 3 additions & 21 deletions packages/ERTP/src/purse.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import {
defineDurableKindMulti,
makeScalarBigSetStore,
provideKindHandle,
makeScalarBigWeakMapStore,
} from '@agoric/vat-data';
import { provide } from '@agoric/store';
import { makeNotifierKit } from '@agoric/notifier';
import { AmountMath } from './amountMath.js';
import { makeTransientNotifierKit } from './transientNotifier.js';

const { details: X } = assert;

Expand All @@ -20,27 +18,11 @@ export const defineDurablePurse = (
) => {
// Note: Virtual for high cardinality, but *not* durable, and so
// broken across an upgrade.
/** @type {WeakMapStore<Purse, NotifierRecord<Amount>>} */
const transientNotiferKits = makeScalarBigWeakMapStore(
'transientNotiferKits',
);

const provideNotifierKit = purse =>
provide(transientNotiferKits, purse, () =>
makeNotifierKit(purse.getCurrentAmount()),
);

const provideNotifier = purse => provideNotifierKit(purse).notifier;
const notifyBalance = purse => {
if (transientNotiferKits.has(purse)) {
const { updater } = transientNotiferKits.get(purse);
updater.updateState(purse.getCurrentAmount());
}
};
const { provideNotifier, update: updateBalance } = makeTransientNotifierKit();

const updatePurseBalance = (state, newPurseBalance, purse) => {
state.currentBalance = newPurseBalance;
notifyBalance(purse);
updateBalance(purse, purse.getCurrentAmount());
};

// - This kind is a pair of purse and depositFacet that have a 1:1
Expand Down
30 changes: 30 additions & 0 deletions packages/ERTP/src/transientNotifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-check

import { makeScalarBigWeakMapStore } from '@agoric/vat-data';
import { provide } from '@agoric/store';
import { makeNotifierKit } from '@agoric/notifier';

// Note: Virtual for high cardinality, but *not* durable, and so
// broken across an upgrade.
export const makeTransientNotifierKit = () => {
/** @type {WeakMapStore<Purse, NotifierRecord<any>>} */
const transientNotiferKits = makeScalarBigWeakMapStore(
'transientNotiferKits',
);

const provideNotifierKit = key =>
provide(transientNotiferKits, key, () =>
makeNotifierKit(key.getCurrentAmount()),
);

const provideNotifier = key => provideNotifierKit(key).notifier;
const update = (key, newValue) => {
if (transientNotiferKits.has(key)) {
const { updater } = transientNotiferKits.get(key);
updater.updateState(newValue);
}
};

return { provideNotifier, update };
};
harden(makeTransientNotifierKit);
8 changes: 6 additions & 2 deletions packages/SwingSet/src/liveslots/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ function throwNotDurable(value, slotIndex, serializedValue) {
);
} catch (justinError) {
const err = assert.error(
X`value is not durable: ${value} at slot ${slotIndex} of ${serializedValue}`,
X`value is not durable: ${value} at slot ${q(
slotIndex,
)} of ${serializedValue}`,
);
assert.note(
err,
Expand All @@ -88,7 +90,9 @@ function throwNotDurable(value, slotIndex, serializedValue) {
throw err;
}
assert.fail(
X`value is not durable: ${value} at slot ${slotIndex} of ${encodedValue}`,
X`value is not durable: ${value} at slot ${q(
slotIndex,
)} of ${encodedValue}`,
);
}

Expand Down
6 changes: 4 additions & 2 deletions packages/SwingSet/src/liveslots/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,9 @@ export function makeVirtualObjectManager(
after.slots.forEach((vref, index) => {
assert(
vrm.isDurable(vref),
X`value for ${prop} is not durable at slot ${index} of ${after}`,
X`value for ${q(prop)} is not durable at slot ${q(
index,
)} of ${after}`,
);
});
}
Expand Down Expand Up @@ -736,7 +738,7 @@ export function makeVirtualObjectManager(
const data = serialize(initialData[prop]);
if (durable) {
data.slots.forEach(vref => {
assert(vrm.isDurable(vref), X`value for ${prop} is not durable`);
assert(vrm.isDurable(vref), X`value for ${q(prop)} is not durable`);
});
}
data.slots.forEach(vrm.addReachableVref);
Expand Down
8 changes: 4 additions & 4 deletions packages/SwingSet/src/liveslots/virtualReferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,15 +256,15 @@ export function makeVirtualReferenceManager(
*/
function isDurable(vref) {
const { type, id, virtual, allocatedByVat } = parseVatSlot(vref);
if (type !== 'object') {
if (relaxDurabilityRules) {
// we'll pretend an object is durable if running with relaxed rules
return true;
} else if (type !== 'object') {
// promises and devices are not durable
return false;
} else if (!allocatedByVat) {
// imports are durable
return true;
} else if (relaxDurabilityRules) {
// we'll pretend an object is durable if running with relaxed rules
return true;
} else if (virtual) {
// stores and virtual objects are durable if their kinds are so configured
return isDurableKind(id);
Expand Down
2 changes: 1 addition & 1 deletion packages/store/src/stores/store-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ harden(makeCurrentKeysKit);
* that key, and return it.
*
* @template K,V
* @param {MapStore<K,V>} mapStore
* @param {WeakMapStore<K,V>} mapStore
* @param {K} key
* @param {(key: K) => V} makeValue
* @returns {V}
Expand Down
11 changes: 8 additions & 3 deletions packages/zoe/src/contractFacet/offerHandlerStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

import { makeWeakStore } from '@agoric/store';
import { ToFarFunction } from '@endo/marshal';
import { makeScalarBigMapStore } from '@agoric/vat-data';

import { makeHandle } from '../makeHandle.js';
import { defineDurableHandle } from '../makeHandle.js';

export const makeOfferHandlerStorage = () => {
export const makeOfferHandlerStorage = (
zcfBaggage = makeScalarBigMapStore('zcfBaggage', { durable: true }),
) => {
const makeInvitationHandle = defineDurableHandle(zcfBaggage, 'Invitation');
/** @type {WeakStore<InvitationHandle, OfferHandler>} */
// TODO(MSM): we need to manage durable offerHandlers
const invitationHandleToHandler = makeWeakStore('invitationHandle');

/** @type {(offerHandler: OfferHandler) => InvitationHandle} */
const storeOfferHandler = offerHandler => {
const farOfferHandler = ToFarFunction('offerHandler', offerHandler);
const invitationHandle = makeHandle('Invitation');
const invitationHandle = makeInvitationHandle();
invitationHandleToHandler.init(invitationHandle, farOfferHandler);
return invitationHandle;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/zoe/src/contractFacet/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
* @typedef {object} ZCFSeat
* @property {() => void} exit
* @property {ZCFSeatFail} fail
* @property {() => Notifier<Allocation>} getNotifier
* @property {() => Promise<Notifier<Allocation>>} getNotifier
* @property {() => boolean} hasExited
* @property {() => ProposalRecord} getProposal
* @property {ZCFGetAmountAllocated} getAmountAllocated
Expand Down
18 changes: 10 additions & 8 deletions packages/zoe/src/contractFacet/vatRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export function buildRootObject(powers) {

/** @type {ExecuteContract} */
const executeContract = (
bundleOrBundleCap,
zoeService,
invitationIssuer,
zoeInstanceAdmin,
instanceRecordFromZoe,
issuerStorageFromZoe,
privateArgs = undefined,
contractBundleCap, // vatParams every time
zoeService, // vatParams first time and then baggage
invitationIssuer, // vatParams first time and then baggage
zoeInstanceAdmin, // in 1st message post-clone
instanceRecordFromZoe, // in 1st msg post-clone (could split out installation)
// terms might change on upgrade?
issuerStorageFromZoe, // instance specific; stored in baggage
privateArgs = undefined, // instance specific; stored in baggage;
// upgrade might supplement this
) => {
/** @type {ZCFZygote} */
const zcfZygote = makeZCFZygote(
Expand All @@ -43,7 +45,7 @@ export function buildRootObject(powers) {
invitationIssuer,
testJigSetter,
);
zcfZygote.evaluateContract(bundleOrBundleCap);
zcfZygote.evaluateContract(contractBundleCap);
return zcfZygote.startContract(
zoeInstanceAdmin,
instanceRecordFromZoe,
Expand Down
176 changes: 176 additions & 0 deletions packages/zoe/src/contractFacet/zcfMint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// @ts-check

import { E } from '@endo/eventual-send';
import { AmountMath } from '@agoric/ertp';
import {
provideDurableSingleton,
provideDurableSetStore,
makeScalarBigMapStore,
} from '@agoric/vat-data';

import { coerceAmountKeywordRecord } from '../cleanProposal.js';
import { makeIssuerRecord } from '../issuerRecord.js';
import { addToAllocation, subtractFromAllocation } from './allocationMath.js';

import '../../exported.js';
import '../internal-types.js';
import './internal-types.js';

import '@agoric/swingset-vat/src/types-ambient.js';

// helpers for the code shared between MakeZCFMint and RegisterZCFMint

export const makeZCFMintFactory = async (
zcfBaggage,
recordIssuer,
getAssetKindByBrand,
makeEmptySeatKit,
reallocateForZCFMint,
) => {
// The set of baggages for zcfMints
const zcfMintBaggageSet = provideDurableSetStore(zcfBaggage, 'baggageSet');

/**
* retrieve the state of the zcfMint from the baggage, and create a durable
* singleton reflecting that state.
*
* @param {MapStore<string,any>} zcfMintBaggage
*/
const provideDurableZcfMint = async zcfMintBaggage => {
const keyword = zcfMintBaggage.get('keyword');
const zoeMintP = zcfMintBaggage.get('zoeMintP');
const {
brand: mintyBrand,
issuer: mintyIssuer,
displayInfo: mintyDisplayInfo,
} = await E(zoeMintP).getIssuerRecord();
// AWAIT
const mintyIssuerRecord = makeIssuerRecord(
mintyBrand,
mintyIssuer,
mintyDisplayInfo,
);
recordIssuer(keyword, mintyIssuerRecord);

const empty = AmountMath.makeEmpty(mintyBrand, mintyDisplayInfo.assetKind);
const add = (total, amountToAdd) => {
return AmountMath.add(total, amountToAdd, mintyBrand);
};

return provideDurableSingleton(
zcfMintBaggage,
'zcfMint',
/** @type {ZCFMint} */
{
getIssuerRecord: _context => {
return mintyIssuerRecord;
},
mintGains: (_context, gains, zcfSeat = undefined) => {
gains = coerceAmountKeywordRecord(gains, getAssetKindByBrand);
if (zcfSeat === undefined) {
zcfSeat = makeEmptySeatKit().zcfSeat;
}
const totalToMint = Object.values(gains).reduce(add, empty);
assert(
// @ts-expect-error It's non-null
!zcfSeat.hasExited(),
`zcfSeat must be active to mint gains for the zcfSeat`,
);
const allocationPlusGains = addToAllocation(
// @ts-expect-error It's non-null
zcfSeat.getCurrentAllocation(),
gains,
);

// Increment the stagedAllocation if it exists so that the
// stagedAllocation is kept up to the currentAllocation
// @ts-expect-error It's non-null
if (zcfSeat.hasStagedAllocation()) {
// @ts-expect-error It's non-null
zcfSeat.incrementBy(gains);
}

// Offer safety should never be able to be violated here, as
// we are adding assets. However, we keep this check so that
// all reallocations are covered by offer safety checks, and
// that any bug within Zoe that may affect this is caught.
assert(
// @ts-expect-error It's non-null
zcfSeat.isOfferSafe(allocationPlusGains),
`The allocation after minting gains ${allocationPlusGains} for the zcfSeat was not offer safe`,
);
// No effects above, apart from incrementBy. Note COMMIT POINT within
// reallocateForZCFMint. The following two steps *should* be
// committed atomically, but it is not a disaster if they are
// not. If we minted only, no one would ever get those
// invisibly-minted assets.
E(zoeMintP).mintAndEscrow(totalToMint);
reallocateForZCFMint(zcfSeat, allocationPlusGains);
return zcfSeat;
},
burnLosses: (_context, losses, zcfSeat) => {
losses = coerceAmountKeywordRecord(losses, getAssetKindByBrand);
const totalToBurn = Object.values(losses).reduce(add, empty);
assert(
!zcfSeat.hasExited(),
`zcfSeat must be active to burn losses from the zcfSeat`,
);
const allocationMinusLosses = subtractFromAllocation(
zcfSeat.getCurrentAllocation(),
losses,
);

// verifies offer safety
assert(
zcfSeat.isOfferSafe(allocationMinusLosses),
`The allocation after burning losses ${allocationMinusLosses}for the zcfSeat was not offer safe`,
);

// Decrement the stagedAllocation if it exists so that the
// stagedAllocation is kept up to the currentAllocation
if (zcfSeat.hasStagedAllocation()) {
zcfSeat.decrementBy(losses);
}

// No effects above, apart from decrementBy. Note COMMIT POINT within
// reallocateForZCFMint. The following two steps *should* be
// committed atomically, but it is not a disaster if they are
// not. If we only commit the allocationMinusLosses no one would
// ever get the unburned assets.
reallocateForZCFMint(zcfSeat, allocationMinusLosses);
E(zoeMintP).withdrawAndBurn(totalToBurn);
},
},
);
};

const makeDurableZcfMint = async (keyword, zoeMintP, zcfMintBaggage) => {
zcfMintBaggage.init('keyword', keyword);
zcfMintBaggage.init('zoeMintP', zoeMintP);
return provideDurableZcfMint(zcfMintBaggage);
};

/**
* zcfMintFactory has a method makeZcfMint() that takes a keyword and the
* promise returned by a makeZoeMint() call. makeZcfMint() creates a new
* baggage for the state of the zcfMint, makes a durableZcfMint from that
* baggage, and registers that baggage to be revived with the factory.
*/
const zcfMintFactory = provideDurableSingleton(zcfBaggage, 'zcfMintFactory', {
makeZcfMint: (keyword, zoeMintP) => {
const zcfMintBaggage = makeScalarBigMapStore('zcfMintBaggage', {
durable: true,
});
const zcfMint = makeDurableZcfMint(keyword, zoeMintP, zcfMintBaggage);
zcfMintBaggageSet.add(zcfMint);
return zcfMint;
},
});

for (const zcfMintBaggage of zcfMintBaggageSet.values()) {
provideDurableZcfMint(zcfMintBaggage);
}

return zcfMintFactory;
};
harden(makeZCFMintFactory);
Loading

0 comments on commit bf46bdb

Please sign in to comment.