Skip to content

Commit

Permalink
refactor(liveslots): minor improvements to prepare for upcoming chang…
Browse files Browse the repository at this point in the history
…es (Agoric#8752)

* refactor(liveslots): minor improvements to prepare for upcoming changes

* fix: use ClassContextProvider from endo Agoric#1966

* fix: adapt to endo types pre and post endo Agoric#1966
  • Loading branch information
erights authored Jan 17, 2024
1 parent 6e90d96 commit 8ef62f4
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 46 deletions.
8 changes: 5 additions & 3 deletions packages/swingset-liveslots/src/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Fail } from '@agoric/assert';
* @template V
* @callback CacheGet
* @param {string} key
* @returns {V}
* @returns {V | undefined}
*/

/**
Expand All @@ -17,7 +17,7 @@ import { Fail } from '@agoric/assert';
/**
* @callback CacheDelete
* @param {string} key
* @returns {void}
* @returns {boolean}
*
* @callback CacheFlush
* @returns {void}
Expand Down Expand Up @@ -62,6 +62,7 @@ import { Fail } from '@agoric/assert';
export function makeCache(readBacking, writeBacking, deleteBacking) {
const stash = new Map();
const dirtyKeys = new Set();
/** @type {Cache<V>} */
const cache = {
get: key => {
assert.typeof(key, 'string');
Expand All @@ -82,8 +83,9 @@ export function makeCache(readBacking, writeBacking, deleteBacking) {
},
delete: key => {
assert.typeof(key, 'string');
stash.delete(key);
const result = stash.delete(key);
dirtyKeys.add(key);
return result;
},
flush: () => {
const keys = [...dirtyKeys.keys()];
Expand Down
10 changes: 8 additions & 2 deletions packages/swingset-liveslots/src/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,11 @@ export function makeCollectionManager(
const kindInfo = storeKindInfo[kindName];
kindInfo || Fail`unknown collection kind ${kindName}`;
const { hasWeakKeys, durable } = kindInfo;
const getSchema = () => schemaCache.get(collectionID);
const getSchema = () => {
const result = schemaCache.get(collectionID);
assert(result !== undefined);
return result;
};
const dbKeyPrefix = `vc.${collectionID}.`;
let currentGenerationNumber = 0;

Expand Down Expand Up @@ -749,7 +753,9 @@ export function makeCollectionManager(
const collection = summonCollectionInternal(false, collectionID, kindName);

let doMoreGC = collection.clearInternal(true);
const { schemataCapData } = schemaCache.get(collectionID);
const record = schemaCache.get(collectionID);
assert(record !== undefined);
const { schemataCapData } = record;
doMoreGC =
schemataCapData.slots.map(vrm.removeReachableVref).some(b => b) ||
doMoreGC;
Expand Down
4 changes: 2 additions & 2 deletions packages/swingset-liveslots/src/vatDataTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import type {
WeakMapStore,
WeakSetStore,
} from '@agoric/store';
import type { StateShape } from '@endo/exo';
import type { makeWatchedPromiseManager } from './watchedPromises.js';

// TODO should be moved into @endo/patterns and eventually imported here
// instead of this local definition.
export type InterfaceGuardKit = Record<string, InterfaceGuard>;

export type { MapStore, Pattern };

// This needs `any` values. If they were `unknown`, code that uses Baggage
Expand Down Expand Up @@ -88,7 +88,7 @@ export type DefineKindOptions<C> = {
* If provided, it describes the shape of all state records of instances
* of this kind.
*/
stateShape?: { [name: string]: Pattern };
stateShape?: StateShape;

/**
* Intended for internal use only.
Expand Down
124 changes: 85 additions & 39 deletions packages/swingset-liveslots/src/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,32 @@ import {

/** @template T @typedef {import('@agoric/vat-data').DefineKindOptions<T>} DefineKindOptions */

const { hasOwn, defineProperty, getOwnPropertyNames, entries } = Object;
/**
* @typedef {(
* representative: any
* ) => import('@endo/exo/src/exo-tools.js').ClassContext | undefined} ClassContextProvider
* Definition only temporarily copied from exo-tools.js to here.
* TODO Once agoric-sdk is upgraded to depend on an `@endo/exo` incorporating
* https://github.com/endojs/endo/pull/1966
* then replace with the following (fixing the implied "@")
*
* at-typedef {import('@endo/exo/src/exo-tools.js').ClassContextProvider } ClassContextProvider
*
* @typedef {(facet: any) => import('@endo/exo/src/exo-tools.js').KitContext | undefined} KitContextProvider
* Definition only temporarily copied from exo-tools.js to here.
* TODO Once agoric-sdk is upgraded to depend on an `@endo/exo` incorporating
* https://github.com/endojs/endo/pull/1966
* then replace with the following (fixing the implied "@")
*
* at-typedef {import('@endo/exo/src/exo-tools.js').KitContextProvider } KitContextProvider
*/

/**
*
*/

const { hasOwn, defineProperty, getOwnPropertyNames, entries, fromEntries } =
Object;
const { ownKeys } = Reflect;
const { quote: q } = assert;

Expand Down Expand Up @@ -141,39 +166,6 @@ const makeContextCache = (makeState, makeContext) => {
return makeCache(readBacking, writeBacking, deleteBacking);
};

/**
* @typedef {import('@endo/exo/src/exo-tools.js').ContextProvider } ContextProvider
*/

/**
* @param {*} contextCache
* @param {*} getSlotForVal
* @returns {ContextProvider}
*/
const makeContextProvider = (contextCache, getSlotForVal) =>
harden(rep => contextCache.get(getSlotForVal(rep)));

const makeContextProviderKit = (contextCache, getSlotForVal, facetNames) => {
/** @type { Record<string, any> } */
const contextProviderKit = {};
for (const [index, name] of facetNames.entries()) {
contextProviderKit[name] = rep => {
const vref = getSlotForVal(rep);
const { baseRef, facet } = parseVatSlot(vref);

// Without this check, an attacker (with access to both cohort1.facetA
// and cohort2.facetB) could effectively forge access to cohort1.facetB
// and cohort2.facetA. They could not forge the identity of those two
// objects, but they could invoke all their equivalent methods, by using
// e.g. cohort1.facetA.foo.apply(cohort2.facetB, [...args])
Number(facet) === index || Fail`illegal cross-facet access`;

return harden(contextCache.get(baseRef));
};
}
return harden(contextProviderKit);
};

// The management of single Representatives (i.e. defineKind) is very similar
// to that of a cohort of facets (i.e. defineKindMulti). In this description,
// we use "self/facets" to refer to either 'self' or 'facets', as appropriate
Expand Down Expand Up @@ -891,7 +883,9 @@ export const makeVirtualObjectManager = (
return harden({
get() {
const baseRef = getBaseRef(this);
const { valueMap, capdatas } = dataCache.get(baseRef);
const record = dataCache.get(baseRef);
assert(record !== undefined);
const { valueMap, capdatas } = record;
if (!valueMap.has(prop)) {
const value = harden(unserialize(capdatas[prop]));
checkStatePropertyValue(value, prop);
Expand All @@ -908,6 +902,7 @@ export const makeVirtualObjectManager = (
insistDurableCapdata(vrm, prop, capdata, true);
}
const record = dataCache.get(baseRef); // mutable
assert(record !== undefined);
const oldSlots = record.capdatas[prop].slots;
const newSlots = capdata.slots;
vrm.updateReferenceCounts(oldSlots, newSlots);
Expand All @@ -931,7 +926,9 @@ export const makeVirtualObjectManager = (
const makeState = baseRef => {
const state = { __proto__: statePrototype };
if (stateShape === undefined) {
for (const prop of ownKeys(dataCache.get(baseRef).capdatas)) {
const record = dataCache.get(baseRef);
assert(record !== undefined);
for (const prop of ownKeys(record.capdatas)) {
assert(typeof prop === 'string');
checkStateProperty(prop);
defineProperty(state, prop, makeFieldDescriptor(prop));
Expand Down Expand Up @@ -982,17 +979,65 @@ export const makeVirtualObjectManager = (

let proto;
if (multifaceted) {
/** @type { Record<string, KitContextProvider> } */
const contextProviderKit = fromEntries(
facetNames.map((name, index) => [
name,
rep => {
const vref = getSlotForVal(rep);
assert(vref !== undefined);
const { baseRef, facet } = parseVatSlot(vref);

// Without this check, an attacker (with access to both
// cohort1.facetA and cohort2.facetB)
// could effectively forge access to
// cohort1.facetB and cohort2.facetA.
// They could not forge the identity of those two
// objects, but they could invoke all their equivalent methods,
// by using e.g.
// cohort1.facetA.foo.apply(cohort2.facetB, [...args])
Number(facet) === index || Fail`illegal cross-facet access`;

return harden(contextCache.get(baseRef));
},
]),
);

proto = defendPrototypeKit(
tag,
makeContextProviderKit(contextCache, getSlotForVal, facetNames),
// TODO Once agoric-sdk is upgraded to depend on an `@endo/exo`
// incorporating https://github.com/endojs/endo/pull/1966
// Then the following at-ts-ignore will no longer be needed.
// However, it is an at-ts-ignore rather than an
// at-ts-expect-error to be compat with endo both before and
// after, until we're safely across the transition.
//
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore
harden(contextProviderKit),
behavior,
thisfulMethods,
interfaceGuardKit,
);
} else {
/** @type {ClassContextProvider} */
const contextProvider = rep => {
const slot = getSlotForVal(rep);
assert(slot !== undefined);
return harden(contextCache.get(slot));
};
proto = defendPrototype(
tag,
makeContextProvider(contextCache, getSlotForVal),
// TODO Once agoric-sdk is upgraded to depend on an `@endo/exo`
// incorporating https://github.com/endojs/endo/pull/1966
// Then the following at-ts-ignore will no longer be needed.
// However, it is an at-ts-ignore rather than an
// at-ts-expect-error to be compat with endo both before and
// after, until we're safely across the transition.
//
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore
harden(contextProvider),
behavior,
thisfulMethods,
interfaceGuard,
Expand All @@ -1014,6 +1059,7 @@ export const makeVirtualObjectManager = (
const deleteStoredVO = baseRef => {
let doMoreGC = false;
const record = dataCache.get(baseRef);
assert(record !== undefined);
for (const valueCD of Object.values(record.capdatas)) {
for (const vref of valueCD.slots) {
doMoreGC = vrm.removeReachableVref(vref) || doMoreGC;
Expand Down Expand Up @@ -1072,7 +1118,7 @@ export const makeVirtualObjectManager = (
val = makeRepresentative(proto, baseRef);
}
registerValue(baseRef, val, multifaceted);
finish?.(contextCache.get(baseRef));
finish && finish(contextCache.get(baseRef));
return val;
};

Expand Down

0 comments on commit 8ef62f4

Please sign in to comment.