Skip to content

Commit

Permalink
feat: suspend an outbound connection until the relayer picks it up
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Apr 27, 2020
1 parent e303777 commit ee22926
Showing 1 changed file with 91 additions and 7 deletions.
98 changes: 91 additions & 7 deletions packages/cosmic-swingset/lib/ag-solo/vats/ibc.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export function makeIBCProtocolHandler(
onReceive = withChannelReceiveQueue(onReceive);
}

const ckey = `${channelID}:${portID}`;
return harden({
async onOpen(conn, handler) {
console.info('onOpen Remote IBC Connection', channelID, portID);
Expand Down Expand Up @@ -183,6 +182,11 @@ export function makeIBCProtocolHandler(
*/
let protocolImpl;

/**
* @type {Store<string, Store<string, (typeof makeIBCConnectionHandler)[]>>}
*/
const outboundWaiters = makeStore('destination');

const goodLetters = 'abcdefghijklmnopqrstuvwxyz';

// We delegate to a loopback protocol, too, to connect locally.
Expand All @@ -206,8 +210,43 @@ export function makeIBCProtocolHandler(
},
async onConnect(_port, localAddr, remoteAddr, _protocolHandler) {
console.warn('IBC onConnect', localAddr, remoteAddr);
// return makeIBCConnectionHandler('FIXME', localAddr, remoteAddr, true);
return undefined;
const portID = localAddrToPortID(localAddr);
if (!remoteAddr.match(/^(\/ibc-hop\/[^/]+)+\/ibc-port\/[^/]+$/)) {
throw TypeError(
`Remote address ${remoteAddr} must be /ibc-hop/HOP.../ibc-port/PORT`,
);
}

// We need to wait around for a relayer to continue the connection.
// TODO: We should also send a ChanOpenInit to get a passive relayer flowing.

/** @type {PromiseRecord<ConnectionHandler,any>} */
const chandler = producePromise();

/**
* @type {typeof makeIBCConnectionHandler}
*/
const connected = (...args) => {
const ch = makeIBCConnectionHandler(...args);
chandler.resolve(ch);
return ch;
};
let waiters;
if (outboundWaiters.has(remoteAddr)) {
waiters = outboundWaiters.get(remoteAddr);
} else {
waiters = makeStore('source');
outboundWaiters.init(remoteAddr, waiters);
}
let waiterList;
if (waiters.has(portID)) {
waiterList = waiters.get(portID);
} else {
waiterList = [];
waiters.init(remoteAddr, waiterList);
}
waiterList.push(connected);
return chandler.promise;
},
async onListen(_port, localAddr, _listenHandler) {
console.warn('IBC onListen', localAddr);
Expand All @@ -220,18 +259,56 @@ export function makeIBCProtocolHandler(
},
});

// FIXME: Actually implement!
function getWaiter(hops, portID, rPort, claim = false) {
const us = `/ibc-port/${portID}`;
for (let i = 0; i <= hops.length; i += 1) {
// Try most specific to least specific outbound connections.
const ibcHops = hops
.slice(0, hops.length - i)
.map(hop => `/ibc-hop/${hop}`)
.join('/');
const them = `${ibcHops}/ibc-port/${rPort}`;
if (outboundWaiters.has(them)) {
const waiters = outboundWaiters.get(them);
if (waiters.has(us)) {
// Return the list of waiters.
if (!claim) {
return true;
}
const waiterList = waiters.get(us);
// Clean up the maps.
const waiter = waiterList.shift();
if (waiterList.length === 0) {
waiters.delete(us);
}
if (waiters.keys().length === 0) {
outboundWaiters.delete(them);
}
return waiter;
}
}
}
return false;
}

return harden({
...protocol,
async fromBridge(srcID, obj) {
console.warn('IBC fromBridge', srcID, obj);
switch (obj.event) {
case 'channelOpenTry':
case 'channelOpenInit': {
const { channelID, portID } = obj;
const {
channelID,
portID,
counterparty: { port_id: rPortID },
connectionHops: hops,
} = obj;

await E(protocolImpl).isListening([`/ibc-port/${portID}`]);
const channelKey = `${channelID}:${portID}`;
if (!getWaiter(hops, portID, rPortID)) {
await E(protocolImpl).isListening([`/ibc-port/${portID}`]);
}
channelKeyToConnectingInfo.init(channelKey, obj);
break;
}
Expand All @@ -257,8 +334,15 @@ export function makeIBCProtocolHandler(
const localAddr = `/ibc-port/${portID}/${order.toLowerCase()}/${version}`;
const ibcHops = hops.map(hop => `/ibc-hop/${hop}`).join('/');
const remoteAddr = `${ibcHops}/ibc-port/${rPortID}/${order.toLowerCase()}/${rVersion}`;
const listenSearch = [`/ibc-port/${portID}`];
const waiter = getWaiter(hops, portID, rPortID, true);
if (typeof waiter === 'function') {
// An outbound connection wants to use this channel.
waiter(channelID, portID, rChannelID, rPortID, order === 'ORDERED');
break;
}

// Check for a listener for this channel.
const listenSearch = [`/ibc-port/${portID}`];
const rchandler = makeIBCConnectionHandler(
channelID,
portID,
Expand Down

0 comments on commit ee22926

Please sign in to comment.