Skip to content

Commit 3915e4c

Browse files
authored
9449 membrane types (#9685)
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.
2 parents 12ce494 + 6b41829 commit 3915e4c

37 files changed

Lines changed: 444 additions & 452 deletions

packages/async-flow/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './src/async-flow.js';
2+
export * from './src/types.js';
23
export { makeStateRecord } from './src/endowments.js';

packages/async-flow/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
"@agoric/internal": "^0.3.2",
2929
"@agoric/store": "^0.9.2",
3030
"@agoric/vow": "^0.1.0",
31-
"@endo/pass-style": "^1.4.0",
3231
"@endo/common": "^1.2.2",
3332
"@endo/errors": "^1.2.2",
3433
"@endo/eventual-send": "^1.2.2",
3534
"@endo/marshal": "^1.5.0",
35+
"@endo/pass-style": "^1.4.0",
3636
"@endo/patterns": "^1.4.0",
3737
"@endo/promise-kit": "^1.1.2"
3838
},
@@ -41,7 +41,8 @@
4141
"@agoric/zone": "^0.2.2",
4242
"@endo/env-options": "^1.1.4",
4343
"@endo/ses-ava": "^1.2.2",
44-
"ava": "^5.3.0"
44+
"ava": "^5.3.0",
45+
"tsd": "^0.31.1"
4546
},
4647
"publishConfig": {
4748
"access": "public"
@@ -60,6 +61,6 @@
6061
"workerThreads": false
6162
},
6263
"typeCoverage": {
63-
"atLeast": 77.83
64+
"atLeast": 77.32
6465
}
6566
}

packages/async-flow/src/replay-membrane.js

Lines changed: 6 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
/* eslint-disable no-use-before-define */
2+
import { isVow } from '@agoric/vow/src/vow-utils.js';
3+
import { heapVowE } from '@agoric/vow/vat.js';
4+
import { throwLabeled } from '@endo/common/throw-labeled.js';
25
import { Fail, X, b, makeError, q } from '@endo/errors';
36
import { E } from '@endo/eventual-send';
47
import { getMethodNames } from '@endo/eventual-send/utils.js';
5-
import { throwLabeled } from '@endo/common/throw-labeled.js';
6-
import { objectMap } from '@endo/common/object-map.js';
7-
import {
8-
Far,
9-
Remotable,
10-
getInterfaceOf,
11-
getTag,
12-
makeTagged,
13-
passStyleOf,
14-
} from '@endo/pass-style';
15-
import { heapVowE } from '@agoric/vow/vat.js';
16-
import { isVow } from '@agoric/vow/src/vow-utils.js';
17-
import { makeEquate } from './equate.js';
8+
import { Far, Remotable, getInterfaceOf } from '@endo/pass-style';
189
import { makeConvertKit } from './convert.js';
10+
import { makeEquate } from './equate.js';
1911

2012
/**
2113
* @import {PromiseKit} from '@endo/promise-kit'
@@ -43,7 +35,7 @@ export const makeReplayMembrane = ({
4335
watchWake,
4436
panic,
4537
}) => {
46-
const { when, watch, makeVowKit } = vowTools;
38+
const { when, makeVowKit } = vowTools;
4739

4840
const equate = makeEquate(bijection);
4941

@@ -137,63 +129,12 @@ export const makeReplayMembrane = ({
137129

138130
// ///////////// Guest to Host or consume log ////////////////////////////////
139131

140-
/**
141-
* The host is not supposed to expose host-side promises to the membrane,
142-
* since they cannot be stored durably or survive upgrade. We cannot just
143-
* automatically wrap any such host promises with host vows, because that
144-
* would mask upgrade hazards if an upgrade happens before the vow settles.
145-
* However, during the transition, the current host APIs called by
146-
* orchestration still return many promises. We want to generate diagnostics
147-
* when we encounter them, but for now, automatically convert them to
148-
* host vow anyway, just so integration testing can proceed to reveal
149-
* additional problems beyond these.
150-
*
151-
* @param {Passable} h
152-
*/
153-
const tolerateHostPromiseToVow = h => {
154-
const passStyle = passStyleOf(h);
155-
switch (passStyle) {
156-
case 'promise': {
157-
const e = Error('where warning happened');
158-
console.log('Warning for now: vow expected, not promise', h, e);
159-
// TODO remove this stopgap. Here for now because host-side
160-
// promises are everywhere!
161-
// Note: A good place to set a breakpoint, or to uncomment the
162-
// `debugger;` line, to work around bundling.
163-
// debugger;
164-
return watch(h);
165-
}
166-
case 'copyRecord': {
167-
const o = /** @type {object} */ (h);
168-
return objectMap(o, tolerateHostPromiseToVow);
169-
}
170-
case 'copyArray': {
171-
const a = /** @type {Array} */ (h);
172-
return harden(a.map(tolerateHostPromiseToVow));
173-
}
174-
case 'tagged': {
175-
const t = /** @type {CopyTagged} */ (h);
176-
if (isVow(t)) {
177-
return h;
178-
}
179-
return makeTagged(getTag(t), tolerateHostPromiseToVow(t.payload));
180-
}
181-
default: {
182-
return h;
183-
}
184-
}
185-
};
186-
187132
const performCall = (hostTarget, optVerb, hostArgs, callIndex) => {
188133
let hostResult;
189134
try {
190135
hostResult = optVerb
191136
? hostTarget[optVerb](...hostArgs)
192137
: hostTarget(...hostArgs);
193-
// This is a temporary kludge anyway. But note that it only
194-
// catches the case where the promise is at the top of hostResult.
195-
harden(hostResult);
196-
hostResult = tolerateHostPromiseToVow(hostResult);
197138
// Try converting here just to route the error correctly
198139
hostToGuest(hostResult, `converting ${optVerb || 'host'} result`);
199140
} catch (hostProblem) {
@@ -575,7 +516,6 @@ export const makeReplayMembrane = ({
575516
* @returns {Promise}
576517
*/
577518
const makeGuestForHostVow = (hVow, promiseKey = undefined) => {
578-
hVow = tolerateHostPromiseToVow(hVow);
579519
isVow(hVow) || Fail`vow expected ${hVow}`;
580520
const { promise, resolve, reject } = makeGuestPromiseKit();
581521
promiseKey ??= promise;

packages/async-flow/src/types.d.ts

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import type { Passable } from '@endo/pass-style';
2+
import type { Vow, VowTools } from '@agoric/vow';
3+
import type { LogStore } from './log-store.js';
4+
import type { Bijection } from './bijection.js';
5+
import type { EndowmentTools } from './endowments.js';
6+
7+
export type FlowState =
8+
| 'Running'
9+
| 'Sleeping'
10+
| 'Replaying'
11+
| 'Failed'
12+
| 'Done';
13+
14+
/**
15+
* `T` defaults to `any`, not `Passable`, because unwrapped guests include
16+
* non-passables, like unwrapped functions and unwrapped state records.
17+
* (Unwrapped functions could be made into Remotables,
18+
* but since they still could not be made durable, in this context
19+
* it'd be pointless.)
20+
*/
21+
export type Guest<T extends unknown = any> = T;
22+
export type Host<T extends Passable = Passable> = T;
23+
24+
/**
25+
* A HostVow must be durably storable. It corresponds to an
26+
* ephemeral guest promise.
27+
*/
28+
export type HostVow<T extends Passable = Passable> = Host<Vow<T>>;
29+
30+
export type GuestAsyncFunc = (
31+
...activationArgs: Guest[]
32+
) => Guest<Promise<any>>;
33+
34+
export type HostAsyncFuncWrapper = (...activationArgs: Host[]) => HostVow;
35+
36+
/**
37+
* The function from the host as it will be available in the guest.
38+
*
39+
* Specifically, Vow return values are converted to Promises.
40+
*/
41+
export type GuestOf<F extends HostAsyncFuncWrapper> = F extends (
42+
...args: infer A
43+
) => Vow<infer R>
44+
? (...args: A) => Promise<R>
45+
: F;
46+
47+
/**
48+
* Convert an entire Guest interface into what the host will implement.
49+
*/
50+
type HostInterface<T> = {
51+
[K in keyof T]: HostOf<T[K]>;
52+
};
53+
54+
/**
55+
* The function the host must provide to match an interface the guest expects.
56+
*
57+
* Specifically, Promise return values are converted to Vows.
58+
*/
59+
export type HostOf<F> = F extends (...args: infer A) => Promise<infer R>
60+
? (...args: A) => Vow<R extends Passable ? R : HostInterface<R>>
61+
: F;
62+
63+
export type PreparationOptions = {
64+
vowTools?: VowTools;
65+
makeLogStore?: (() => LogStore) | undefined;
66+
makeBijection?: (() => Bijection) | undefined;
67+
endowmentTools?: EndowmentTools;
68+
};
69+
export type OutcomeKind = 'return' | 'throw';
70+
71+
export type Outcome =
72+
| {
73+
kind: 'return';
74+
result: any;
75+
}
76+
| {
77+
kind: 'throw';
78+
problem: any;
79+
};
80+
81+
export type Ephemera<S extends WeakKey = WeakKey, V extends unknown = any> = {
82+
for: (self: S) => V;
83+
resetFor: (self: S) => void;
84+
};
85+
86+
/**
87+
* This is the type alias for the membrane log entries we currently implement.
88+
*
89+
* @see {FutureLogEntry} below for the full membrane log entry, which we do not
90+
* yet support.
91+
*/
92+
export type LogEntry =
93+
| [
94+
// ///////////////// From Host to Guest /////////////////////////
95+
op: 'doFulfill',
96+
vow: HostVow,
97+
fulfillment: Host,
98+
]
99+
| [op: 'doReject', vow: HostVow, reason: Host]
100+
| [op: 'doReturn', callIndex: number, result: Host]
101+
| [op: 'doThrow', callIndex: number, problem: Host]
102+
| [
103+
// ///////////////////// From Guest to Host /////////////////////////
104+
op: 'checkCall',
105+
target: Host,
106+
optVerb: PropertyKey | undefined,
107+
args: Host[],
108+
callIndex: number,
109+
]
110+
| [
111+
op: 'checkSendOnly',
112+
target: Host,
113+
optVerb: PropertyKey | undefined,
114+
args: Host[],
115+
callIndex: number,
116+
]
117+
| [
118+
op: 'checkSend',
119+
target: Host,
120+
optVerb: PropertyKey | undefined,
121+
args: Host[],
122+
callIndex: number,
123+
];
124+
125+
/**
126+
* This would be the type alias for the full membrane log, if we supported:
127+
* - the guest sending guest-promises and guest-remotables to the host
128+
* - the guest using `E` to eventual-send to guest wrappers of the host
129+
* vows and remotables.
130+
*/
131+
export type FutureLogEntry =
132+
| [
133+
// ///////////////// From Host to Guest ///////////////////////
134+
op: 'doFulfill',
135+
vow: HostVow,
136+
fulfillment: Host,
137+
]
138+
| [op: 'doReject', vow: HostVow, reason: Host]
139+
| [
140+
op: 'doCall',
141+
target: Host,
142+
optVerb: PropertyKey | undefined,
143+
args: Host[],
144+
callIndex: number,
145+
]
146+
| [
147+
op: 'doSendOnly',
148+
target: Host,
149+
optVerb: PropertyKey | undefined,
150+
args: Host[],
151+
callIndex: number,
152+
]
153+
| [
154+
op: 'doSend',
155+
target: Host,
156+
optVerb: PropertyKey | undefined,
157+
args: Host[],
158+
callIndex: number,
159+
]
160+
| [op: 'doReturn', callIndex: number, result: Host]
161+
| [op: 'doThrow', callIndex: number, problem: Host]
162+
| [
163+
// ///////////////////// From Guest to Host /////////////////////////
164+
op: 'checkFulfill',
165+
vow: HostVow,
166+
fulfillment: Host,
167+
]
168+
| [op: 'checkReject', vow: HostVow, reason: Host]
169+
| [
170+
op: 'checkCall',
171+
target: Host,
172+
optVerb: PropertyKey | undefined,
173+
args: Host[],
174+
callIndex: number,
175+
]
176+
| [
177+
op: 'checkSendOnly',
178+
target: Host,
179+
optVerb: PropertyKey | undefined,
180+
args: Host[],
181+
callIndex: number,
182+
]
183+
| [
184+
op: 'checkSend',
185+
target: Host,
186+
optVerb: PropertyKey | undefined,
187+
args: Host[],
188+
callIndex: number,
189+
]
190+
| [op: 'checkReturn', callIndex: number, result: Host]
191+
| [op: 'checkThrow', callIndex: number, problem: Host];

0 commit comments

Comments
 (0)