@agoric/network 0.1.1-orchestration-dev-096c4e8.0 → 0.1.1-upgrade-16-dev-8879538.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/network.js CHANGED
@@ -1,13 +1,17 @@
1
1
  // @ts-check
2
2
 
3
+ /// <reference types="@agoric/store/exported.js" />
4
+
3
5
  import { E } from '@endo/far';
4
6
  import { M } from '@endo/patterns';
5
7
  import { Fail } from '@agoric/assert';
6
- import { whileTrue } from '@agoric/internal';
7
8
  import { toBytes } from './bytes.js';
9
+ import { Shape } from './shapes.js';
8
10
 
9
- import '@agoric/store/exported.js';
10
11
  /// <reference path="./types.js" />
12
+ /**
13
+ * @import {AttemptDescription, Bytes, Closable, CloseReason, Connection, ConnectionHandler, Endpoint, ListenHandler, Port, Protocol, ProtocolHandler, ProtocolImpl} from './types.js';
14
+ */
11
15
 
12
16
  /**
13
17
  * Compatibility note: this must match what our peers use, so don't change it
@@ -15,136 +19,13 @@ import '@agoric/store/exported.js';
15
19
  */
16
20
  export const ENDPOINT_SEPARATOR = '/';
17
21
 
18
- const Shape1 = /** @type {const} */ ({
19
- /**
20
- * Data is string | Buffer | ArrayBuffer
21
- * but only string is passable
22
- */
23
- Data: M.string(),
24
- Bytes: M.string(),
25
- Endpoint: M.string(),
26
- // TODO: match on "Vow" tag
27
- // @endo/patterns supports it as of
28
- // https://github.com/endojs/endo/pull/2091
29
- // but that's not in agoric-sdk yet.
30
- // For now, use M.any() to avoid:
31
- // cannot check unrecognized tag "Vow": "[Vow]"
32
- Vow: M.any(),
33
-
34
- ConnectionHandler: M.remotable('ConnectionHandler'),
35
- Connection: M.remotable('Connection'),
36
- InboundAttempt: M.remotable('InboundAttempt'),
37
- Listener: M.remotable('Listener'),
38
- ListenHandler: M.remotable('ListenHandler'),
39
- Port: M.remotable('Port'),
40
- ProtocolHandler: M.remotable('ProtocolHandler'),
41
- ProtocolImpl: M.remotable('ProtocolImpl'),
42
- });
43
-
44
- const Shape2 = /** @type {const} */ ({
45
- ...Shape1,
46
- Vow$: shape => M.or(shape, Shape1.Vow),
47
- AttemptDescription: M.splitRecord(
48
- { handler: Shape1.ConnectionHandler },
49
- { remoteAddress: Shape1.Endpoint, localAddress: Shape1.Endpoint },
50
- ),
51
- Opts: M.recordOf(M.string(), M.any()),
52
- });
53
-
54
- export const Shape = /** @type {const} */ harden({
55
- ...Shape2,
56
- ConnectionI: M.interface('Connection', {
57
- send: M.callWhen(Shape2.Data)
58
- .optional(Shape2.Opts)
59
- .returns(Shape2.Vow$(Shape2.Bytes)),
60
- close: M.callWhen().returns(Shape2.Vow$(M.undefined())),
61
- getLocalAddress: M.call().returns(Shape2.Endpoint),
62
- getRemoteAddress: M.call().returns(Shape2.Endpoint),
63
- }),
64
- InboundAttemptI: M.interface('InboundAttempt', {
65
- accept: M.callWhen(Shape2.AttemptDescription).returns(
66
- Shape2.Vow$(Shape2.Connection),
67
- ),
68
- getLocalAddress: M.call().returns(Shape2.Endpoint),
69
- getRemoteAddress: M.call().returns(Shape2.Endpoint),
70
- close: M.callWhen().returns(Shape2.Vow$(M.undefined())),
71
- }),
72
- PortI: M.interface('Port', {
73
- getLocalAddress: M.call().returns(Shape2.Endpoint),
74
- addListener: M.callWhen(Shape2.Listener).returns(
75
- Shape2.Vow$(M.undefined()),
76
- ),
77
- connect: M.callWhen(Shape2.Endpoint)
78
- .optional(Shape2.ConnectionHandler)
79
- .returns(Shape2.Vow$(Shape2.Connection)),
80
- removeListener: M.callWhen(Shape2.Listener).returns(
81
- Shape2.Vow$(M.undefined()),
82
- ),
83
- revoke: M.callWhen().returns(M.undefined()),
84
- }),
85
- ProtocolHandlerI: M.interface('ProtocolHandler', {
86
- onCreate: M.callWhen(M.remotable(), Shape2.ProtocolHandler).returns(
87
- Shape2.Vow$(M.undefined()),
88
- ),
89
- generatePortID: M.callWhen(Shape2.Endpoint, Shape2.ProtocolHandler).returns(
90
- Shape2.Vow$(M.string()),
91
- ),
92
- onBind: M.callWhen(
93
- Shape2.Port,
94
- Shape2.Endpoint,
95
- Shape2.ProtocolHandler,
96
- ).returns(Shape2.Vow$(M.undefined())),
97
- onListen: M.callWhen(
98
- Shape2.Port,
99
- Shape2.Endpoint,
100
- Shape2.ListenHandler,
101
- Shape2.ProtocolHandler,
102
- ).returns(Shape2.Vow$(M.undefined())),
103
- onListenRemove: M.callWhen(
104
- Shape2.Port,
105
- Shape2.Endpoint,
106
- Shape2.ListenHandler,
107
- Shape2.ProtocolHandler,
108
- ).returns(Shape2.Vow$(M.undefined())),
109
- onInstantiate: M.callWhen(
110
- Shape2.Port,
111
- Shape2.Endpoint,
112
- Shape2.Endpoint,
113
- Shape2.ProtocolHandler,
114
- ).returns(Shape2.Vow$(Shape2.Endpoint)),
115
- onConnect: M.callWhen(
116
- Shape2.Port,
117
- Shape2.Endpoint,
118
- Shape2.Endpoint,
119
- Shape2.ConnectionHandler,
120
- Shape2.ProtocolHandler,
121
- ).returns(Shape2.Vow$(Shape2.AttemptDescription)),
122
- onRevoke: M.callWhen(
123
- Shape2.Port,
124
- Shape2.Endpoint,
125
- Shape2.ProtocolHandler,
126
- ).returns(Shape2.Vow$(M.undefined())),
127
- }),
128
- ProtocolImplI: M.interface('ProtocolImpl', {
129
- bind: M.callWhen(Shape2.Endpoint).returns(Shape2.Vow$(Shape2.Port)),
130
- inbound: M.callWhen(Shape2.Endpoint, Shape2.Endpoint).returns(
131
- Shape2.Vow$(Shape2.InboundAttempt),
132
- ),
133
- outbound: M.callWhen(
134
- Shape2.Port,
135
- Shape2.Endpoint,
136
- Shape2.ConnectionHandler,
137
- ).returns(Shape2.Vow$(Shape2.Connection)),
138
- }),
139
- });
140
-
141
22
  /** @param {unknown} err */
142
23
  export const rethrowUnlessMissing = err => {
143
24
  // Ugly hack rather than being able to determine if the function
144
25
  // exists.
145
26
  if (
146
27
  !(err instanceof TypeError) ||
147
- !err.message.match(/target has no method|is not a function$/)
28
+ !String(err.message).match(/target has no method|is not a function$/)
148
29
  ) {
149
30
  throw err;
150
31
  }
@@ -169,10 +50,24 @@ export function getPrefixes(addr) {
169
50
  return ret;
170
51
  }
171
52
 
53
+ /**
54
+ * Validate IBC port name
55
+ * @param {string} specifiedName
56
+ */
57
+ function throwIfInvalidPortName(specifiedName) {
58
+ // Contains between 2 and 128 characters
59
+ // Can contain alphanumeric characters
60
+ // Valid symbols: ., ,, _, +, -, #, [, ], <, >
61
+ const portNameRegex = new RegExp('^[a-zA-Z0-9.,_+\\-#<>\\[\\]]{2,128}$');
62
+ if (!portNameRegex.test(specifiedName)) {
63
+ throw new Error(`Invalid IBC port name: ${specifiedName}`);
64
+ }
65
+ }
66
+
172
67
  /**
173
68
  * @typedef {object} ConnectionOpts
174
69
  * @property {Endpoint[]} addrs
175
- * @property {ConnectionHandler[]} handlers
70
+ * @property {import('@agoric/vow').Remote<Required<ConnectionHandler>>[]} handlers
176
71
  * @property {MapStore<number, Connection>} conns
177
72
  * @property {WeakSetStore<Closable>} current
178
73
  * @property {0|1} l
@@ -183,15 +78,12 @@ export function getPrefixes(addr) {
183
78
  * @param {import('@agoric/base-zone').Zone} zone
184
79
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
185
80
  */
186
- const prepareHalfConnection = (zone, { when }) => {
187
- const makeHalfConnection = zone.exoClass(
81
+ const prepareHalfConnection = (zone, { watch }) => {
82
+ const makeHalfConnectionKit = zone.exoClassKit(
188
83
  'Connection',
189
84
  Shape.ConnectionI,
190
85
  /** @param {ConnectionOpts} opts */
191
86
  ({ addrs, handlers, conns, current, l, r }) => {
192
- /** @type {string | undefined} */
193
- let closed;
194
-
195
87
  return {
196
88
  addrs,
197
89
  handlers,
@@ -199,62 +91,97 @@ const prepareHalfConnection = (zone, { when }) => {
199
91
  current,
200
92
  l,
201
93
  r,
202
- closed,
94
+ /** @type {string | undefined} */
95
+ closed: undefined,
203
96
  };
204
97
  },
205
98
  {
206
- getLocalAddress() {
207
- const { addrs, l } = this.state;
208
- return addrs[l];
209
- },
210
- getRemoteAddress() {
211
- const { addrs, r } = this.state;
212
- return addrs[r];
213
- },
214
- /** @param {Data} packetBytes */
215
- async send(packetBytes) {
216
- const { closed, handlers, r, conns } = this.state;
217
- if (closed) {
218
- throw closed;
219
- }
99
+ connection: {
100
+ getLocalAddress() {
101
+ const { addrs, l } = this.state;
102
+ return addrs[l];
103
+ },
104
+ getRemoteAddress() {
105
+ const { addrs, r } = this.state;
106
+ return addrs[r];
107
+ },
108
+ /** @param {Bytes} packetBytes */
109
+ async send(packetBytes) {
110
+ const { closed, handlers, r, conns } = this.state;
111
+ if (closed) {
112
+ throw Error(closed);
113
+ }
220
114
 
221
- const ack = await when(
222
- E(handlers[r])
223
- .onReceive(conns.get(r), toBytes(packetBytes), handlers[r])
224
- .catch(rethrowUnlessMissing),
225
- );
115
+ const innerVow = watch(
116
+ E(handlers[r]).onReceive(
117
+ conns.get(r),
118
+ toBytes(packetBytes),
119
+ handlers[r],
120
+ ),
121
+ this.facets.openConnectionAckWatcher,
122
+ );
123
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
124
+ },
125
+ async close() {
126
+ const { closed, current, conns, l, handlers } = this.state;
127
+ if (closed) {
128
+ throw Error(closed);
129
+ }
130
+ this.state.closed = 'Connection closed';
131
+ current.delete(conns.get(l));
132
+ const innerVow = watch(
133
+ E(this.state.handlers[l]).onClose(
134
+ conns.get(l),
135
+ undefined,
136
+ handlers[l],
137
+ ),
138
+ this.facets.sinkWatcher,
139
+ );
226
140
 
227
- return toBytes(ack || '');
141
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
142
+ },
228
143
  },
229
- async close() {
230
- const { closed, current, conns, l, handlers } = this.state;
231
- if (closed) {
232
- throw Error(closed);
233
- }
234
- this.state.closed = 'Connection closed';
235
- current.delete(conns.get(l));
236
- await when(
237
- E(this.state.handlers[l]).onClose(
238
- conns.get(l),
239
- undefined,
240
- handlers[l],
241
- ),
242
- ).catch(rethrowUnlessMissing);
144
+ openConnectionAckWatcher: {
145
+ onFulfilled(ack) {
146
+ return toBytes(ack || '');
147
+ },
148
+ },
149
+ rethrowUnlessMissingWatcher: {
150
+ onRejected(e) {
151
+ rethrowUnlessMissing(e);
152
+ },
153
+ },
154
+ sinkWatcher: {
155
+ onFulfilled(_value) {
156
+ return undefined;
157
+ },
243
158
  },
244
159
  },
245
160
  );
246
161
 
162
+ const makeHalfConnection = ({ addrs, handlers, conns, current, l, r }) => {
163
+ const { connection } = makeHalfConnectionKit({
164
+ addrs,
165
+ handlers,
166
+ conns,
167
+ current,
168
+ l,
169
+ r,
170
+ });
171
+ return harden(connection);
172
+ };
173
+
247
174
  return makeHalfConnection;
248
175
  };
249
176
 
250
177
  /**
251
178
  * @param {import('@agoric/zone').Zone} zone
252
- * @param {ConnectionHandler} handler0
179
+ * @param {import('@agoric/vow').Remote<Required<ConnectionHandler>>} handler0
253
180
  * @param {Endpoint} addr0
254
- * @param {ConnectionHandler} handler1
181
+ * @param {import('@agoric/vow').Remote<Required<ConnectionHandler>>} handler1
255
182
  * @param {Endpoint} addr1
256
183
  * @param {(opts: ConnectionOpts) => Connection} makeConnection
257
- * @param {WeakSetStore<Closable>} current
184
+ * @param {WeakSetStore<Closable>} [current]
258
185
  */
259
186
  export const crossoverConnection = (
260
187
  zone,
@@ -270,7 +197,7 @@ export const crossoverConnection = (
270
197
  /** @type {MapStore<number, Connection>} */
271
198
  const conns = detached.mapStore('addrToConnections');
272
199
 
273
- /** @type {ConnectionHandler[]} */
200
+ /** @type {import('@agoric/vow').Remote<Required<ConnectionHandler>>[]} */
274
201
  const handlers = harden([handler0, handler1]);
275
202
  /** @type {Endpoint[]} */
276
203
  const addrs = harden([addr0, addr1]);
@@ -308,17 +235,17 @@ export const crossoverConnection = (
308
235
  * @param {(opts: ConnectionOpts) => Connection} makeConnection
309
236
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
310
237
  */
311
- const prepareInboundAttempt = (zone, makeConnection, { when }) => {
312
- const makeInboundAttempt = zone.exoClass(
238
+ const prepareInboundAttempt = (zone, makeConnection, { watch }) => {
239
+ const makeInboundAttemptKit = zone.exoClassKit(
313
240
  'InboundAttempt',
314
241
  Shape.InboundAttemptI,
315
242
  /**
316
243
  * @param {object} opts
317
244
  * @param {string} opts.localAddr
318
245
  * @param {string} opts.remoteAddr
319
- * @param { MapStore<Port, SetStore<Closable>> } opts.currentConnections
246
+ * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
320
247
  * @param {string} opts.listenPrefix
321
- * @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
248
+ * @param {MapStore<Endpoint, [Port, import('@agoric/vow').Remote<Required<ListenHandler>>]>} opts.listening
322
249
  */
323
250
  ({
324
251
  localAddr,
@@ -327,7 +254,7 @@ const prepareInboundAttempt = (zone, makeConnection, { when }) => {
327
254
  listenPrefix,
328
255
  listening,
329
256
  }) => {
330
- /** @type {String | undefined} */
257
+ /** @type {string | undefined} */
331
258
  let consummated;
332
259
 
333
260
  return {
@@ -340,75 +267,125 @@ const prepareInboundAttempt = (zone, makeConnection, { when }) => {
340
267
  };
341
268
  },
342
269
  {
343
- getLocalAddress() {
344
- // Return address metadata.
345
- return this.state.localAddr;
346
- },
347
- getRemoteAddress() {
348
- return this.state.remoteAddr;
349
- },
350
- async close() {
351
- const { consummated, localAddr, remoteAddr } = this.state;
352
- const { listening, listenPrefix, currentConnections } = this.state;
270
+ inboundAttempt: {
271
+ getLocalAddress() {
272
+ // Return address metadata.
273
+ return this.state.localAddr;
274
+ },
275
+ getRemoteAddress() {
276
+ return this.state.remoteAddr;
277
+ },
278
+ async close() {
279
+ const { consummated, localAddr, remoteAddr } = this.state;
280
+ const { listening, listenPrefix, currentConnections } = this.state;
353
281
 
354
- if (consummated) {
355
- throw Error(consummated);
356
- }
357
- this.state.consummated = 'Already closed';
282
+ if (consummated) {
283
+ throw Error(consummated);
284
+ }
285
+ this.state.consummated = 'Already closed';
358
286
 
359
- const [port, listener] = listening.get(listenPrefix);
287
+ const [port, listener] = listening.get(listenPrefix);
360
288
 
361
- const current = currentConnections.get(port);
362
- current.delete(this.self);
289
+ const current = currentConnections.get(port);
290
+ current.delete(this.facets.inboundAttempt);
363
291
 
364
- await when(
365
- E(listener).onReject(port, localAddr, remoteAddr, listener),
366
- ).catch(rethrowUnlessMissing);
367
- },
368
- /**
369
- * @param {object} opts
370
- * @param {string} [opts.localAddress]
371
- * @param {string} [opts.remoteAddress]
372
- * @param {ConnectionHandler} opts.handler
373
- */
374
- async accept({ localAddress, remoteAddress, handler: rchandler }) {
375
- const { consummated, localAddr, remoteAddr } = this.state;
376
- const { listening, listenPrefix, currentConnections } = this.state;
377
- if (consummated) {
378
- throw Error(consummated);
379
- }
380
- this.state.consummated = 'Already accepted';
292
+ const innerVow = watch(
293
+ E(listener).onReject(port, localAddr, remoteAddr, listener),
294
+ this.facets.sinkWatcher,
295
+ );
381
296
 
382
- if (localAddress === undefined) {
383
- localAddress = localAddr;
384
- }
297
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
298
+ },
299
+ /**
300
+ * @param {object} opts
301
+ * @param {string} [opts.localAddress]
302
+ * @param {string} [opts.remoteAddress]
303
+ * @param {import('@agoric/vow').Remote<ConnectionHandler>} opts.handler
304
+ */
305
+ async accept({ localAddress, remoteAddress, handler: rchandler }) {
306
+ const { consummated, localAddr, remoteAddr } = this.state;
307
+ const { listening, listenPrefix, currentConnections } = this.state;
308
+ if (consummated) {
309
+ throw Error(consummated);
310
+ }
385
311
 
386
- if (remoteAddress === undefined) {
387
- remoteAddress = remoteAddr;
388
- }
312
+ if (localAddress === undefined) {
313
+ localAddress = localAddr;
314
+ }
315
+ this.state.consummated = `${localAddress} Already accepted`;
389
316
 
390
- const [port, listener] = listening.get(listenPrefix);
391
- const current = currentConnections.get(port);
317
+ if (remoteAddress === undefined) {
318
+ remoteAddress = remoteAddr;
319
+ }
392
320
 
393
- current.delete(this.self);
321
+ const [port, listener] = listening.get(listenPrefix);
322
+ const current = currentConnections.get(port);
394
323
 
395
- const lchandler = await when(
396
- E(listener).onAccept(port, localAddress, remoteAddress, listener),
397
- );
324
+ current.delete(this.facets.inboundAttempt);
325
+
326
+ return watch(
327
+ E(listener).onAccept(port, localAddress, remoteAddress, listener),
328
+ this.facets.inboundAttemptAcceptWatcher,
329
+ {
330
+ localAddress,
331
+ rchandler,
332
+ remoteAddress,
333
+ current,
334
+ },
335
+ );
336
+ },
337
+ },
338
+ inboundAttemptAcceptWatcher: {
339
+ onFulfilled(lchandler, watchContext) {
340
+ const { localAddress, rchandler, remoteAddress, current } =
341
+ watchContext;
398
342
 
399
- return crossoverConnection(
400
- zone,
401
- lchandler,
402
- localAddress,
403
- rchandler,
404
- remoteAddress,
405
- makeConnection,
406
- current,
407
- )[1];
343
+ return crossoverConnection(
344
+ zone,
345
+ /** @type {import('@agoric/vow').Remote<Required<ConnectionHandler>>} */ (
346
+ lchandler
347
+ ),
348
+ localAddress,
349
+ /** @type {import('@agoric/vow').Remote<Required<ConnectionHandler>>} */ (
350
+ rchandler
351
+ ),
352
+ remoteAddress,
353
+ makeConnection,
354
+ current,
355
+ )[1];
356
+ },
357
+ },
358
+ rethrowUnlessMissingWatcher: {
359
+ onRejected(e) {
360
+ rethrowUnlessMissing(e);
361
+ },
362
+ },
363
+ sinkWatcher: {
364
+ onFulfilled(_value) {
365
+ return undefined;
366
+ },
408
367
  },
409
368
  },
410
369
  );
411
370
 
371
+ const makeInboundAttempt = ({
372
+ localAddr,
373
+ remoteAddr,
374
+ currentConnections,
375
+ listenPrefix,
376
+ listening,
377
+ }) => {
378
+ const { inboundAttempt } = makeInboundAttemptKit({
379
+ localAddr,
380
+ remoteAddr,
381
+ currentConnections,
382
+ listenPrefix,
383
+ listening,
384
+ });
385
+
386
+ return harden(inboundAttempt);
387
+ };
388
+
412
389
  return makeInboundAttempt;
413
390
  };
414
391
 
@@ -423,49 +400,50 @@ const RevokeState = /** @type {const} */ ({
423
400
  * @param {import('@agoric/zone').Zone} zone
424
401
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
425
402
  */
426
- const preparePort = (zone, { when }) => {
403
+ const preparePort = (zone, powers) => {
427
404
  const makeIncapable = zone.exoClass('Incapable', undefined, () => ({}), {});
428
405
 
429
- const makePort = zone.exoClass(
430
- 'Port',
431
- Shape.PortI,
432
- /**
433
- * @param {object} opts
434
- * @param {Endpoint} opts.localAddr
435
- * @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
436
- * @param {SetStore<Connection>} opts.openConnections
437
- * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
438
- * @param {MapStore<string, Port>} opts.boundPorts
439
- * @param {ProtocolHandler} opts.protocolHandler
440
- * @param {ProtocolImpl} opts.protocolImpl
441
- */
442
- ({
443
- localAddr,
406
+ const { watch, allVows } = powers;
407
+
408
+ /**
409
+ * @param {object} opts
410
+ * @param {Endpoint} opts.localAddr
411
+ * @param {MapStore<Endpoint, [Port, import('@agoric/vow').Remote<Required<ListenHandler>>]>} opts.listening
412
+ * @param {SetStore<import('@agoric/vow').Remote<Connection>>} opts.openConnections
413
+ * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
414
+ * @param {MapStore<string, Port>} opts.boundPorts
415
+ * @param {import('@agoric/vow').Remote<ProtocolHandler>} opts.protocolHandler
416
+ * @param {import('@agoric/vow').Remote<ProtocolImpl>} opts.protocolImpl
417
+ */
418
+ const initPort = ({
419
+ localAddr,
420
+ listening,
421
+ openConnections,
422
+ currentConnections,
423
+ boundPorts,
424
+ protocolHandler,
425
+ protocolImpl,
426
+ }) => {
427
+ return {
444
428
  listening,
445
429
  openConnections,
446
430
  currentConnections,
447
431
  boundPorts,
432
+ localAddr,
448
433
  protocolHandler,
449
434
  protocolImpl,
450
- }) => {
451
- return {
452
- listening,
453
- openConnections,
454
- currentConnections,
455
- boundPorts,
456
- localAddr,
457
- protocolHandler,
458
- protocolImpl,
459
- /** @type {RevokeState | undefined} */
460
- revoked: undefined,
461
- };
462
- },
463
- {
435
+ /** @type {RevokeState | undefined} */
436
+ revoked: undefined,
437
+ };
438
+ };
439
+
440
+ const makePortKit = zone.exoClassKit('Port', Shape.PortI, initPort, {
441
+ port: {
464
442
  getLocalAddress() {
465
443
  // Works even after revoke().
466
444
  return this.state.localAddr;
467
445
  },
468
- /** @param {ListenHandler} listenHandler */
446
+ /** @param {import('@agoric/vow').Remote<ListenHandler>} listenHandler */
469
447
  async addListener(listenHandler) {
470
448
  const { revoked, listening, localAddr, protocolHandler } = this.state;
471
449
 
@@ -478,96 +456,196 @@ const preparePort = (zone, { when }) => {
478
456
  if (lhandler === listenHandler) {
479
457
  return;
480
458
  }
481
- listening.set(localAddr, [this.self, listenHandler]);
459
+ listening.set(localAddr, [
460
+ this.facets.port,
461
+ /** @type {import('@agoric/vow').Remote<Required<ListenHandler>>} */ (
462
+ listenHandler
463
+ ),
464
+ ]);
482
465
  E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
483
466
  } else {
484
- listening.init(localAddr, harden([this.self, listenHandler]));
467
+ listening.init(
468
+ localAddr,
469
+ harden([
470
+ this.facets.port,
471
+ /** @type {import('@agoric/vow').Remote<Required<ListenHandler>>} */ (
472
+ listenHandler
473
+ ),
474
+ ]),
475
+ );
485
476
  }
486
477
 
487
478
  // ASSUME: that the listener defines onAccept.
488
479
 
489
- await when(
480
+ const innerVow = watch(
490
481
  E(protocolHandler).onListen(
491
- this.self,
482
+ this.facets.port,
492
483
  localAddr,
493
484
  listenHandler,
494
485
  protocolHandler,
495
486
  ),
487
+ this.facets.portAddListenerWatcher,
488
+ { listenHandler },
496
489
  );
497
- await when(E(listenHandler).onListen(this.self, listenHandler)).catch(
498
- rethrowUnlessMissing,
499
- );
490
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
500
491
  },
501
- /** @param {ListenHandler} listenHandler */
492
+ /** @param {import('@agoric/vow').Remote<ListenHandler>} listenHandler */
502
493
  async removeListener(listenHandler) {
503
494
  const { listening, localAddr, protocolHandler } = this.state;
504
495
  listening.has(localAddr) || Fail`Port ${localAddr} is not listening`;
505
496
  listening.get(localAddr)[1] === listenHandler ||
506
497
  Fail`Port ${localAddr} handler to remove is not listening`;
507
498
  listening.delete(localAddr);
508
- await when(
499
+
500
+ const innerVow = watch(
509
501
  E(protocolHandler).onListenRemove(
510
- this.self,
502
+ this.facets.port,
511
503
  localAddr,
512
504
  listenHandler,
513
505
  protocolHandler,
514
506
  ),
507
+ this.facets.portRemoveListenerWatcher,
508
+ { listenHandler },
515
509
  );
516
- await when(E(listenHandler).onRemove(this.self, listenHandler)).catch(
517
- rethrowUnlessMissing,
518
- );
510
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
519
511
  },
520
512
  /**
521
513
  * @param {Endpoint} remotePort
522
- * @param {ConnectionHandler} connectionHandler
514
+ * @param {import('@agoric/vow').Remote<ConnectionHandler>} [connectionHandler]
523
515
  */
524
516
  async connect(
525
517
  remotePort,
526
- connectionHandler = /** @type {any} */ (makeIncapable()),
518
+ connectionHandler = /** @type {import('@agoric/vow').Remote<ConnectionHandler>} */ (
519
+ makeIncapable()
520
+ ),
527
521
  ) {
528
- const { revoked, localAddr, protocolImpl, openConnections } =
529
- this.state;
522
+ const { revoked, localAddr, protocolImpl } = this.state;
530
523
 
531
524
  !revoked || Fail`Port ${localAddr} is revoked`;
532
525
  /** @type {Endpoint} */
533
526
  const dst = harden(remotePort);
534
- // eslint-disable-next-line no-use-before-define
535
- const conn = await when(
536
- protocolImpl.outbound(this.self, dst, connectionHandler),
527
+ return watch(
528
+ E(protocolImpl).outbound(this.facets.port, dst, connectionHandler),
529
+ this.facets.portConnectWatcher,
530
+ { revoked },
537
531
  );
538
- if (revoked) {
539
- void E(conn).close();
540
- } else {
541
- openConnections.add(conn);
542
- }
543
- return conn;
544
532
  },
545
533
  async revoke() {
546
534
  const { revoked, localAddr } = this.state;
547
- const { protocolHandler, currentConnections, listening, boundPorts } =
548
- this.state;
535
+ const { protocolHandler } = this.state;
549
536
 
550
537
  revoked !== RevokeState.REVOKED ||
551
538
  Fail`Port ${localAddr} is already revoked`;
539
+
552
540
  this.state.revoked = RevokeState.REVOKING;
553
- await when(
554
- E(protocolHandler).onRevoke(this.self, localAddr, protocolHandler),
541
+
542
+ const revokeVow = watch(
543
+ E(protocolHandler).onRevoke(
544
+ this.facets.port,
545
+ localAddr,
546
+ protocolHandler,
547
+ ),
548
+ this.facets.portRevokeWatcher,
555
549
  );
556
- this.state.revoked = RevokeState.REVOKED;
550
+
551
+ return watch(revokeVow, this.facets.portRevokeCleanupWatcher);
552
+ },
553
+ },
554
+ portAddListenerWatcher: {
555
+ onFulfilled(_value, watcherContext) {
556
+ const { listenHandler } = watcherContext;
557
+ return E(listenHandler).onListen(this.facets.port, listenHandler);
558
+ },
559
+ },
560
+ portRemoveListenerWatcher: {
561
+ onFulfilled(_value, watcherContext) {
562
+ const { listenHandler } = watcherContext;
563
+ return E(listenHandler).onRemove(this.facets.port, listenHandler);
564
+ },
565
+ },
566
+ portConnectWatcher: {
567
+ onFulfilled(conn, watchContext) {
568
+ const { revoked } = watchContext;
569
+ const { openConnections } = this.state;
570
+
571
+ if (revoked) {
572
+ void E(conn).close();
573
+ } else {
574
+ openConnections.add(conn);
575
+ }
576
+ return conn;
577
+ },
578
+ },
579
+ portRevokeWatcher: {
580
+ onFulfilled(_value) {
581
+ const { currentConnections, listening, localAddr } = this.state;
582
+ const port = this.facets.port;
557
583
 
558
584
  // Clean up everything we did.
559
- const values = [...currentConnections.get(this.self).values()];
560
- const ps = values.map(conn => when(E(conn).close()).catch(_ => {}));
585
+ const values = [...currentConnections.get(port).values()];
586
+
587
+ /** @type {import('@agoric/vow').Specimen[]} */
588
+ const ps = [];
589
+
590
+ ps.push(
591
+ ...values.map(conn =>
592
+ watch(E(conn).close(), this.facets.sinkWatcher),
593
+ ),
594
+ );
595
+
561
596
  if (listening.has(localAddr)) {
562
597
  const listener = listening.get(localAddr)[1];
563
- ps.push(this.self.removeListener(listener));
598
+ ps.push(port.removeListener(listener));
564
599
  }
565
- await Promise.all(ps);
566
- currentConnections.delete(this.self);
600
+
601
+ return watch(allVows(ps), this.facets.rethrowUnlessMissingWatcher);
602
+ },
603
+ },
604
+ sinkWatcher: {
605
+ onFulfilled() {
606
+ return undefined;
607
+ },
608
+ onRejected() {
609
+ return undefined;
610
+ },
611
+ },
612
+ portRevokeCleanupWatcher: {
613
+ onFulfilled(_value) {
614
+ const { currentConnections, boundPorts, localAddr } = this.state;
615
+
616
+ this.state.revoked = RevokeState.REVOKED;
617
+
618
+ currentConnections.delete(this.facets.port);
567
619
  boundPorts.delete(localAddr);
568
620
  },
569
621
  },
570
- );
622
+ rethrowUnlessMissingWatcher: {
623
+ onRejected(e) {
624
+ rethrowUnlessMissing(e);
625
+ },
626
+ },
627
+ });
628
+
629
+ const makePort = ({
630
+ localAddr,
631
+ listening,
632
+ openConnections,
633
+ currentConnections,
634
+ boundPorts,
635
+ protocolHandler,
636
+ protocolImpl,
637
+ }) => {
638
+ const { port } = makePortKit({
639
+ localAddr,
640
+ listening,
641
+ openConnections,
642
+ currentConnections,
643
+ boundPorts,
644
+ protocolHandler,
645
+ protocolImpl,
646
+ });
647
+ return harden(port);
648
+ };
571
649
 
572
650
  return makePort;
573
651
  };
@@ -578,29 +656,84 @@ const preparePort = (zone, { when }) => {
578
656
  */
579
657
  const prepareBinder = (zone, powers) => {
580
658
  const makeConnection = prepareHalfConnection(zone, powers);
581
- const { when } = powers;
659
+
660
+ const { watch } = powers;
661
+
582
662
  const makeInboundAttempt = prepareInboundAttempt(
583
663
  zone,
584
664
  makeConnection,
585
665
  powers,
586
666
  );
667
+
587
668
  const makePort = preparePort(zone, powers);
669
+
588
670
  const detached = zone.detached();
589
671
 
590
- const makeBinderKit = zone.exoClassKit(
672
+ const makeFullBinderKit = zone.exoClassKit(
591
673
  'binder',
592
674
  {
593
675
  protocolImpl: Shape.ProtocolImplI,
594
676
  binder: M.interface('Binder', {
595
- bind: M.callWhen(Shape.Endpoint).returns(Shape.Port),
677
+ bindPort: M.callWhen(Shape.Endpoint).returns(Shape.Vow$(Shape.Port)),
678
+ }),
679
+ binderInboundInstantiateWatcher: M.interface(
680
+ 'BinderInboundInstantiateWatcher',
681
+ {
682
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
683
+ },
684
+ ),
685
+ binderInboundInstantiateCatchWatcher: M.interface(
686
+ 'BinderInboundInstantiateCatchWatcher',
687
+ {
688
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
689
+ },
690
+ ),
691
+ binderOutboundInstantiateWatcher: M.interface(
692
+ 'BinderOutboundInstantiateWatcher',
693
+ {
694
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
695
+ },
696
+ ),
697
+ binderOutboundConnectWatcher: M.interface(
698
+ 'BinderOutboundConnectWatcher',
699
+ {
700
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
701
+ },
702
+ ),
703
+ binderOutboundCatchWatcher: M.interface('BinderOutboundCatchWatcher', {
704
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
705
+ }),
706
+ binderOutboundInboundWatcher: M.interface(
707
+ 'BinderOutboundInboundWatcher',
708
+ {
709
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
710
+ },
711
+ ),
712
+ binderOutboundAcceptWatcher: M.interface('BinderOutboundAcceptWatcher', {
713
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
714
+ }),
715
+ binderBindGeneratePortWatcher: M.interface(
716
+ 'BinderBindGeneratePortWatcher',
717
+ {
718
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
719
+ },
720
+ ),
721
+ binderPortWatcher: M.interface('BinderPortWatcher', {
722
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
723
+ }),
724
+ binderBindWatcher: M.interface('BinderBindWatcher', {
725
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
726
+ }),
727
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
728
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
596
729
  }),
597
730
  },
598
731
  /**
599
732
  * @param {object} opts
600
- * @param { MapStore<Port, SetStore<Closable>> } opts.currentConnections
733
+ * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
601
734
  * @param {MapStore<string, Port>} opts.boundPorts
602
- * @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
603
- * @param {ProtocolHandler} opts.protocolHandler
735
+ * @param {MapStore<Endpoint, [Port, import('@agoric/vow').Remote<Required<ListenHandler>>]>} opts.listening
736
+ * @param {import('@agoric/vow').Remote<ProtocolHandler>} opts.protocolHandler
604
737
  */
605
738
  ({ currentConnections, boundPorts, listening, protocolHandler }) => {
606
739
  /** @type {SetStore<Connection>} */
@@ -613,8 +746,6 @@ const prepareBinder = (zone, powers) => {
613
746
  revoked: RevokeState.NOT_REVOKED,
614
747
  openConnections,
615
748
  protocolHandler,
616
- /** @type {Endpoint | undefined} */
617
- localAddr: undefined,
618
749
  };
619
750
  },
620
751
  {
@@ -624,49 +755,58 @@ const prepareBinder = (zone, powers) => {
624
755
  * @param {Endpoint} remoteAddr
625
756
  */
626
757
  async inbound(listenAddr, remoteAddr) {
627
- const { listening, protocolHandler, currentConnections } = this.state;
758
+ const { listening, protocolHandler } = this.state;
759
+
760
+ const prefixes = getPrefixes(listenAddr);
761
+ let listenPrefixIndex = 0;
762
+ let listenPrefix;
628
763
 
629
- let lastFailure = Error(`No listeners for ${listenAddr}`);
630
- for await (const listenPrefix of getPrefixes(listenAddr)) {
764
+ while (listenPrefixIndex < prefixes.length) {
765
+ listenPrefix = prefixes[listenPrefixIndex];
631
766
  if (!listening.has(listenPrefix)) {
767
+ listenPrefixIndex += 1;
632
768
  continue;
633
769
  }
634
- const [port, _] = listening.get(listenPrefix);
635
- let localAddr;
636
770
 
637
- await (async () => {
638
- // See if our protocol is willing to receive this connection.
639
- const localInstance = await when(
640
- E(protocolHandler).onInstantiate(
641
- port,
642
- listenPrefix,
643
- remoteAddr,
644
- protocolHandler,
645
- ),
646
- ).catch(rethrowUnlessMissing);
647
- localAddr = localInstance
648
- ? `${listenAddr}/${localInstance}`
649
- : listenAddr;
650
- })().catch(e => {
651
- lastFailure = e;
652
- });
653
- if (!localAddr) {
654
- continue;
655
- }
656
- // We have a legitimate inbound attempt.
657
- const current = currentConnections.get(port);
658
- const inboundAttempt = makeInboundAttempt({
659
- localAddr,
660
- remoteAddr,
661
- currentConnections,
662
- listenPrefix,
663
- listening,
664
- });
771
+ break;
772
+ }
665
773
 
666
- current.add(inboundAttempt);
667
- return inboundAttempt;
774
+ if (listenPrefixIndex >= prefixes.length) {
775
+ throw Error(`No listeners for ${listenAddr}`);
668
776
  }
669
- throw lastFailure;
777
+
778
+ const [port] = listening.get(/** @type {string} **/ (listenPrefix));
779
+
780
+ const innerVow = watch(
781
+ E(
782
+ /** @type {import('@agoric/vow').Remote<Required<ProtocolHandler>>} */ (
783
+ protocolHandler
784
+ ),
785
+ ).onInstantiate(
786
+ /** @type {Port} **/ (port),
787
+ prefixes[listenPrefixIndex],
788
+ remoteAddr,
789
+ protocolHandler,
790
+ ),
791
+ this.facets.binderInboundInstantiateWatcher,
792
+ {
793
+ listenAddr,
794
+ remoteAddr,
795
+ port,
796
+ listenPrefixIndex,
797
+ },
798
+ );
799
+
800
+ return watch(
801
+ innerVow,
802
+ this.facets.binderInboundInstantiateCatchWatcher,
803
+ {
804
+ listenPrefixIndex,
805
+ listenAddr,
806
+ remoteAddr,
807
+ lastFailure: Error(`No listeners for ${listenAddr}`),
808
+ },
809
+ );
670
810
  },
671
811
  /**
672
812
  * @param {Port} port
@@ -674,101 +814,312 @@ const prepareBinder = (zone, powers) => {
674
814
  * @param {ConnectionHandler} lchandler
675
815
  */
676
816
  async outbound(port, remoteAddr, lchandler) {
677
- const { protocolHandler, currentConnections } = this.state;
817
+ const { protocolHandler } = this.state;
678
818
 
679
819
  const localAddr = await E(port).getLocalAddress();
680
820
 
681
821
  // Allocate a local address.
682
- const initialLocalInstance = await when(
683
- E(protocolHandler).onInstantiate(
822
+ const instantiateInnerVow = watch(
823
+ E(
824
+ /** @type {import('@agoric/vow').Remote<Required<ProtocolHandler>>} */ (
825
+ protocolHandler
826
+ ),
827
+ ).onInstantiate(port, localAddr, remoteAddr, protocolHandler),
828
+ this.facets.binderOutboundInstantiateWatcher,
829
+ {
684
830
  port,
685
831
  localAddr,
686
832
  remoteAddr,
687
833
  protocolHandler,
688
- ),
689
- ).catch(rethrowUnlessMissing);
690
- const initialLocalAddr = initialLocalInstance
691
- ? `${localAddr}/${initialLocalInstance}`
692
- : localAddr;
834
+ },
835
+ );
693
836
 
694
- let lastFailure;
695
- let accepted;
696
- await (async () => {
697
- // Attempt the loopback connection.
698
- const attempt = await when(
699
- this.facets.protocolImpl.inbound(remoteAddr, initialLocalAddr),
700
- );
701
- accepted = await when(attempt.accept({ handler: lchandler }));
702
- })().catch(e => {
703
- lastFailure = e;
837
+ const instantiateVow = watch(
838
+ instantiateInnerVow,
839
+ this.facets.rethrowUnlessMissingWatcher,
840
+ );
841
+
842
+ const attemptVow = watch(
843
+ instantiateVow,
844
+ this.facets.binderOutboundInboundWatcher,
845
+ {
846
+ localAddr,
847
+ remoteAddr,
848
+ },
849
+ );
850
+ const acceptedVow = watch(
851
+ attemptVow,
852
+ this.facets.binderOutboundAcceptWatcher,
853
+ {
854
+ handler: lchandler,
855
+ },
856
+ );
857
+
858
+ return watch(acceptedVow, this.facets.binderOutboundCatchWatcher, {
859
+ port,
860
+ remoteAddr,
861
+ lchandler,
862
+ localAddr,
704
863
  });
705
- if (accepted) {
706
- return accepted;
864
+ },
865
+ async bindPort(localAddr) {
866
+ return this.facets.binder.bindPort(localAddr);
867
+ },
868
+ },
869
+ binder: {
870
+ /** @param {string} localAddr */
871
+ async bindPort(localAddr) {
872
+ const { protocolHandler } = this.state;
873
+
874
+ // Check if we are underspecified (ends in slash)
875
+ const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
876
+
877
+ const localAddrVow = watch(
878
+ E(protocolHandler).generatePortID(localAddr, protocolHandler),
879
+ this.facets.binderBindGeneratePortWatcher,
880
+ {
881
+ underspecified,
882
+ localAddr,
883
+ },
884
+ );
885
+
886
+ return watch(localAddrVow, this.facets.binderBindWatcher);
887
+ },
888
+ },
889
+ binderInboundInstantiateWatcher: {
890
+ onFulfilled(localInstance, watchContext) {
891
+ const { listenAddr, remoteAddr, port, listenPrefixIndex } =
892
+ watchContext;
893
+ const { listening, currentConnections } = this.state;
894
+ const prefixes = getPrefixes(listenAddr);
895
+
896
+ const localAddr = localInstance
897
+ ? `${listenAddr}/${localInstance}`
898
+ : listenAddr;
899
+ const current = currentConnections.get(port);
900
+ const inboundAttempt = makeInboundAttempt({
901
+ localAddr,
902
+ remoteAddr,
903
+ currentConnections,
904
+ listenPrefix: prefixes[listenPrefixIndex],
905
+ listening,
906
+ });
907
+
908
+ current.add(inboundAttempt);
909
+ return inboundAttempt;
910
+ },
911
+ },
912
+ binderInboundInstantiateCatchWatcher: {
913
+ onRejected(e, watchContext) {
914
+ let { lastFailure, listenPrefixIndex } = watchContext;
915
+
916
+ try {
917
+ rethrowUnlessMissing(e);
918
+ } catch (innerE) {
919
+ lastFailure = innerE;
707
920
  }
708
921
 
709
- const {
710
- remoteAddress = remoteAddr,
922
+ const { listenAddr, remoteAddr } = watchContext;
923
+
924
+ const { listening, protocolHandler } = this.state;
925
+
926
+ const prefixes = getPrefixes(listenAddr);
927
+
928
+ let listenPrefix;
929
+
930
+ listenPrefixIndex += 1;
931
+
932
+ while (listenPrefixIndex < prefixes.length) {
933
+ listenPrefix = prefixes[listenPrefixIndex];
934
+ if (!listening.has(listenPrefix)) {
935
+ listenPrefixIndex += 1;
936
+ continue;
937
+ }
938
+
939
+ break;
940
+ }
941
+
942
+ if (listenPrefixIndex >= prefixes.length) {
943
+ throw lastFailure;
944
+ }
945
+
946
+ const [port] = listening.get(/** @type {string} */ (listenPrefix));
947
+
948
+ const innerVow = watch(
949
+ E(
950
+ /** @type {import('@agoric/vow').Remote<Required<ProtocolHandler>>} */ (
951
+ protocolHandler
952
+ ),
953
+ ).onInstantiate(
954
+ port,
955
+ prefixes[listenPrefixIndex],
956
+ remoteAddr,
957
+ protocolHandler,
958
+ ),
959
+ this.facets.binderInboundInstantiateWatcher,
960
+ {
961
+ listenAddr,
962
+ remoteAddr,
963
+ port,
964
+ listenPrefixIndex,
965
+ },
966
+ );
967
+
968
+ return watch(
969
+ innerVow,
970
+ this.facets.binderInboundInstantiateCatchWatcher,
971
+ {
972
+ ...watchContext,
973
+ lastFailure,
974
+ listenPrefixIndex,
975
+ },
976
+ );
977
+ },
978
+ },
979
+ binderOutboundInstantiateWatcher: {
980
+ onFulfilled(localInstance, watchContext) {
981
+ const { localAddr } = watchContext;
982
+
983
+ return localInstance ? `${localAddr}/${localInstance}` : localAddr;
984
+ },
985
+ },
986
+ binderOutboundConnectWatcher: {
987
+ onFulfilled(
988
+ {
711
989
  handler: rchandler,
712
- localAddress = localAddr,
713
- } =
714
- /** @type {Partial<AttemptDescription>} */
715
- (
716
- await when(
717
- E(protocolHandler).onConnect(
718
- port,
719
- initialLocalAddr,
720
- remoteAddr,
721
- lchandler,
722
- protocolHandler,
723
- ),
724
- )
725
- );
990
+ remoteAddress: negotiatedRemoteAddress,
991
+ localAddress: negotiatedLocalAddress,
992
+ },
993
+ watchContext,
994
+ ) {
995
+ const {
996
+ lastFailure,
997
+ lchandler,
998
+ localAddr: requestedLocalAddress,
999
+ remoteAddr: requestedRemoteAddress,
1000
+ port,
1001
+ } = watchContext;
1002
+
1003
+ const { currentConnections } = this.state;
726
1004
 
727
1005
  if (!rchandler) {
728
1006
  throw lastFailure;
729
1007
  }
730
1008
 
731
1009
  const current = currentConnections.get(port);
1010
+
732
1011
  return crossoverConnection(
733
1012
  zone,
734
- lchandler,
735
- localAddress,
736
- rchandler,
737
- remoteAddress,
1013
+ /** @type {import('@agoric/vow').Remote<Required<ConnectionHandler>>} */ (
1014
+ lchandler
1015
+ ),
1016
+ negotiatedLocalAddress || requestedLocalAddress,
1017
+ /** @type {import('@agoric/vow').Remote<Required<ConnectionHandler>>} */ (
1018
+ rchandler
1019
+ ),
1020
+ negotiatedRemoteAddress || requestedRemoteAddress,
738
1021
  makeConnection,
739
1022
  current,
740
1023
  )[0];
741
1024
  },
742
- async bind(localAddr) {
743
- return this.facets.binder.bind(localAddr);
1025
+ },
1026
+ binderOutboundCatchWatcher: {
1027
+ onRejected(e, watchContext) {
1028
+ let lastFailure;
1029
+
1030
+ try {
1031
+ rethrowUnlessMissing(e);
1032
+ } catch (innerE) {
1033
+ lastFailure = innerE;
1034
+ }
1035
+
1036
+ const { port, remoteAddr, lchandler, localAddr } = watchContext;
1037
+
1038
+ const { protocolHandler } = this.state;
1039
+
1040
+ const connectVow = watch(
1041
+ E(protocolHandler).onConnect(
1042
+ port,
1043
+ localAddr,
1044
+ remoteAddr,
1045
+ lchandler,
1046
+ protocolHandler,
1047
+ ),
1048
+ );
1049
+
1050
+ return watch(connectVow, this.facets.binderOutboundConnectWatcher, {
1051
+ lastFailure,
1052
+ remoteAddr,
1053
+ localAddr,
1054
+ lchandler,
1055
+ port,
1056
+ });
744
1057
  },
745
1058
  },
746
- binder: {
747
- /** @param {string} localAddr */
748
- async bind(localAddr) {
1059
+ binderOutboundInboundWatcher: {
1060
+ onFulfilled(initialLocalAddress, watchContext) {
1061
+ const { remoteAddr, localAddr } = watchContext;
1062
+
1063
+ if (initialLocalAddress === undefined) {
1064
+ initialLocalAddress = localAddr;
1065
+ }
1066
+
1067
+ // Attempt the loopback connection.
1068
+ return this.facets.protocolImpl.inbound(
1069
+ remoteAddr,
1070
+ initialLocalAddress,
1071
+ );
1072
+ },
1073
+ },
1074
+ binderOutboundAcceptWatcher: {
1075
+ onFulfilled(attempt, watchContext) {
1076
+ const { handler } = watchContext;
1077
+ return E(attempt).accept({ handler });
1078
+ },
1079
+ },
1080
+ binderBindGeneratePortWatcher: {
1081
+ onFulfilled(portID, watchContext) {
1082
+ const { localAddr, underspecified } = watchContext;
1083
+ const { protocolHandler, boundPorts } = this.state;
1084
+
1085
+ if (!underspecified) {
1086
+ return localAddr;
1087
+ }
1088
+
1089
+ const newAddr = `${localAddr}${portID}`;
1090
+ if (!boundPorts.has(newAddr)) {
1091
+ return newAddr;
1092
+ }
1093
+ return watch(
1094
+ E(protocolHandler).generatePortID(localAddr, protocolHandler),
1095
+ this.facets.binderBindGeneratePortWatcher,
1096
+ watchContext,
1097
+ );
1098
+ },
1099
+ },
1100
+ binderPortWatcher: {
1101
+ onFulfilled(_value, watchContext) {
1102
+ const { port, localAddr } = watchContext;
1103
+ const { boundPorts, currentConnections } = this.state;
1104
+
1105
+ boundPorts.init(localAddr, port);
1106
+ currentConnections.init(
1107
+ port,
1108
+ zone.detached().setStore('connections'),
1109
+ );
1110
+ return port;
1111
+ },
1112
+ },
1113
+ binderBindWatcher: {
1114
+ onFulfilled(localAddr) {
749
1115
  const {
750
- protocolHandler,
751
1116
  boundPorts,
752
1117
  listening,
753
1118
  openConnections,
754
1119
  currentConnections,
1120
+ protocolHandler,
755
1121
  } = this.state;
756
1122
 
757
- // Check if we are underspecified (ends in slash)
758
- const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
759
- for await (const _ of whileTrue(() => underspecified)) {
760
- const portID = await when(
761
- E(protocolHandler).generatePortID(localAddr, protocolHandler),
762
- );
763
- const newAddr = `${localAddr}${portID}`;
764
- if (!boundPorts.has(newAddr)) {
765
- localAddr = newAddr;
766
- break;
767
- }
768
- }
769
-
770
- this.state.localAddr = localAddr;
771
-
772
1123
  if (boundPorts.has(localAddr)) {
773
1124
  return boundPorts.get(localAddr);
774
1125
  }
@@ -783,17 +1134,38 @@ const prepareBinder = (zone, powers) => {
783
1134
  protocolImpl: this.facets.protocolImpl,
784
1135
  });
785
1136
 
786
- await when(
1137
+ return watch(
787
1138
  E(protocolHandler).onBind(port, localAddr, protocolHandler),
1139
+ this.facets.binderPortWatcher,
1140
+ {
1141
+ port,
1142
+ localAddr,
1143
+ },
788
1144
  );
789
- boundPorts.init(localAddr, harden(port));
790
- currentConnections.init(port, detached.setStore('connections'));
791
- return port;
1145
+ },
1146
+ },
1147
+ rethrowUnlessMissingWatcher: {
1148
+ onRejected(e) {
1149
+ rethrowUnlessMissing(e);
792
1150
  },
793
1151
  },
794
1152
  },
795
1153
  );
796
1154
 
1155
+ const makeBinderKit = ({
1156
+ currentConnections,
1157
+ boundPorts,
1158
+ listening,
1159
+ protocolHandler,
1160
+ }) => {
1161
+ const { protocolImpl, binder } = makeFullBinderKit({
1162
+ currentConnections,
1163
+ boundPorts,
1164
+ listening,
1165
+ protocolHandler,
1166
+ });
1167
+ return harden({ protocolImpl, binder });
1168
+ };
797
1169
  return makeBinderKit;
798
1170
  };
799
1171
 
@@ -805,7 +1177,7 @@ export const prepareNetworkProtocol = (zone, powers) => {
805
1177
  const makeBinderKit = prepareBinder(zone, powers);
806
1178
 
807
1179
  /**
808
- * @param {ProtocolHandler} protocolHandler
1180
+ * @param {import('@agoric/vow').Remote<ProtocolHandler>} protocolHandler
809
1181
  * @returns {Protocol}
810
1182
  */
811
1183
  const makeNetworkProtocol = protocolHandler => {
@@ -817,7 +1189,7 @@ export const prepareNetworkProtocol = (zone, powers) => {
817
1189
  /** @type {MapStore<string, Port>} */
818
1190
  const boundPorts = detached.mapStore('addrToPort');
819
1191
 
820
- /** @type {MapStore<Endpoint, [Port, ListenHandler]>} */
1192
+ /** @type {MapStore<Endpoint, [Port, import('@agoric/vow').Remote<Required<ListenHandler>>]>} */
821
1193
  const listening = detached.mapStore('listening');
822
1194
 
823
1195
  const { binder, protocolImpl } = makeBinderKit({
@@ -846,14 +1218,14 @@ export const prepareEchoConnectionKit = zone => {
846
1218
  {
847
1219
  handler: M.interface('ConnectionHandler', {
848
1220
  onReceive: M.callWhen(
849
- Shape2.Connection,
850
- Shape2.Bytes,
851
- Shape2.ConnectionHandler,
1221
+ Shape.Connection,
1222
+ Shape.Bytes,
1223
+ Shape.ConnectionHandler,
852
1224
  )
853
- .optional(Shape2.Opts)
854
- .returns(Shape2.Data),
855
- onClose: M.callWhen(Shape2.Connection)
856
- .optional(M.any(), Shape2.ConnectionHandler)
1225
+ .optional(Shape.Opts)
1226
+ .returns(Shape.Data),
1227
+ onClose: M.callWhen(Shape.Connection)
1228
+ .optional(M.any(), Shape.ConnectionHandler)
857
1229
  .returns(M.undefined()),
858
1230
  }),
859
1231
  listener: M.interface('Listener', {
@@ -869,10 +1241,9 @@ export const prepareEchoConnectionKit = zone => {
869
1241
  }),
870
1242
  },
871
1243
  () => {
872
- /** @type {string | undefined} */
873
- let closed;
874
1244
  return {
875
- closed,
1245
+ /** @type {string | undefined} */
1246
+ closed: undefined,
876
1247
  };
877
1248
  },
878
1249
  {
@@ -886,7 +1257,7 @@ export const prepareEchoConnectionKit = zone => {
886
1257
  const { closed } = this.state;
887
1258
 
888
1259
  if (closed) {
889
- throw closed;
1260
+ throw Error(closed);
890
1261
  }
891
1262
  return bytes;
892
1263
  },
@@ -907,7 +1278,7 @@ export const prepareEchoConnectionKit = zone => {
907
1278
  },
908
1279
  listener: {
909
1280
  async onAccept(_port, _localAddr, _remoteAddr, _listenHandler) {
910
- return harden(this.facets.handler);
1281
+ return this.facets.handler;
911
1282
  },
912
1283
  async onListen(port, _listenHandler) {
913
1284
  console.debug(`listening on echo port: ${port}`);
@@ -925,99 +1296,225 @@ export const prepareEchoConnectionKit = zone => {
925
1296
  * @param {import('@agoric/base-zone').Zone} zone
926
1297
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
927
1298
  */
928
- export function prepareLoopbackProtocolHandler(zone, { when }) {
1299
+ export function prepareLoopbackProtocolHandler(zone, { watch, allVows }) {
929
1300
  const detached = zone.detached();
930
1301
 
931
- const makeLoopbackProtocolHandler = zone.exoClass(
1302
+ /** @param {string} [instancePrefix] */
1303
+ const initHandler = (instancePrefix = 'nonce/') => {
1304
+ /** @type {MapStore<string, [import('@agoric/vow').Remote<Port>, import('@agoric/vow').Remote<Required<ListenHandler>>]>} */
1305
+ const listeners = detached.mapStore('localAddr');
1306
+
1307
+ return {
1308
+ listeners,
1309
+ portNonce: 0n,
1310
+ instancePrefix,
1311
+ instanceNonce: 0n,
1312
+ };
1313
+ };
1314
+
1315
+ const makeLoopbackProtocolHandlerKit = zone.exoClassKit(
932
1316
  'ProtocolHandler',
933
1317
  Shape.ProtocolHandlerI,
934
1318
  /** @param {string} [instancePrefix] */
935
- (instancePrefix = 'nonce/') => {
936
- /** @type {MapStore<string, [Port, ListenHandler]>} */
937
- const listeners = detached.mapStore('localAddr');
938
-
939
- return {
940
- listeners,
941
- portNonce: 0n,
942
- instancePrefix,
943
- instanceNonce: 0n,
944
- };
945
- },
1319
+ initHandler,
946
1320
  {
947
- async onCreate(_impl, _protocolHandler) {
948
- // noop
949
- },
950
- async generatePortID(_localAddr, _protocolHandler) {
951
- this.state.portNonce += 1n;
952
- return `port${this.state.portNonce}`;
1321
+ protocolHandler: {
1322
+ async onCreate(_impl, _protocolHandler) {
1323
+ // noop
1324
+ },
1325
+ async generatePortID(_localAddr, _protocolHandler) {
1326
+ this.state.portNonce += 1n;
1327
+ return `port${this.state.portNonce}`;
1328
+ },
1329
+ async onBind(_port, _localAddr, _protocolHandler) {
1330
+ // noop, for now; Maybe handle a bind?
1331
+ },
1332
+ /**
1333
+ * @param {*} _port
1334
+ * @param {Endpoint} localAddr
1335
+ * @param {Endpoint} remoteAddr
1336
+ * @returns {import('@agoric/vow').PromiseVow<AttemptDescription>}}
1337
+ */
1338
+ async onConnect(_port, localAddr, remoteAddr) {
1339
+ const { listeners } = this.state;
1340
+ const [lport, lhandler] = listeners.get(remoteAddr);
1341
+
1342
+ const acceptVow = watch(
1343
+ E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler),
1344
+ this.facets.protocolHandlerAcceptWatcher,
1345
+ );
1346
+
1347
+ const instantiateInnerVow = watch(
1348
+ E(this.facets.protocolHandler).onInstantiate(
1349
+ lport,
1350
+ remoteAddr,
1351
+ localAddr,
1352
+ this.facets.protocolHandler,
1353
+ ),
1354
+ this.facets.protocolHandlerInstantiateWatcher,
1355
+ );
1356
+
1357
+ const instantiateVow = watch(
1358
+ instantiateInnerVow,
1359
+ this.facets.rethrowUnlessMissingWatcher,
1360
+ );
1361
+ return watch(
1362
+ allVows([acceptVow, instantiateVow]),
1363
+ this.facets.protocolHandlerConnectWatcher,
1364
+ );
1365
+ },
1366
+ async onInstantiate(_port, _localAddr, _remote, _protocol) {
1367
+ const { instancePrefix } = this.state;
1368
+ this.state.instanceNonce += 1n;
1369
+ return `${instancePrefix}${this.state.instanceNonce}`;
1370
+ },
1371
+ async onListen(port, localAddr, listenHandler, _protocolHandler) {
1372
+ const { listeners } = this.state;
1373
+
1374
+ // This implementation has a simple last-one-wins replacement policy.
1375
+ // Other handlers might use different policies.
1376
+ if (listeners.has(localAddr)) {
1377
+ const lhandler = listeners.get(localAddr)[1];
1378
+ if (lhandler !== listenHandler) {
1379
+ listeners.set(
1380
+ localAddr,
1381
+ harden([
1382
+ port,
1383
+ /** @type {import('@agoric/vow').Remote<Required<ListenHandler>>} */ (
1384
+ listenHandler
1385
+ ),
1386
+ ]),
1387
+ );
1388
+ }
1389
+ } else {
1390
+ listeners.init(
1391
+ localAddr,
1392
+ harden([
1393
+ port,
1394
+ /** @type {import('@agoric/vow').Remote<Required<ListenHandler>>} */ (
1395
+ listenHandler
1396
+ ),
1397
+ ]),
1398
+ );
1399
+ }
1400
+ },
1401
+ /**
1402
+ * @param {import('@agoric/vow').Remote<Port>} port
1403
+ * @param {Endpoint} localAddr
1404
+ * @param {import('@agoric/vow').Remote<ListenHandler>} listenHandler
1405
+ * @param {*} _protocolHandler
1406
+ */
1407
+ async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
1408
+ const { listeners } = this.state;
1409
+ const [lport, lhandler] = listeners.get(localAddr);
1410
+ lport === port || Fail`Port does not match listener on ${localAddr}`;
1411
+ lhandler === listenHandler ||
1412
+ Fail`Listen handler does not match listener on ${localAddr}`;
1413
+ listeners.delete(localAddr);
1414
+ },
1415
+ async onRevoke(_port, _localAddr, _protocolHandler) {
1416
+ // This is an opportunity to clean up resources.
1417
+ },
953
1418
  },
954
- async onBind(_port, _localAddr, _protocolHandler) {
955
- // noop, for now; Maybe handle a bind?
1419
+ protocolHandlerAcceptWatcher: {
1420
+ onFulfilled(rchandler) {
1421
+ return rchandler;
1422
+ },
956
1423
  },
957
- async onConnect(
958
- _port,
959
- localAddr,
960
- remoteAddr,
961
- _chandler,
962
- protocolHandler,
963
- ) {
964
- const { listeners } = this.state;
965
- const [lport, lhandler] = listeners.get(remoteAddr);
966
- const rchandler = await when(
967
- E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler),
968
- );
969
- // console.log(`rchandler is`, rchandler);
970
- const remoteInstance = await when(
971
- E(protocolHandler).onInstantiate(
972
- lport,
973
- remoteAddr,
974
- localAddr,
975
- protocolHandler,
976
- ),
977
- ).catch(rethrowUnlessMissing);
978
- return {
979
- remoteInstance,
980
- handler: rchandler,
981
- };
982
- },
983
- async onInstantiate(_port, _localAddr, _remote, _protocol) {
984
- const { instancePrefix } = this.state;
985
- this.state.instanceNonce += 1n;
986
- return `${instancePrefix}${this.state.instanceNonce}`;
987
- },
988
- async onListen(port, localAddr, listenHandler, _protocolHandler) {
989
- const { listeners } = this.state;
990
-
991
- // This implementation has a simple last-one-wins replacement policy.
992
- // Other handlers might use different policies.
993
- if (listeners.has(localAddr)) {
994
- const lhandler = listeners.get(localAddr)[1];
995
- if (lhandler !== listenHandler) {
996
- listeners.set(localAddr, [port, listenHandler]);
997
- }
998
- } else {
999
- listeners.init(localAddr, harden([port, listenHandler]));
1000
- }
1424
+ protocolHandlerConnectWatcher: {
1425
+ onFulfilled(results) {
1426
+ return {
1427
+ remoteInstance: results[0],
1428
+ handler: results[1],
1429
+ };
1430
+ },
1001
1431
  },
1002
- /**
1003
- * @param {Port} port
1004
- * @param {Endpoint} localAddr
1005
- * @param {ListenHandler} listenHandler
1006
- * @param {*} _protocolHandler
1007
- */
1008
- async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
1009
- const { listeners } = this.state;
1010
- const [lport, lhandler] = listeners.get(localAddr);
1011
- lport === port || Fail`Port does not match listener on ${localAddr}`;
1012
- lhandler === listenHandler ||
1013
- Fail`Listen handler does not match listener on ${localAddr}`;
1014
- listeners.delete(localAddr);
1432
+ protocolHandlerInstantiateWatcher: {
1433
+ onFulfilled(remoteInstance) {
1434
+ return remoteInstance;
1435
+ },
1015
1436
  },
1016
- async onRevoke(_port, _localAddr, _protocolHandler) {
1017
- // This is an opportunity to clean up resources.
1437
+ rethrowUnlessMissingWatcher: {
1438
+ onRejected(e) {
1439
+ rethrowUnlessMissing(e);
1440
+ },
1018
1441
  },
1019
1442
  },
1020
1443
  );
1021
1444
 
1445
+ /** @param {string} [instancePrefix] */
1446
+ const makeLoopbackProtocolHandler = instancePrefix => {
1447
+ const { protocolHandler } = makeLoopbackProtocolHandlerKit(instancePrefix);
1448
+ return harden(protocolHandler);
1449
+ };
1450
+
1022
1451
  return makeLoopbackProtocolHandler;
1023
1452
  }
1453
+
1454
+ /**
1455
+ *
1456
+ * @param {import('@agoric/base-zone').Zone} zone
1457
+ * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
1458
+ */
1459
+ export const preparePortAllocator = (zone, { watch }) =>
1460
+ zone.exoClass(
1461
+ 'PortAllocator',
1462
+ M.interface('PortAllocator', {
1463
+ allocateCustomIBCPort: M.callWhen()
1464
+ .optional(M.string())
1465
+ .returns(Shape.Vow$(Shape.Port)),
1466
+ allocateICAControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)),
1467
+ allocateICQControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)),
1468
+ allocateCustomLocalPort: M.callWhen()
1469
+ .optional(M.string())
1470
+ .returns(Shape.Vow$(Shape.Port)),
1471
+ }),
1472
+ ({ protocol }) => ({ protocol, lastICAPortNum: 0n, lastICQPortNum: 0n }),
1473
+ {
1474
+ allocateCustomIBCPort(specifiedName = '') {
1475
+ const { state } = this;
1476
+ let localAddr = `/ibc-port/`;
1477
+
1478
+ if (specifiedName) {
1479
+ throwIfInvalidPortName(specifiedName);
1480
+
1481
+ localAddr = `/ibc-port/custom-${specifiedName}`;
1482
+ }
1483
+
1484
+ // Allocate an IBC port with a unique generated name.
1485
+ return watch(E(state.protocol).bindPort(localAddr));
1486
+ },
1487
+ allocateICAControllerPort() {
1488
+ const { state } = this;
1489
+ state.lastICAPortNum += 1n;
1490
+ return watch(
1491
+ E(state.protocol).bindPort(
1492
+ `/ibc-port/icacontroller-${state.lastICAPortNum}`,
1493
+ ),
1494
+ );
1495
+ },
1496
+ allocateICQControllerPort() {
1497
+ const { state } = this;
1498
+ state.lastICQPortNum += 1n;
1499
+ return watch(
1500
+ E(state.protocol).bindPort(
1501
+ `/ibc-port/icqcontroller-${state.lastICQPortNum}`,
1502
+ ),
1503
+ );
1504
+ },
1505
+ allocateCustomLocalPort(specifiedName = '') {
1506
+ const { state } = this;
1507
+
1508
+ let localAddr = `/local/`;
1509
+
1510
+ if (specifiedName) {
1511
+ throwIfInvalidPortName(specifiedName);
1512
+
1513
+ localAddr = `/local/custom-${specifiedName}`;
1514
+ }
1515
+
1516
+ // Allocate a local port with a unique generated name.
1517
+ return watch(E(state.protocol).bindPort(localAddr));
1518
+ },
1519
+ },
1520
+ );