@agoric/vats 0.15.2-dev-7ffae88.0 → 0.15.2-dev-c5284e4.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.
Files changed (48) hide show
  1. package/package.json +24 -24
  2. package/src/core/chain-behaviors.d.ts.map +1 -1
  3. package/src/core/chain-behaviors.js +1 -7
  4. package/src/core/client-behaviors.d.ts +1 -0
  5. package/src/core/client-behaviors.d.ts.map +1 -1
  6. package/src/core/client-behaviors.js +1 -0
  7. package/src/core/startWalletFactory.d.ts +4 -4
  8. package/src/core/types-ambient.d.ts +1 -0
  9. package/src/core/utils.d.ts +2 -2
  10. package/src/core/utils.d.ts.map +1 -1
  11. package/src/core/utils.js +13 -13
  12. package/src/ibc.d.ts +63 -25
  13. package/src/ibc.d.ts.map +1 -1
  14. package/src/ibc.js +671 -457
  15. package/src/localchain.d.ts +22 -0
  16. package/src/localchain.d.ts.map +1 -0
  17. package/src/localchain.js +106 -0
  18. package/src/proposals/localchain-proposal.d.ts +405 -0
  19. package/src/proposals/localchain-proposal.d.ts.map +1 -0
  20. package/src/proposals/localchain-proposal.js +114 -0
  21. package/src/proposals/localchain-test.d.ts +25 -0
  22. package/src/proposals/localchain-test.d.ts.map +1 -0
  23. package/src/proposals/localchain-test.js +81 -0
  24. package/src/proposals/network-proposal.d.ts +42 -1
  25. package/src/proposals/network-proposal.d.ts.map +1 -1
  26. package/src/proposals/network-proposal.js +58 -42
  27. package/src/proposals/probeZcfBundle.d.ts +26 -0
  28. package/src/proposals/probeZcfBundle.d.ts.map +1 -0
  29. package/src/proposals/probeZcfBundle.js +72 -0
  30. package/src/proposals/restart-vats-proposal.d.ts +2 -0
  31. package/src/proposals/restart-vats-proposal.d.ts.map +1 -1
  32. package/src/proposals/restart-vats-proposal.js +13 -1
  33. package/src/proposals/zcf-proposal.d.ts.map +1 -1
  34. package/src/proposals/zcf-proposal.js +1 -0
  35. package/src/vat-bridge.d.ts +16 -2
  36. package/src/vat-bridge.d.ts.map +1 -1
  37. package/src/vat-ibc.d.ts +43 -3
  38. package/src/vat-ibc.d.ts.map +1 -1
  39. package/src/vat-ibc.js +20 -9
  40. package/src/vat-localchain.d.ts +33 -0
  41. package/src/vat-localchain.d.ts.map +1 -0
  42. package/src/vat-localchain.js +24 -0
  43. package/src/vat-network.d.ts +67 -1
  44. package/src/vat-network.d.ts.map +1 -1
  45. package/src/vat-network.js +39 -3
  46. package/tools/boot-test-utils.d.ts +8 -2
  47. package/tools/boot-test-utils.d.ts.map +1 -1
  48. package/tools/boot-test-utils.js +2 -1
package/src/ibc.js CHANGED
@@ -1,9 +1,8 @@
1
- import { makeScalarMapStore, makeLegacyMap } from '@agoric/store';
2
- import { makePromiseKit } from '@endo/promise-kit';
1
+ // @ts-check
2
+
3
3
  import { assert, details as X, Fail } from '@agoric/assert';
4
- import { Far } from '@endo/far';
4
+ import { E } from '@endo/far';
5
5
 
6
- import { makeWithQueue } from '@agoric/internal/src/queue.js';
7
6
  import { dataToBase64, base64ToBytes } from '@agoric/network';
8
7
 
9
8
  import '@agoric/store/exported.js';
@@ -35,90 +34,57 @@ const DEFAULT_PACKET_TIMEOUT_NS = 10n * 60n * 1_000_000_000n;
35
34
  * @property {IBCPortID} destination_port
36
35
  */
37
36
 
37
+ /** @param {import('@agoric/base-zone').Zone} zone */
38
+ const prepareAckWatcher = zone => {
39
+ const makeAckWatcher = zone.exoClass(
40
+ 'AckWatcher',
41
+ undefined,
42
+ (protocolUtils, packet) => ({ protocolUtils, packet }),
43
+ {
44
+ onFulfilled(ack) {
45
+ const { protocolUtils, packet } = this.state;
46
+
47
+ const realAck = ack || DEFAULT_ACKNOWLEDGEMENT;
48
+ const ack64 = dataToBase64(realAck);
49
+ protocolUtils
50
+ .downcall('receiveExecuted', {
51
+ packet,
52
+ ack: ack64,
53
+ })
54
+ .catch(e => this.self.onRejected(e));
55
+ },
56
+ onRejected(e) {
57
+ console.error(e);
58
+ },
59
+ },
60
+ );
61
+ return makeAckWatcher;
62
+ };
63
+
38
64
  /**
39
- * Create a handler for the IBC protocol, both from the network and from the
40
- * bridge.
65
+ * @typedef {object} Counterparty
66
+ * @property {string} port_id
67
+ * @property {string} channel_id
68
+ *
69
+ * @typedef {object} ConnectingInfo
70
+ * @property {'ORDERED' | 'UNORDERED'} order
71
+ * @property {string[]} connectionHops
72
+ * @property {string} portID
73
+ * @property {string} channelID
74
+ * @property {Counterparty} counterparty
75
+ * @property {string} version
41
76
  *
42
- * @param {typeof import('@endo/far').E} E
43
- * @param {(method: string, params: any) => Promise<any>} rawCallIBCDevice
44
- * @returns {ProtocolHandler & BridgeHandler} Protocol/Bridge handler
77
+ * @typedef {import('@agoric/vow').VowKit<AttemptDescription>} OnConnectP
78
+ *
79
+ * @typedef {Omit<ConnectingInfo, 'counterparty' | 'channelID'> & {
80
+ * localAddr: Endpoint;
81
+ * onConnectP: OnConnectP;
82
+ * counterparty: { port_id: string };
83
+ * }} Outbound
45
84
  */
46
- export function makeIBCProtocolHandler(E, rawCallIBCDevice) {
47
- // Nonce for creating port identifiers.
48
- let lastPortID = 0;
49
-
50
- const callIBCDevice = (method, params) => {
51
- console.info('IBC downcall', method, params);
52
- return rawCallIBCDevice(method, params);
53
- };
54
- /** @type {MapStore<string, Promise<Connection>>} */
55
- const channelKeyToConnP = makeScalarMapStore('CHANNEL:PORT');
56
-
57
- /**
58
- * @typedef {object} Counterparty
59
- * @property {string} port_id
60
- * @property {string} channel_id
61
- *
62
- * @typedef {object} ConnectingInfo
63
- * @property {'ORDERED' | 'UNORDERED'} order
64
- * @property {string[]} connectionHops
65
- * @property {string} portID
66
- * @property {string} channelID
67
- * @property {Counterparty} counterparty
68
- * @property {string} version
69
- *
70
- * @typedef {PromiseRecord<AttemptDescription>} OnConnectP
71
- *
72
- * @typedef {Omit<ConnectingInfo, 'counterparty' | 'channelID'> & {
73
- * localAddr: Endpoint;
74
- * onConnectP: OnConnectP;
75
- * counterparty: { port_id: string };
76
- * }} Outbound
77
- */
78
-
79
- /** @type {LegacyMap<string, Outbound[]>} */
80
- // Legacy because it holds a mutable Javascript Array
81
- const srcPortToOutbounds = makeLegacyMap('SRC-PORT');
82
-
83
- /** @type {MapStore<string, ConnectingInfo>} */
84
- const channelKeyToInfo = makeScalarMapStore('CHANNEL:PORT');
85
-
86
- /** @type {MapStore<string, Promise<InboundAttempt>>} */
87
- const channelKeyToAttemptP = makeScalarMapStore('CHANNEL:PORT');
88
-
89
- /** @type {LegacyMap<string, LegacyMap<number, PromiseRecord<Bytes>>>} */
90
- // Legacy because it holds a LegacyMap
91
- const channelKeyToSeqAck = makeLegacyMap('CHANNEL:PORT');
92
-
93
- /**
94
- * Send a packet out via the IBC device.
95
- *
96
- * @param {IBCPacket} packet
97
- * @param {LegacyMap<number, PromiseRecord<Bytes>>} seqToAck
98
- * @param {bigint} [relativeTimeoutNs]
99
- */
100
- async function ibcSendPacket(
101
- packet,
102
- seqToAck,
103
- relativeTimeoutNs = DEFAULT_PACKET_TIMEOUT_NS,
104
- ) {
105
- // Make a kernel call to do the send.
106
- const fullPacket = await callIBCDevice('sendPacket', {
107
- packet,
108
- relativeTimeoutNs,
109
- });
110
-
111
- // Extract the actual sequence number from the return.
112
- const { sequence } = fullPacket;
113
-
114
- /** @type {PromiseRecord<Bytes>} */
115
- const ackDeferred = makePromiseKit();
116
-
117
- // Register the ack resolver/rejector with this sequence number.
118
- seqToAck.init(sequence, ackDeferred);
119
- return ackDeferred.promise;
120
- }
121
85
 
86
+ /** @param {import('@agoric/base-zone').Zone} zone */
87
+ export const prepareIBCConnectionHandler = zone => {
122
88
  /**
123
89
  * @param {string} channelID
124
90
  * @param {string} portID
@@ -127,51 +93,32 @@ export function makeIBCProtocolHandler(E, rawCallIBCDevice) {
127
93
  * @param {'ORDERED' | 'UNORDERED'} order
128
94
  * @returns {ConnectionHandler}
129
95
  */
130
- function makeIBCConnectionHandler(
131
- channelID,
132
- portID,
133
- rChannelID,
134
- rPortID,
135
- order,
136
- ) {
137
- const channelKey = `${channelID}:${portID}`;
138
- // Legacy because it holds a PromiseRecord
139
- const seqToAck = makeLegacyMap('SEQUENCE');
140
- channelKeyToSeqAck.init(channelKey, seqToAck);
141
-
142
- /**
143
- * @param {Connection} _conn
144
- * @param {Bytes} packetBytes
145
- * @param {ConnectionHandler} _handler
146
- * @param {object} root0
147
- * @param {bigint} [root0.relativeTimeoutNs]
148
- * @returns {Promise<Bytes>} Acknowledgement data
149
- */
150
- let onReceive = async (
151
- _conn,
152
- packetBytes,
153
- _handler,
154
- { relativeTimeoutNs } = {},
96
+ const makeIBCConnectionHandler = zone.exoClass(
97
+ 'IBCConnectionHandler',
98
+ undefined,
99
+ (
100
+ { protocolUtils, channelKeyToConnP, channelKeyToSeqAck },
101
+ { channelID, portID, rChannelID, rPortID, order },
155
102
  ) => {
156
- // console.error(`Remote IBC Handler ${portID} ${channelID}`);
157
- const packet = {
158
- source_port: portID,
159
- source_channel: channelID,
160
- destination_port: rPortID,
161
- destination_channel: rChannelID,
162
- data: dataToBase64(packetBytes),
163
- };
164
- return ibcSendPacket(packet, seqToAck, relativeTimeoutNs);
165
- };
166
-
167
- if (order === 'ORDERED') {
168
- // We set up a queue on the receiver to enforce ordering.
169
- const withChannelReceiveQueue = makeWithQueue();
170
- onReceive = withChannelReceiveQueue(onReceive);
171
- }
103
+ const channelKey = `${channelID}:${portID}`;
104
+ const seqToAck = zone.detached().mapStore('seqToAck');
105
+ channelKeyToSeqAck.init(channelKey, seqToAck);
172
106
 
173
- return Far('IBCConnectionHandler', {
107
+ return {
108
+ protocolUtils,
109
+ channelID,
110
+ portID,
111
+ rChannelID,
112
+ rPortID,
113
+ order,
114
+ channelKeyToConnP,
115
+ channelKeyToSeqAck,
116
+ };
117
+ },
118
+ {
174
119
  async onOpen(conn, localAddr, remoteAddr, _handler) {
120
+ const { channelID, portID, channelKeyToConnP } = this.state;
121
+
175
122
  console.debug(
176
123
  'onOpen Remote IBC Connection',
177
124
  channelID,
@@ -179,385 +126,652 @@ export function makeIBCProtocolHandler(E, rawCallIBCDevice) {
179
126
  localAddr,
180
127
  remoteAddr,
181
128
  );
182
- const connP = E.resolve(conn);
183
- channelKeyToConnP.init(channelKey, connP);
129
+ const channelKey = `${channelID}:${portID}`;
130
+
131
+ channelKeyToConnP.init(channelKey, conn);
132
+ },
133
+ /**
134
+ * @param {Connection} _conn
135
+ * @param {Bytes} packetBytes
136
+ * @param {ConnectionHandler} _handler
137
+ * @param {object} root0
138
+ * @param {bigint} [root0.relativeTimeoutNs]
139
+ * @returns {PromiseVow<Bytes>} Acknowledgement data
140
+ */
141
+ async onReceive(
142
+ _conn,
143
+ packetBytes,
144
+ _handler,
145
+ { relativeTimeoutNs } = {},
146
+ ) {
147
+ const { portID, channelID, rPortID, rChannelID } = this.state;
148
+ const { protocolUtils } = this.state;
149
+ // console.error(`Remote IBC Handler ${portID} ${channelID}`);
150
+ const packet = {
151
+ source_port: portID,
152
+ source_channel: channelID,
153
+ destination_port: rPortID,
154
+ destination_channel: rChannelID,
155
+ data: dataToBase64(packetBytes),
156
+ };
157
+ return protocolUtils.ibcSendPacket(packet, relativeTimeoutNs);
184
158
  },
185
- onReceive,
186
159
  async onClose(_conn, _reason, _handler) {
160
+ const { portID, channelID } = this.state;
161
+ const { protocolUtils, channelKeyToSeqAck } = this.state;
162
+
187
163
  const packet = {
188
164
  source_port: portID,
189
165
  source_channel: channelID,
190
166
  };
191
- await callIBCDevice('startChannelCloseInit', { packet });
167
+ await protocolUtils.downcall('startChannelCloseInit', {
168
+ packet,
169
+ });
192
170
  const rejectReason = Error('Connection closed');
193
- for (const ackDeferred of seqToAck.values()) {
194
- ackDeferred.reject(rejectReason);
171
+ const channelKey = `${channelID}:${portID}`;
172
+
173
+ const seqToAck = channelKeyToSeqAck.get(channelKey);
174
+
175
+ for (const ackKit of seqToAck.values()) {
176
+ ackKit.resolver.reject(rejectReason);
195
177
  }
196
178
  channelKeyToSeqAck.delete(channelKey);
197
179
  },
198
- });
199
- }
200
-
201
- /** @param {string} localAddr */
202
- const localAddrToPortID = localAddr => {
203
- const m = localAddr.match(/^\/ibc-port\/([-a-zA-Z0-9._+#[\]<>]+)$/);
204
- if (!m) {
205
- throw TypeError(
206
- `Invalid port specification ${localAddr}; expected "/ibc-port/PORT"`,
207
- );
208
- }
209
- return m[1];
210
- };
180
+ },
181
+ );
182
+
183
+ return makeIBCConnectionHandler;
184
+ };
211
185
 
212
- /** @type {ProtocolImpl} */
213
- let protocolImpl;
186
+ /**
187
+ * @param {import('@agoric/base-zone').Zone} zone
188
+ * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
189
+ */
190
+ export const prepareIBCProtocol = (zone, { makeVowKit, watch, when }) => {
191
+ const makeAckWatcher = prepareAckWatcher(zone);
192
+ const makeIBCConnectionHandler = prepareIBCConnectionHandler(zone);
214
193
 
215
194
  /**
216
- * @typedef {object} OutboundCircuitRecord
217
- * @property {IBCConnectionID} dst
218
- * @property {'ORDERED' | 'UNORDERED'} order
219
- * @property {string} version
220
- * @property {IBCPacket} packet
221
- * @property {PromiseRecord<ConnectionHandler>} deferredHandler
195
+ * @typedef {object} IBCDevice
196
+ * @property {(method: string, args: object) => Promise<any>} downcall
222
197
  */
223
198
 
224
- /** @type {LegacyMap<Port, Set<PromiseRecord<ConnectionHandler>>>} */
225
- // Legacy because it holds a raw JavaScript Set
226
- const portToPendingConns = makeLegacyMap('Port');
199
+ /**
200
+ * @type {WeakMapStore<
201
+ * IBCDevice,
202
+ * {
203
+ * protocolHandler: ProtocolHandler;
204
+ * bridgeHandler: BridgeHandler;
205
+ * }
206
+ * >} Map
207
+ * from IBC device to existing handler
208
+ */
209
+ const ibcdevToKit = zone.weakMapStore('ibcdevToHandler');
227
210
 
228
- /** @type {ProtocolHandler} */
229
- const protocol = Far('IBCProtocolHandler', {
230
- async onCreate(impl, _protocolHandler) {
231
- console.debug('IBC onCreate');
232
- protocolImpl = impl;
233
- },
234
- async onInstantiate() {
235
- // The IBC channel is not known until after handshake.
236
- return '';
237
- },
238
- async generatePortID(_localAddr, _protocolHandler) {
239
- lastPortID += 1;
240
- return `port-${lastPortID}`;
241
- },
242
- async onBind(port, localAddr, _protocolHandler) {
243
- const portID = localAddrToPortID(localAddr);
244
- portToPendingConns.init(port, new Set());
245
- const packet = {
246
- source_port: portID,
211
+ const detached = zone.detached();
212
+
213
+ /**
214
+ * Create a handler for the IBC protocol, both from the network and from the
215
+ * bridge.
216
+ *
217
+ * @param {IBCDevice} ibcdev
218
+ */
219
+ const makeIBCProtocolKit = zone.exoClassKit(
220
+ 'IBCProtocolHandler',
221
+ undefined,
222
+ ibcdev => {
223
+ /** @type {MapStore<string, Connection>} */
224
+ const channelKeyToConnP = detached.mapStore('channelKeyToConnP');
225
+
226
+ /** @type {MapStore<string, ConnectingInfo>} */
227
+ const channelKeyToInfo = detached.mapStore('channelKeyToInfo');
228
+
229
+ /** @type {MapStore<string, InboundAttempt>} */
230
+ const channelKeyToAttempt = detached.mapStore('channelKeyToAttempt');
231
+
232
+ /** @type {MapStore<string, Outbound[]>} */
233
+ const srcPortToOutbounds = detached.mapStore('srcPortToOutbounds');
234
+
235
+ /**
236
+ * @type {MapStore<
237
+ * string,
238
+ * MapStore<number, import('@agoric/vow').VowKit<Bytes>>
239
+ * >}
240
+ */
241
+ const channelKeyToSeqAck = detached.mapStore('channelKeyToSeqAck');
242
+
243
+ /**
244
+ * @type {MapStore<
245
+ * string,
246
+ * SetStore<import('@agoric/vow').VowResolver>
247
+ * >}
248
+ */
249
+ const portToPendingConns = detached.mapStore('portToPendingConns');
250
+
251
+ return {
252
+ ibcdev,
253
+ channelKeyToConnP,
254
+ channelKeyToInfo,
255
+ channelKeyToAttempt,
256
+ srcPortToOutbounds,
257
+ channelKeyToSeqAck,
258
+ portToPendingConns,
259
+ lastPortID: 0, // Nonce for creating port identifiers.
260
+ /** @type {ProtocolImpl | undefined} */ protocolImpl: undefined,
247
261
  };
248
- return callIBCDevice('bindPort', { packet });
249
262
  },
250
- async onConnect(port, localAddr, remoteAddr, _chandler, _protocolHandler) {
251
- console.debug('IBC onConnect', localAddr, remoteAddr);
252
- const portID = localAddrToPortID(localAddr);
253
- const pendingConns = portToPendingConns.get(port);
254
-
255
- const match = remoteAddr.match(
256
- /^(\/ibc-hop\/[^/]+)*\/ibc-port\/([^/]+)\/(ordered|unordered)\/([^/]+)$/s,
257
- );
258
- if (!match) {
259
- throw TypeError(
260
- `Remote address ${remoteAddr} must be '(/ibc-hop/CONNECTION)*/ibc-port/PORT/(ordered|unordered)/VERSION'`,
261
- );
262
- }
263
-
264
- const hops = [];
265
- let h = match[1];
266
- while (h) {
267
- const m = h.match(/^\/ibc-hop\/([^/]+)/);
268
- if (!m) {
269
- throw Error(
270
- `internal: ${JSON.stringify(h)} did not begin with "/ibc-hop/XXX"`,
263
+ {
264
+ protocolHandler: {
265
+ async onCreate(impl, _protocolHandler) {
266
+ console.debug('IBC onCreate');
267
+ this.state.protocolImpl = impl;
268
+ },
269
+ async onInstantiate() {
270
+ // The IBC channel is not known until after handshake.
271
+ return '';
272
+ },
273
+ async generatePortID(_localAddr, _protocolHandler) {
274
+ this.state.lastPortID += 1;
275
+ return `port-${this.state.lastPortID}`;
276
+ },
277
+ async onBind(port, localAddr, _protocolHandler) {
278
+ const { util } = this.facets;
279
+ const { portToPendingConns } = this.state;
280
+
281
+ const portID = util.localAddrToPortID(localAddr);
282
+ portToPendingConns.init(portID, detached.setStore('pendingConns'));
283
+ const packet = {
284
+ source_port: portID,
285
+ };
286
+ return util.downcall('bindPort', { packet });
287
+ },
288
+ async onConnect(
289
+ port,
290
+ localAddr,
291
+ remoteAddr,
292
+ _chandler,
293
+ _protocolHandler,
294
+ ) {
295
+ const { util } = this.facets;
296
+ const { portToPendingConns, srcPortToOutbounds } = this.state;
297
+
298
+ console.debug('IBC onConnect', localAddr, remoteAddr);
299
+ const portID = util.localAddrToPortID(localAddr);
300
+ const pendingConns = portToPendingConns.get(portID);
301
+
302
+ const match = remoteAddr.match(
303
+ /^(\/ibc-hop\/[^/]+)*\/ibc-port\/([^/]+)\/(ordered|unordered)\/([^/]+)$/s,
271
304
  );
272
- }
273
- h = h.substr(m[0].length);
274
- hops.push(m[1]);
275
- }
276
-
277
- // Generate a circuit.
278
- const rPortID = match[2];
279
- const order = match[3] === 'ordered' ? 'ORDERED' : 'UNORDERED';
280
- const version = match[4];
281
-
282
- const onConnectP = makePromiseKit();
283
-
284
- pendingConns.add(onConnectP);
285
- /** @type {Outbound[]} */
286
- let outbounds;
287
- if (srcPortToOutbounds.has(portID)) {
288
- outbounds = [...srcPortToOutbounds.get(portID)];
289
- } else {
290
- outbounds = [];
291
- srcPortToOutbounds.init(portID, outbounds);
292
- }
293
- outbounds.push({
294
- portID,
295
- counterparty: { port_id: rPortID },
296
- connectionHops: hops,
297
- order,
298
- version,
299
- onConnectP,
300
- localAddr,
301
- });
302
-
303
- // Initialise the channel, which automatic relayers should pick up.
304
- const packet = {
305
- source_port: portID,
306
- destination_port: rPortID,
307
- };
305
+ if (!match) {
306
+ throw TypeError(
307
+ `Remote address ${remoteAddr} must be '(/ibc-hop/CONNECTION)*/ibc-port/PORT/(ordered|unordered)/VERSION'`,
308
+ );
309
+ }
308
310
 
309
- await callIBCDevice('startChannelOpenInit', {
310
- packet,
311
- order,
312
- hops,
313
- version,
314
- });
311
+ const hops = [];
312
+ let h = match[1];
313
+ while (h) {
314
+ const m = h.match(/^\/ibc-hop\/([^/]+)/);
315
+ if (!m) {
316
+ throw Error(
317
+ `internal: ${JSON.stringify(
318
+ h,
319
+ )} did not begin with "/ibc-hop/XXX"`,
320
+ );
321
+ }
322
+ h = h.substr(m[0].length);
323
+ hops.push(m[1]);
324
+ }
315
325
 
316
- return onConnectP.promise;
317
- },
318
- async onListen(_port, localAddr, _listenHandler) {
319
- console.debug('IBC onListen', localAddr);
320
- },
321
- async onListenRemove(_port, localAddr, _listenHandler) {
322
- console.debug('IBC onListenRemove', localAddr);
323
- },
324
- async onRevoke(port, localAddr, _protocolHandler) {
325
- console.debug('IBC onRevoke', localAddr);
326
- const pendingConns = portToPendingConns.get(port);
327
- portToPendingConns.delete(port);
328
- const revoked = Error(`Port ${localAddr} revoked`);
329
- for (const onConnectP of pendingConns.values()) {
330
- onConnectP.reject(revoked);
331
- }
332
- },
333
- });
334
-
335
- return Far('IBCProtocolHandler', {
336
- ...protocol,
337
- async fromBridge(obj) {
338
- console.info('IBC fromBridge', obj);
339
- await null;
340
- switch (obj.event) {
341
- case 'channelOpenTry': {
342
- // They're (more or less politely) asking if we are listening, so make an attempt.
343
- const {
344
- channelID,
326
+ // Generate a circuit.
327
+ const rPortID = match[2];
328
+ const order = match[3] === 'ordered' ? 'ORDERED' : 'UNORDERED';
329
+ const version = match[4];
330
+
331
+ const kit = makeVowKit();
332
+
333
+ pendingConns.add(kit.resolver);
334
+ /** @type {Outbound} */
335
+ const ob = {
345
336
  portID,
346
- counterparty: { port_id: rPortID, channel_id: rChannelID },
337
+ counterparty: { port_id: rPortID },
347
338
  connectionHops: hops,
348
339
  order,
349
340
  version,
350
- counterpartyVersion: rVersion,
351
- } = obj;
352
-
353
- const localAddr = `/ibc-port/${portID}/${order.toLowerCase()}/${version}`;
354
- const ibcHops = hops.map(hop => `/ibc-hop/${hop}`).join('/');
355
- const remoteAddr = `${ibcHops}/ibc-port/${rPortID}/${order.toLowerCase()}/${rVersion}/ibc-channel/${rChannelID}`;
356
-
357
- // See if we allow an inbound attempt for this address pair (without
358
- // rejecting).
359
- const attemptP = E(protocolImpl).inbound(localAddr, remoteAddr);
360
-
361
- // Tell what version string we negotiated.
362
- const attemptedLocal = await E(attemptP).getLocalAddress();
363
- const match = attemptedLocal.match(
364
- // Match: ... /ORDER/VERSION ...
365
- new RegExp('^(/[^/]+/[^/]+)*/(ordered|unordered)/([^/]+)(/|$)'),
366
- );
367
-
368
- const channelKey = `${channelID}:${portID}`;
369
- if (!match) {
370
- throw Error(
371
- `${channelKey}: cannot determine version from attempted local address ${attemptedLocal}`,
372
- );
341
+ onConnectP: kit,
342
+ localAddr,
343
+ };
344
+ if (!srcPortToOutbounds.has(portID)) {
345
+ srcPortToOutbounds.init(portID, harden([ob]));
373
346
  }
374
- const negotiatedVersion = match[3];
375
347
 
376
- channelKeyToAttemptP.init(channelKey, attemptP);
377
- channelKeyToInfo.init(channelKey, obj);
348
+ // Initialise the channel, which a listening relayer should pick up.
349
+ const packet = {
350
+ source_port: portID,
351
+ destination_port: rPortID,
352
+ };
378
353
 
379
- try {
380
- if (negotiatedVersion !== version) {
381
- // Too late; the relayer gave us a version we didn't like.
382
- throw Error(
383
- `${channelKey}: negotiated version was ${negotiatedVersion}; rejecting ${version}`,
384
- );
385
- }
386
- } catch (e) {
387
- // Clean up after our failed attempt.
388
- channelKeyToAttemptP.delete(channelKey);
389
- channelKeyToInfo.delete(channelKey);
390
- void E(attemptP).close();
391
- throw e;
392
- }
393
- break;
394
- }
354
+ await util.downcall('startChannelOpenInit', {
355
+ packet,
356
+ order,
357
+ hops,
358
+ version,
359
+ });
395
360
 
396
- case 'channelOpenAck': {
397
- // Complete the pending outbound connection.
361
+ return kit.vow;
362
+ },
363
+ async onListen(_port, localAddr, _listenHandler) {
364
+ console.debug('IBC onListen', localAddr);
365
+ },
366
+ async onListenRemove(_port, localAddr, _listenHandler) {
367
+ console.debug('IBC onListenRemove', localAddr);
368
+ },
369
+ async onRevoke(_port, localAddr, _protocolHandler) {
370
+ const { util } = this.facets;
371
+ const { portToPendingConns } = this.state;
372
+
373
+ console.debug('IBC onRevoke', localAddr);
374
+ const portID = util.localAddrToPortID(localAddr);
375
+
376
+ const pendingConns = portToPendingConns.get(portID);
377
+ portToPendingConns.delete(portID);
378
+ const revoked = Error(`Port ${localAddr} revoked`);
379
+ for (const resolver of pendingConns.values()) {
380
+ resolver.reject(revoked);
381
+ }
382
+ },
383
+ },
384
+ bridgeHandler: {
385
+ async fromBridge(obj) {
398
386
  const {
399
- portID,
400
- channelID,
401
- counterparty: { port_id: rPortID, channel_id: rChannelID },
402
- counterpartyVersion: rVersion,
403
- connectionHops: rHops,
404
- } = obj;
405
- const outbounds = srcPortToOutbounds.has(portID)
406
- ? srcPortToOutbounds.get(portID)
407
- : [];
408
- const oidx = outbounds.findIndex(
409
- ({ counterparty: { port_id: iPortID }, connectionHops: iHops }) => {
410
- if (iPortID !== rPortID) {
411
- return false;
387
+ protocolImpl,
388
+ channelKeyToAttempt,
389
+ channelKeyToInfo,
390
+ srcPortToOutbounds,
391
+ channelKeyToConnP,
392
+ channelKeyToSeqAck,
393
+ } = this.state;
394
+
395
+ const { util } = this.facets;
396
+
397
+ console.info('IBC fromBridge', obj);
398
+ await null;
399
+ switch (obj.event) {
400
+ case 'channelOpenInit': {
401
+ const {
402
+ channelID,
403
+ portID,
404
+ counterparty: { port_id: rPortID, channel_id: rChannelID },
405
+ connectionHops: hops,
406
+ order,
407
+ version,
408
+ asyncVersions,
409
+ } = obj;
410
+ if (!asyncVersions) {
411
+ // Synchronous versions have already been negotiated.
412
+ break;
412
413
  }
413
- if (iHops.length !== rHops.length) {
414
- return false;
414
+
415
+ // We have async version negotiation, so we must call back before the
416
+ // channel can make progress.
417
+ // We just use the provided version without failing.
418
+ await util.downcall('initOpenExecuted', {
419
+ packet: {
420
+ source_port: portID,
421
+ source_channel: channelID,
422
+ destination_port: rPortID,
423
+ destination_channel: rChannelID,
424
+ },
425
+ order,
426
+ version,
427
+ hops,
428
+ });
429
+ break;
430
+ }
431
+ case 'channelOpenTry': {
432
+ // They're (more or less politely) asking if we are listening, so make an attempt.
433
+ const {
434
+ channelID,
435
+ portID,
436
+ counterparty: { port_id: rPortID, channel_id: rChannelID },
437
+ connectionHops: hops,
438
+ order,
439
+ version,
440
+ counterpartyVersion: rVersion,
441
+ asyncVersions,
442
+ } = obj;
443
+
444
+ const localAddr = `/ibc-port/${portID}/${order.toLowerCase()}/${version}`;
445
+ const ibcHops = hops.map(hop => `/ibc-hop/${hop}`).join('/');
446
+ const remoteAddr = `${ibcHops}/ibc-port/${rPortID}/${order.toLowerCase()}/${rVersion}/ibc-channel/${rChannelID}`;
447
+
448
+ // See if we allow an inbound attempt for this address pair (without
449
+ // rejecting).
450
+ // const attempt = await this.state.protocolImpl
451
+ // ?.inbound(localAddr, remoteAddr)
452
+ // .then(
453
+ // ack => console.info('Manual packet', ack, 'acked:', ack),
454
+ // e => console.warn('Manual packet', e, 'failed:', e),
455
+ // );
456
+
457
+ const attempt = await when(
458
+ /** @type {ProtocolImpl} */ (protocolImpl).inbound(
459
+ localAddr,
460
+ remoteAddr,
461
+ ),
462
+ );
463
+
464
+ // Tell what version string we negotiated.
465
+ const attemptedLocal = await E(attempt).getLocalAddress();
466
+ const match = attemptedLocal.match(
467
+ // Match: ... /ORDER/VERSION ...
468
+ new RegExp('^(/[^/]+/[^/]+)*/(ordered|unordered)/([^/]+)(/|$)'),
469
+ );
470
+
471
+ const channelKey = `${channelID}:${portID}`;
472
+ if (!match) {
473
+ throw Error(
474
+ `${channelKey}: cannot determine version from attempted local address ${attemptedLocal}`,
475
+ );
415
476
  }
416
- for (let i = 0; i < iHops.length; i += 1) {
417
- if (iHops[i] !== rHops[i]) {
418
- return false;
477
+ const negotiatedVersion = match[3];
478
+
479
+ channelKeyToAttempt.init(channelKey, attempt);
480
+ channelKeyToInfo.init(channelKey, obj);
481
+
482
+ try {
483
+ if (asyncVersions) {
484
+ // We have async version negotiation, so we must call back now.
485
+ await util.downcall('tryOpenExecuted', {
486
+ packet: {
487
+ source_port: rPortID,
488
+ source_channel: rChannelID,
489
+ destination_port: portID,
490
+ destination_channel: channelID,
491
+ },
492
+ order,
493
+ version: negotiatedVersion,
494
+ hops,
495
+ });
496
+ } else if (negotiatedVersion !== version) {
497
+ // Too late; the other side gave us a version we didn't like.
498
+ throw Error(
499
+ `${channelKey}: async negotiated version was ${negotiatedVersion} but synchronous version was ${version}`,
500
+ );
419
501
  }
502
+ } catch (e) {
503
+ // Clean up after our failed attempt.
504
+ channelKeyToAttempt.delete(channelKey);
505
+ channelKeyToInfo.delete(channelKey);
506
+ void E(attempt).close();
507
+ throw e;
420
508
  }
421
- return true;
422
- },
423
- );
424
- oidx >= 0 || Fail`${portID}: did not expect channelOpenAck`;
425
- const { onConnectP, localAddr, ...chanInfo } = outbounds[oidx];
426
- outbounds.splice(oidx, 1);
427
- if (outbounds.length === 0) {
428
- srcPortToOutbounds.delete(portID);
429
- }
509
+ break;
510
+ }
430
511
 
431
- // Finish the outbound connection.
432
- const ibcHops = rHops.map(hop => `/ibc-hop/${hop}`).join('/');
433
- const remoteAddress = `${ibcHops}/ibc-port/${rPortID}/${chanInfo.order.toLowerCase()}/${rVersion}/ibc-channel/${rChannelID}`;
434
- const localAddress = `${localAddr}/ibc-channel/${channelID}`;
435
- const rchandler = makeIBCConnectionHandler(
436
- channelID,
437
- portID,
438
- rChannelID,
439
- rPortID,
440
- chanInfo.order,
441
- );
442
- onConnectP.resolve({
443
- localAddress,
444
- remoteAddress,
445
- handler: rchandler,
446
- });
447
- break;
448
- }
512
+ case 'channelOpenAck': {
513
+ // Complete the pending outbound connection.
514
+ const {
515
+ portID,
516
+ channelID,
517
+ counterparty: { port_id: rPortID, channel_id: rChannelID },
518
+ counterpartyVersion: rVersion,
519
+ connectionHops: rHops,
520
+ } = obj;
521
+ const outbounds = this.state.srcPortToOutbounds.has(portID)
522
+ ? [...this.state.srcPortToOutbounds.get(portID)]
523
+ : [];
524
+ const oidx = outbounds.findIndex(
525
+ ({
526
+ counterparty: { port_id: iPortID },
527
+ connectionHops: iHops,
528
+ }) => {
529
+ if (iPortID !== rPortID) {
530
+ return false;
531
+ }
532
+ if (iHops.length !== rHops.length) {
533
+ return false;
534
+ }
535
+ for (let i = 0; i < iHops.length; i += 1) {
536
+ if (iHops[i] !== rHops[i]) {
537
+ return false;
538
+ }
539
+ }
540
+ return true;
541
+ },
542
+ );
543
+ oidx >= 0 || Fail`${portID}: did not expect channelOpenAck`;
544
+ const { onConnectP, localAddr, ...chanInfo } = outbounds[oidx];
545
+ outbounds.splice(oidx, 1);
546
+ if (outbounds.length === 0) {
547
+ srcPortToOutbounds.delete(portID);
548
+ } else {
549
+ srcPortToOutbounds.set(portID, harden(outbounds));
550
+ }
449
551
 
450
- case 'channelOpenConfirm': {
451
- const { portID, channelID } = obj;
452
- const channelKey = `${channelID}:${portID}`;
453
- channelKeyToAttemptP.has(channelKey) ||
454
- Fail`${channelKey}: did not expect channelOpenConfirm`;
455
- const attemptP = channelKeyToAttemptP.get(channelKey);
456
- channelKeyToAttemptP.delete(channelKey);
552
+ // Finish the outbound connection.
553
+ const ibcHops = rHops.map(hop => `/ibc-hop/${hop}`).join('/');
554
+ const remoteAddress = `${ibcHops}/ibc-port/${rPortID}/${chanInfo.order.toLowerCase()}/${rVersion}/ibc-channel/${rChannelID}`;
555
+ const localAddress = `${localAddr}/ibc-channel/${channelID}`;
556
+ const rchandler = makeIBCConnectionHandler(
557
+ {
558
+ protocolUtils: util,
559
+ channelKeyToConnP,
560
+ channelKeyToSeqAck,
561
+ },
562
+ {
563
+ channelID,
564
+ portID,
565
+ rChannelID,
566
+ rPortID,
567
+ order: chanInfo.order,
568
+ },
569
+ );
570
+ onConnectP.resolver.resolve({
571
+ localAddress,
572
+ remoteAddress,
573
+ handler: rchandler,
574
+ });
575
+ break;
576
+ }
457
577
 
458
- // We have the information from our inbound connection, so complete it.
459
- const {
460
- order,
461
- counterparty: { port_id: rPortID, channel_id: rChannelID },
462
- } = channelKeyToInfo.get(channelKey);
463
- channelKeyToInfo.delete(channelKey);
578
+ case 'channelOpenConfirm': {
579
+ const { portID, channelID } = obj;
580
+ const channelKey = `${channelID}:${portID}`;
581
+ channelKeyToAttempt.has(channelKey) ||
582
+ Fail`${channelKey}: did not expect channelOpenConfirm`;
583
+ const attemptP = channelKeyToAttempt.get(channelKey);
584
+ channelKeyToAttempt.delete(channelKey);
585
+
586
+ // We have the information from our inbound connection, so complete it.
587
+ const {
588
+ order,
589
+ counterparty: { port_id: rPortID, channel_id: rChannelID },
590
+ } = channelKeyToInfo.get(channelKey);
591
+ channelKeyToInfo.delete(channelKey);
592
+
593
+ // Accept the attempt.
594
+ const rchandler = makeIBCConnectionHandler(
595
+ {
596
+ protocolUtils: util,
597
+ channelKeyToConnP,
598
+ channelKeyToSeqAck,
599
+ },
600
+ {
601
+ channelID,
602
+ portID,
603
+ rChannelID,
604
+ rPortID,
605
+ order,
606
+ },
607
+ );
608
+ const localAddr = await E(attemptP).getLocalAddress();
609
+ void E(attemptP).accept({
610
+ localAddress: `${localAddr}/ibc-channel/${channelID}`,
611
+ handler: rchandler,
612
+ });
613
+ break;
614
+ }
464
615
 
465
- // Accept the attempt.
466
- const rchandler = makeIBCConnectionHandler(
467
- channelID,
468
- portID,
469
- rChannelID,
470
- rPortID,
471
- order,
472
- );
473
- const localAddr = await E(attemptP).getLocalAddress();
474
- void E(attemptP).accept({
475
- localAddress: `${localAddr}/ibc-channel/${channelID}`,
476
- handler: rchandler,
477
- });
478
- break;
479
- }
616
+ case 'receivePacket': {
617
+ const { packet } = obj;
618
+ const {
619
+ data: data64,
620
+ destination_port: portID,
621
+ destination_channel: channelID,
622
+ } = packet;
623
+ const channelKey = `${channelID}:${portID}`;
624
+ const conn = channelKeyToConnP.get(channelKey);
625
+ const data = base64ToBytes(data64);
626
+
627
+ watch(conn.send(data), makeAckWatcher(util, packet));
628
+ break;
629
+ }
480
630
 
481
- case 'receivePacket': {
482
- const { packet } = obj;
483
- const {
484
- data: data64,
485
- destination_port: portID,
486
- destination_channel: channelID,
487
- } = packet;
488
- const channelKey = `${channelID}:${portID}`;
489
- const connP = channelKeyToConnP.get(channelKey);
490
- const data = base64ToBytes(data64);
491
-
492
- await E(connP)
493
- .send(data)
494
- .then(ack => {
495
- const realAck = ack || DEFAULT_ACKNOWLEDGEMENT;
496
- const ack64 = dataToBase64(realAck);
497
- return callIBCDevice('receiveExecuted', { packet, ack: ack64 });
498
- })
499
- .catch(e => console.error(e));
500
- break;
501
- }
631
+ case 'acknowledgementPacket': {
632
+ const { packet, acknowledgement } = obj;
633
+ const {
634
+ sequence,
635
+ source_channel: channelID,
636
+ source_port: portID,
637
+ } = packet;
638
+ const ackKit = util.findAckKit(channelID, portID, sequence);
639
+ ackKit.resolver.resolve(base64ToBytes(acknowledgement));
640
+ break;
641
+ }
502
642
 
503
- case 'acknowledgementPacket': {
504
- const { packet, acknowledgement } = obj;
505
- const {
506
- sequence,
507
- source_channel: channelID,
508
- source_port: portID,
509
- } = packet;
510
- const channelKey = `${channelID}:${portID}`;
511
- const seqToAck = channelKeyToSeqAck.get(channelKey);
512
- const ackDeferred = seqToAck.get(sequence);
513
- ackDeferred.resolve(base64ToBytes(acknowledgement));
514
- seqToAck.delete(sequence);
515
- break;
516
- }
643
+ case 'timeoutPacket': {
644
+ const { packet } = obj;
645
+ const {
646
+ sequence,
647
+ source_channel: channelID,
648
+ source_port: portID,
649
+ } = packet;
650
+ const ackKit = util.findAckKit(channelID, portID, sequence);
651
+ ackKit.resolver.reject(Error(`Packet timed out`));
652
+ break;
653
+ }
654
+
655
+ case 'channelCloseInit':
656
+ case 'channelCloseConfirm': {
657
+ const { portID, channelID } = obj;
658
+ const channelKey = `${channelID}:${portID}`;
659
+ if (channelKeyToConnP.has(channelKey)) {
660
+ const conn = channelKeyToConnP.get(channelKey);
661
+ channelKeyToConnP.delete(channelKey);
662
+ void conn.close();
663
+ }
664
+ break;
665
+ }
666
+
667
+ case 'sendPacket': {
668
+ const { packet, relativeTimeoutNs } = obj;
669
+ util.ibcSendPacket(packet, relativeTimeoutNs).then(
670
+ ack => console.info('Manual packet', packet, 'acked:', ack),
671
+ e => console.warn('Manual packet', packet, 'failed:', e),
672
+ );
673
+ break;
674
+ }
675
+
676
+ default:
677
+ console.error('Unexpected IBC_EVENT', obj.event);
678
+ assert.fail(X`unrecognized method ${obj.event}`, TypeError);
679
+ }
680
+ },
681
+ },
682
+ util: {
683
+ downcall(method, args) {
684
+ return E(this.state.ibcdev).downcall(method, args);
685
+ },
686
+ /**
687
+ * Send a packet out via the IBC device.
688
+ *
689
+ * @param {IBCPacket} packet
690
+ * @param {bigint} [relativeTimeoutNs]
691
+ * @returns {PromiseVow<Bytes>} Acknowledgement data
692
+ */
693
+ async ibcSendPacket(
694
+ packet,
695
+ relativeTimeoutNs = DEFAULT_PACKET_TIMEOUT_NS,
696
+ ) {
697
+ const { util } = this.facets;
698
+ // Make a kernel call to do the send.
699
+ const fullPacket = await util.downcall('sendPacket', {
700
+ packet,
701
+ relativeTimeoutNs,
702
+ });
517
703
 
518
- case 'timeoutPacket': {
519
- const { packet } = obj;
704
+ // Extract the actual sequence number from the return.
520
705
  const {
521
706
  sequence,
522
707
  source_channel: channelID,
523
708
  source_port: portID,
524
- } = packet;
525
- const channelKey = `${channelID}:${portID}`;
526
- const seqToAck = channelKeyToSeqAck.get(channelKey);
527
- const ackDeferred = seqToAck.get(sequence);
528
- ackDeferred.reject(Error(`Packet timed out`));
529
- seqToAck.delete(sequence);
530
- break;
531
- }
532
-
533
- case 'channelCloseInit':
534
- case 'channelCloseConfirm': {
535
- const { portID, channelID } = obj;
536
- const channelKey = `${channelID}:${portID}`;
537
- if (channelKeyToConnP.has(channelKey)) {
538
- const connP = channelKeyToConnP.get(channelKey);
539
- channelKeyToConnP.delete(channelKey);
540
- void E(connP).close();
709
+ } = fullPacket;
710
+
711
+ /** @type {import('@agoric/vow').VowKit<Bytes>} */
712
+ const { vow } = util.findAckKit(channelID, portID, sequence);
713
+ return vow;
714
+ },
715
+ /** @param {string} localAddr */
716
+ localAddrToPortID(localAddr) {
717
+ const m = localAddr.match(/^\/ibc-port\/([-a-zA-Z0-9._+#[\]<>]+)$/);
718
+ if (!m) {
719
+ throw TypeError(
720
+ `Invalid port specification ${localAddr}; expected "/ibc-port/PORT"`,
721
+ );
541
722
  }
542
- break;
543
- }
723
+ return m[1];
724
+ },
544
725
 
545
- case 'sendPacket': {
546
- const { packet, relativeTimeoutNs } = obj;
547
- const { source_port: portID, source_channel: channelID } = packet;
726
+ findAckKit(channelID, portID, sequence) {
727
+ const { channelKeyToSeqAck } = this.state;
548
728
  const channelKey = `${channelID}:${portID}`;
549
729
  const seqToAck = channelKeyToSeqAck.get(channelKey);
550
- ibcSendPacket(packet, seqToAck, relativeTimeoutNs).then(
551
- ack => console.info('Manual packet', packet, 'acked:', ack),
552
- e => console.warn('Manual packet', packet, 'failed:', e),
553
- );
554
- break;
555
- }
730
+ if (seqToAck.has(sequence)) {
731
+ return seqToAck.get(sequence);
732
+ }
733
+ const kit = makeVowKit();
734
+ seqToAck.init(sequence, harden(kit));
735
+ return kit;
736
+ },
737
+ },
738
+ },
739
+ );
556
740
 
557
- default:
558
- console.error('Unexpected IBC_EVENT', obj.event);
559
- assert.fail(X`unrecognized method ${obj.event}`, TypeError);
560
- }
741
+ const makeIBCProtocolHandlerKit = ibcdev => {
742
+ const { protocolHandler, bridgeHandler } = makeIBCProtocolKit(ibcdev);
743
+ return harden({ protocolHandler, bridgeHandler });
744
+ };
745
+
746
+ const provideIBCProtocolHandlerKit = ibcdev => {
747
+ if (ibcdevToKit.has(ibcdev)) {
748
+ return ibcdevToKit.get(ibcdev);
749
+ }
750
+ const kit = makeIBCProtocolHandlerKit(ibcdev);
751
+ ibcdevToKit.init(ibcdev, kit);
752
+ return kit;
753
+ };
754
+ return provideIBCProtocolHandlerKit;
755
+ };
756
+
757
+ harden(prepareIBCProtocol);
758
+
759
+ /** @param {import('@agoric/base-zone').Zone} zone */
760
+ export const prepareCallbacks = zone => {
761
+ return zone.exoClass(
762
+ 'callbacks',
763
+ undefined,
764
+ /** @param {import('@agoric/vats').ScopedBridgeManager} dibcBridgeManager */
765
+ dibcBridgeManager => ({ dibcBridgeManager }),
766
+ {
767
+ downcall(method, obj) {
768
+ const { dibcBridgeManager } = this.state;
769
+ return E(dibcBridgeManager).toBridge({
770
+ ...obj,
771
+ type: 'IBC_METHOD',
772
+ method,
773
+ });
774
+ },
561
775
  },
562
- });
563
- }
776
+ );
777
+ };