Skip to content

Commit

Permalink
fix: stateShape (#6429)
Browse files Browse the repository at this point in the history
  • Loading branch information
erights authored Oct 9, 2022
1 parent 241fb5f commit 259ec5e
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 41 deletions.
9 changes: 9 additions & 0 deletions packages/ERTP/src/purse.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { M } from '@agoric/store';
import { vivifyFarClassKit, makeScalarBigSetStore } from '@agoric/vat-data';
import { AmountMath } from './amountMath.js';
import { makeTransientNotifierKit } from './transientNotifier.js';
Expand All @@ -12,6 +13,8 @@ export const vivifyPurseKind = (
PurseIKit,
purseMethods,
) => {
const amountShape = brand.getAmountShape();

// Note: Virtual for high cardinality, but *not* durable, and so
// broken across an upgrade.
const { provideNotifier, update: updateBalance } = makeTransientNotifierKit();
Expand Down Expand Up @@ -111,6 +114,12 @@ export const vivifyPurseKind = (
},
},
},
{
stateShape: {
currentBalance: amountShape,
recoverySet: M.remotable('recoverySet'),
},
},
);
return () => makePurseKit().purse;
};
Expand Down
97 changes: 71 additions & 26 deletions packages/SwingSet/src/liveslots/virtualObjectManager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// @ts-check
/* eslint-disable no-use-before-define, jsdoc/require-returns-type */
/* eslint-disable no-use-before-define */

import { assert, details as X, q } from '@agoric/assert';
import { defendPrototype } from '@agoric/store';
import { Far } from '@endo/marshal';
import {
assertPattern,
decompress,
defendPrototype,
mustCompress,
} from '@agoric/store';
import { Far, hasOwnPropertyOf, passStyleOf } from '@endo/marshal';
import { parseVatSlot } from '../lib/parseVatSlots.js';

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

const { ownKeys } = Reflect;
const { details: X, quote: q } = assert;

// import { kdebug } from './kdebug.js';

// Marker associated to flag objects that should be held onto strongly if
Expand All @@ -28,7 +35,7 @@ const unweakable = new WeakSet();
* @param {(baseRef: string, rawState: object) => void} store Function to
* store raw object state by its baseRef
*
* @returns An LRU cache of (up to) the given size
* @returns {object} An LRU cache of (up to) the given size
*
* This cache is part of the virtual object manager and is not intended to be
* used independently; it is exported only for the benefit of test code.
Expand Down Expand Up @@ -146,24 +153,28 @@ export function makeCache(size, fetch, store) {
/**
* Create a new virtual object manager. There is one of these for each vat.
*
* @param {*} syscall Vat's syscall object, used to access the vatstore operations.
* @param {*} vrm Virtual reference manager, to handle reference counting and GC
* of virtual references.
* @param {() => number} allocateExportID Function to allocate the next object
* export ID for the enclosing vat.
* @param {(val: object) => string} _getSlotForVal A function that returns the
* object ID (vref) for a given object, if any. their corresponding export
* IDs
* @param {*} registerValue Function to register a new slot+value in liveSlot's
* various tables
* @param {import('@endo/marshal').Serialize<unknown>} serialize Serializer for this vat
* @param {import('@endo/marshal').Unserialize<unknown>} unserialize Unserializer for this vat
* @param {number} cacheSize How many virtual objects this manager should cache
* in memory.
* @param {*} assertAcceptableSyscallCapdataSize Function to check for oversized
* syscall params
* @param {*} syscall
* Vat's syscall object, used to access the vatstore operations.
* @param {*} vrm
* Virtual reference manager, to handle reference counting and GC
* of virtual references.
* @param {() => number} allocateExportID
* Function to allocate the next object export ID for the enclosing vat.
* @param {(val: object) => string} _getSlotForVal
* A function that returns the object ID (vref) for a given object, if any.
* their corresponding export IDs
* @param {*} registerValue
* Function to register a new slot+value in liveSlot's various tables
* @param {import('@endo/marshal').Serialize<unknown>} serialize
* Serializer for this vat
* @param {import('@endo/marshal').Unserialize<unknown>} unserialize
* Unserializer for this vat
* @param {number} cacheSize
* How many virtual objects this manager should cache in memory.
* @param {*} assertAcceptableSyscallCapdataSize
* Function to check for oversized syscall params
*
* @returns a new virtual object manager.
* @returns {object} a new virtual object manager.
*
* The virtual object manager allows the creation of persistent objects that do
* not need to occupy memory when they are not in use. It provides five
Expand Down Expand Up @@ -585,13 +596,46 @@ export function makeVirtualObjectManager(
) {
const {
finish,
stateShape = undefined,
thisfulMethods = false,
interfaceGuard = undefined,
} = options;
let facetNames;
let contextMapTemplate;
let prototypeTemplate;

harden(stateShape);
stateShape === undefined ||
passStyleOf(stateShape) === 'copyRecord' ||
assert.fail(X`A stateShape must be a copyRecord: ${q(stateShape)}`);
assertPattern(stateShape);

const serializeSlot = (slotState, prop) => {
if (stateShape === undefined) {
return serialize(slotState);
}
hasOwnPropertyOf(stateShape, prop) ||
assert.fail(
X`State must only have fields described by stateShape: ${q(
ownKeys(stateShape),
)}`,
);
return serialize(mustCompress(slotState, stateShape[prop], prop));
};

const unserializeSlot = (slotData, prop) => {
if (stateShape === undefined) {
return unserialize(slotData);
}
hasOwnPropertyOf(stateShape, prop) ||
assert.fail(
X`State only has fields described by stateShape: ${q(
ownKeys(stateShape),
)}`,
);
return decompress(unserialize(slotData), stateShape[prop]);
};

const facetiousness = assessFacetiousness(behavior);
switch (facetiousness) {
case 'one': {
Expand Down Expand Up @@ -695,12 +739,12 @@ export function makeVirtualObjectManager(
Object.defineProperty(state, prop, {
get: () => {
ensureState();
return unserialize(innerSelf.rawState[prop]);
return unserializeSlot(innerSelf.rawState[prop], prop);
},
set: value => {
ensureState();
const before = innerSelf.rawState[prop];
const after = serialize(value);
const after = serializeSlot(value, prop);
assertAcceptableSyscallCapdataSize([after]);
if (isDurable) {
after.slots.forEach((vref, index) => {
Expand Down Expand Up @@ -793,11 +837,12 @@ export function makeVirtualObjectManager(
const initialData = init ? init(...args) : {};
const rawState = {};
for (const prop of Object.getOwnPropertyNames(initialData)) {
const data = serialize(initialData[prop]);
const data = serializeSlot(initialData[prop], prop);
assertAcceptableSyscallCapdataSize([data]);
if (isDurable) {
data.slots.forEach(vref => {
assert(vrm.isDurable(vref), X`value for ${q(prop)} is not durable`);
vrm.isDurable(vref) ||
assert.fail(X`value for ${q(prop)} is not durable`);
});
}
data.slots.forEach(vrm.addReachableVref);
Expand Down
46 changes: 31 additions & 15 deletions packages/store/src/patterns/interface-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,27 @@ export const initEmpty = () => emptyRecord;
* @property {T} self
*/

/**
* @template [S = any]
* @template [F = any]
* @typedef {object} KitContext
* @property {S} state
* @property {F} facets
*/

/**
* @typedef {{[name: string]: Pattern}} StateShape
* It looks like a copyRecord pattern, but the interpretation is different.
* Each property is distinct, is checked and changed separately.
*/

/**
* @template C
* @typedef {object} FarClassOptions
* @property {(context: C) => void} [finish]
* @property {StateShape} [stateShape]
*/

/**
* @template A
* @template S
Expand All @@ -288,15 +309,15 @@ export const initEmpty = () => emptyRecord;
* @param {any} interfaceGuard
* @param {(...args: A[]) => S} init
* @param {T} methods
* @param {object} [options]
* @param {FarClassOptions<Context<S,T>>} [options]
* @returns {(...args: A[]) => (T & import('@endo/eventual-send').RemotableBrand<{}, T>)}
*/
export const defineHeapFarClass = (
tag,
interfaceGuard,
init,
methods,
options = undefined,
{ finish = undefined } = {},
) => {
/** @type {WeakMap<T,Context<S, T>>} */
const contextMap = new WeakMap();
Expand All @@ -317,11 +338,8 @@ export const defineHeapFarClass = (
/** @type {Context<S,T>} */
const context = freeze({ state, self });
contextMap.set(self, context);
if (options) {
const { finish = undefined } = options;
if (finish) {
finish(context);
}
if (finish) {
finish(context);
}
return self;
};
Expand All @@ -338,15 +356,15 @@ harden(defineHeapFarClass);
* @param {any} interfaceGuardKit
* @param {(...args: A[]) => S} init
* @param {F} methodsKit
* @param {object} [options]
* @param {FarClassOptions<KitContext<S,F>>} [options]
* @returns {(...args: A[]) => F}
*/
export const defineHeapFarClassKit = (
tag,
interfaceGuardKit,
init,
methodsKit,
options = undefined,
{ finish = undefined } = {},
) => {
const facetNames = ownKeys(methodsKit);
const interfaceNames = ownKeys(interfaceGuardKit);
Expand Down Expand Up @@ -386,11 +404,9 @@ export const defineHeapFarClassKit = (
context.facets = facets;
// Be careful not to freeze the state record
freeze(context);
if (options) {
const { finish = undefined } = options;
if (finish) {
finish(context);
}
if (finish) {
// @ts-expect-error `facets` was added
finish(context);
}
return facets;
};
Expand All @@ -405,7 +421,7 @@ harden(defineHeapFarClassKit);
* @param {string} tag
* @param {InterfaceGuard | undefined} interfaceGuard CAVEAT: static typing does not yet support `callWhen` transformation
* @param {T} methods
* @param {object} [options]
* @param {FarClassOptions<Context<{},T>>} [options]
* @returns {T & import('@endo/eventual-send').RemotableBrand<{}, T>}
*/
export const makeHeapFarInstance = (
Expand Down
6 changes: 6 additions & 0 deletions packages/vat-data/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ type DefineKindOptions<C> = {
*/
durable?: boolean;

/**
* If provided, it describes the shape of all state records of instances
* of this kind.
*/
stateShape?: { [name: string]: Pattern };

/**
* Intended for internal use only.
* Should the raw methods receive their `context` argument as their first
Expand Down

0 comments on commit 259ec5e

Please sign in to comment.