Skip to content

Commit

Permalink
9449 membrane types (#9685)
Browse files Browse the repository at this point in the history
closes: #9449

## Description

Address all the FIXMEs about types and vows in Orchestration. To do so I moved some stuff down to async-flow and zoe packages. I also made some small fixes in smart-wallet. 

I believe this ticks the last box of #9449. 

### Security Considerations
none

### Scaling Considerations
none

### Documentation Considerations
none

### Testing Considerations
This adds type tests and I used `type-coverage` to verify no regressions.

### Upgrade Considerations
orchestration and async-flow not yet deployed.

The changes in zoe and smart-wallet are just typedefs so safe to deploy anytime.
  • Loading branch information
mergify[bot] authored Jul 11, 2024
2 parents 12ce494 + 6b41829 commit 3915e4c
Show file tree
Hide file tree
Showing 37 changed files with 444 additions and 452 deletions.
1 change: 1 addition & 0 deletions packages/async-flow/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './src/async-flow.js';
export * from './src/types.js';
export { makeStateRecord } from './src/endowments.js';
7 changes: 4 additions & 3 deletions packages/async-flow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
"@agoric/internal": "^0.3.2",
"@agoric/store": "^0.9.2",
"@agoric/vow": "^0.1.0",
"@endo/pass-style": "^1.4.0",
"@endo/common": "^1.2.2",
"@endo/errors": "^1.2.2",
"@endo/eventual-send": "^1.2.2",
"@endo/marshal": "^1.5.0",
"@endo/pass-style": "^1.4.0",
"@endo/patterns": "^1.4.0",
"@endo/promise-kit": "^1.1.2"
},
Expand All @@ -41,7 +41,8 @@
"@agoric/zone": "^0.2.2",
"@endo/env-options": "^1.1.4",
"@endo/ses-ava": "^1.2.2",
"ava": "^5.3.0"
"ava": "^5.3.0",
"tsd": "^0.31.1"
},
"publishConfig": {
"access": "public"
Expand All @@ -60,6 +61,6 @@
"workerThreads": false
},
"typeCoverage": {
"atLeast": 77.83
"atLeast": 77.32
}
}
72 changes: 6 additions & 66 deletions packages/async-flow/src/replay-membrane.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
/* eslint-disable no-use-before-define */
import { isVow } from '@agoric/vow/src/vow-utils.js';
import { heapVowE } from '@agoric/vow/vat.js';
import { throwLabeled } from '@endo/common/throw-labeled.js';
import { Fail, X, b, makeError, q } from '@endo/errors';
import { E } from '@endo/eventual-send';
import { getMethodNames } from '@endo/eventual-send/utils.js';
import { throwLabeled } from '@endo/common/throw-labeled.js';
import { objectMap } from '@endo/common/object-map.js';
import {
Far,
Remotable,
getInterfaceOf,
getTag,
makeTagged,
passStyleOf,
} from '@endo/pass-style';
import { heapVowE } from '@agoric/vow/vat.js';
import { isVow } from '@agoric/vow/src/vow-utils.js';
import { makeEquate } from './equate.js';
import { Far, Remotable, getInterfaceOf } from '@endo/pass-style';
import { makeConvertKit } from './convert.js';
import { makeEquate } from './equate.js';

/**
* @import {PromiseKit} from '@endo/promise-kit'
Expand Down Expand Up @@ -43,7 +35,7 @@ export const makeReplayMembrane = ({
watchWake,
panic,
}) => {
const { when, watch, makeVowKit } = vowTools;
const { when, makeVowKit } = vowTools;

const equate = makeEquate(bijection);

Expand Down Expand Up @@ -137,63 +129,12 @@ export const makeReplayMembrane = ({

// ///////////// Guest to Host or consume log ////////////////////////////////

/**
* The host is not supposed to expose host-side promises to the membrane,
* since they cannot be stored durably or survive upgrade. We cannot just
* automatically wrap any such host promises with host vows, because that
* would mask upgrade hazards if an upgrade happens before the vow settles.
* However, during the transition, the current host APIs called by
* orchestration still return many promises. We want to generate diagnostics
* when we encounter them, but for now, automatically convert them to
* host vow anyway, just so integration testing can proceed to reveal
* additional problems beyond these.
*
* @param {Passable} h
*/
const tolerateHostPromiseToVow = h => {
const passStyle = passStyleOf(h);
switch (passStyle) {
case 'promise': {
const e = Error('where warning happened');
console.log('Warning for now: vow expected, not promise', h, e);
// TODO remove this stopgap. Here for now because host-side
// promises are everywhere!
// Note: A good place to set a breakpoint, or to uncomment the
// `debugger;` line, to work around bundling.
// debugger;
return watch(h);
}
case 'copyRecord': {
const o = /** @type {object} */ (h);
return objectMap(o, tolerateHostPromiseToVow);
}
case 'copyArray': {
const a = /** @type {Array} */ (h);
return harden(a.map(tolerateHostPromiseToVow));
}
case 'tagged': {
const t = /** @type {CopyTagged} */ (h);
if (isVow(t)) {
return h;
}
return makeTagged(getTag(t), tolerateHostPromiseToVow(t.payload));
}
default: {
return h;
}
}
};

const performCall = (hostTarget, optVerb, hostArgs, callIndex) => {
let hostResult;
try {
hostResult = optVerb
? hostTarget[optVerb](...hostArgs)
: hostTarget(...hostArgs);
// This is a temporary kludge anyway. But note that it only
// catches the case where the promise is at the top of hostResult.
harden(hostResult);
hostResult = tolerateHostPromiseToVow(hostResult);
// Try converting here just to route the error correctly
hostToGuest(hostResult, `converting ${optVerb || 'host'} result`);
} catch (hostProblem) {
Expand Down Expand Up @@ -575,7 +516,6 @@ export const makeReplayMembrane = ({
* @returns {Promise}
*/
const makeGuestForHostVow = (hVow, promiseKey = undefined) => {
hVow = tolerateHostPromiseToVow(hVow);
isVow(hVow) || Fail`vow expected ${hVow}`;
const { promise, resolve, reject } = makeGuestPromiseKit();
promiseKey ??= promise;
Expand Down
191 changes: 191 additions & 0 deletions packages/async-flow/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import type { Passable } from '@endo/pass-style';
import type { Vow, VowTools } from '@agoric/vow';
import type { LogStore } from './log-store.js';
import type { Bijection } from './bijection.js';
import type { EndowmentTools } from './endowments.js';

export type FlowState =
| 'Running'
| 'Sleeping'
| 'Replaying'
| 'Failed'
| 'Done';

/**
* `T` defaults to `any`, not `Passable`, because unwrapped guests include
* non-passables, like unwrapped functions and unwrapped state records.
* (Unwrapped functions could be made into Remotables,
* but since they still could not be made durable, in this context
* it'd be pointless.)
*/
export type Guest<T extends unknown = any> = T;
export type Host<T extends Passable = Passable> = T;

/**
* A HostVow must be durably storable. It corresponds to an
* ephemeral guest promise.
*/
export type HostVow<T extends Passable = Passable> = Host<Vow<T>>;

export type GuestAsyncFunc = (
...activationArgs: Guest[]
) => Guest<Promise<any>>;

export type HostAsyncFuncWrapper = (...activationArgs: Host[]) => HostVow;

/**
* The function from the host as it will be available in the guest.
*
* Specifically, Vow return values are converted to Promises.
*/
export type GuestOf<F extends HostAsyncFuncWrapper> = F extends (
...args: infer A
) => Vow<infer R>
? (...args: A) => Promise<R>
: F;

/**
* Convert an entire Guest interface into what the host will implement.
*/
type HostInterface<T> = {
[K in keyof T]: HostOf<T[K]>;
};

/**
* The function the host must provide to match an interface the guest expects.
*
* Specifically, Promise return values are converted to Vows.
*/
export type HostOf<F> = F extends (...args: infer A) => Promise<infer R>
? (...args: A) => Vow<R extends Passable ? R : HostInterface<R>>
: F;

export type PreparationOptions = {
vowTools?: VowTools;
makeLogStore?: (() => LogStore) | undefined;
makeBijection?: (() => Bijection) | undefined;
endowmentTools?: EndowmentTools;
};
export type OutcomeKind = 'return' | 'throw';

export type Outcome =
| {
kind: 'return';
result: any;
}
| {
kind: 'throw';
problem: any;
};

export type Ephemera<S extends WeakKey = WeakKey, V extends unknown = any> = {
for: (self: S) => V;
resetFor: (self: S) => void;
};

/**
* This is the type alias for the membrane log entries we currently implement.
*
* @see {FutureLogEntry} below for the full membrane log entry, which we do not
* yet support.
*/
export type LogEntry =
| [
// ///////////////// From Host to Guest /////////////////////////
op: 'doFulfill',
vow: HostVow,
fulfillment: Host,
]
| [op: 'doReject', vow: HostVow, reason: Host]
| [op: 'doReturn', callIndex: number, result: Host]
| [op: 'doThrow', callIndex: number, problem: Host]
| [
// ///////////////////// From Guest to Host /////////////////////////
op: 'checkCall',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [
op: 'checkSendOnly',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [
op: 'checkSend',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
];

/**
* This would be the type alias for the full membrane log, if we supported:
* - the guest sending guest-promises and guest-remotables to the host
* - the guest using `E` to eventual-send to guest wrappers of the host
* vows and remotables.
*/
export type FutureLogEntry =
| [
// ///////////////// From Host to Guest ///////////////////////
op: 'doFulfill',
vow: HostVow,
fulfillment: Host,
]
| [op: 'doReject', vow: HostVow, reason: Host]
| [
op: 'doCall',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [
op: 'doSendOnly',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [
op: 'doSend',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [op: 'doReturn', callIndex: number, result: Host]
| [op: 'doThrow', callIndex: number, problem: Host]
| [
// ///////////////////// From Guest to Host /////////////////////////
op: 'checkFulfill',
vow: HostVow,
fulfillment: Host,
]
| [op: 'checkReject', vow: HostVow, reason: Host]
| [
op: 'checkCall',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [
op: 'checkSendOnly',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [
op: 'checkSend',
target: Host,
optVerb: PropertyKey | undefined,
args: Host[],
callIndex: number,
]
| [op: 'checkReturn', callIndex: number, result: Host]
| [op: 'checkThrow', callIndex: number, problem: Host];
Loading

0 comments on commit 3915e4c

Please sign in to comment.