Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-Hop Channel Draft #741

Closed
wants to merge 14 commits into from
215 changes: 163 additions & 52 deletions spec/core/ics-004-channel-and-packet-semantics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ If not provided, the default `validateChannelIdentifier` function will always re
| Actor | ChanCloseInit | A | (OPEN, OPEN) | (CLOSED, OPEN) |
| Relayer | ChanCloseConfirm | B | (CLOSED, OPEN) | (CLOSED, CLOSED) |

##### Utility Functions

`getNextConnection` is a utility function that takes a route represented as a series of connections joined by `/` and returns the next connection in the sequence.

```typescript
function getNextConnection(route string) {
hops = strings.split(route, "/")
return hops[0]
}
```

##### Opening handshake

The `chanOpenInit` function is called by a module to initiate a channel opening handshake with a module on another chain.
Expand All @@ -282,6 +293,10 @@ type generateIdentifier = () -> Identifier
```

```typescript
// connectionHops is a list of routes that connect the channelEnds
// each element in the list consists of one or more connection identifiers joined by `/'
// the connectionHops must be ordered from the initiating chain to the counterparty chain
// each element in the route is the connection identifier for the next chain in the sequence
function chanOpenInit(
order: ChannelOrder,
connectionHops: [Identifier],
Expand All @@ -291,13 +306,18 @@ function chanOpenInit(
channelIdentifier = generateIdentifier()
abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))

abortTransactionUnless(connectionHops.length === 1) // for v1 of the IBC protocol

abortTransactionUnless(provableStore.get(channelPath(portIdentifier, channelIdentifier)) === null)
connection = provableStore.get(connectionPath(connectionHops[0]))

// optimistic channel handshakes are allowed
abortTransactionUnless(connection !== null)
// so next connection does not need to be OPEN but it does need to exist for each proposed route
for i := 0; i < len(connectionHops); i++ {
hops = splitRoute(connectionHops[i])
nextHopConnectionId = hops[0]
connection = provableStore.get(connectionPath(nextHopConnectionId))

abortTransactionUnless(connection !== null)
}

abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
channel = ChannelEnd{INIT, order, counterpartyPortIdentifier,
"", connectionHops, version}
Expand All @@ -313,36 +333,65 @@ function chanOpenInit(
The `chanOpenTry` function is called by a module to accept the first step of a channel opening handshake initiated by a module on another chain.

```typescript
// the connectionHops in TRY must be the same routes as the ones set in INIT in the same order
// the connectionIDs will represent the identifiers in the other direction from the TRY chain to the ACK chain.
function chanOpenTry(
order: ChannelOrder,
connectionHops: [Identifier],
portIdentifier: Identifier,
counterpartyChosenChannelIdentifer: Identifier,
counterpartyPortIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
version: string, // deprecated
counterpartyVersion: string,
proofInit: CommitmentProof,
proofHeight: Height): CapabilityKey {
initChannel: ChannelEnd,
proofInit: [CommitmentProof],
proofHeight: [Height]): CapabilityKey {
channelIdentifier = generateIdentifier()

abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))
abortTransactionUnless(connectionHops.length === 1) // for v1 of the IBC protocol
abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
connection = provableStore.get(connectionPath(connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
expected = ChannelEnd{INIT, order, portIdentifier,
"", [connection.counterpartyConnectionIdentifier], counterpartyVersion}
abortTransactionUnless(connection.verifyChannelState(
proofHeight,
proofInit,
counterpartyPortIdentifier,
counterpartyChannelIdentifier,
expected
))
channel = ChannelEnd{TRYOPEN, order, counterpartyPortIdentifier,
counterpartyChannelIdentifier, connectionHops, version}

abortTransactionUnless(initChannel.state == INIT)
abortTransactionUnless(initChannel.order == order)
abortTransactionUnless(initChannel.counterpartyPortIdentifier == portIdentifier)

// handshake messages must be proven against
// every possible route
for i := 0; i < len(connectionHops); i++ {
hops = splitRoute(connectionHops[i])

nextHopConnectionId = getNextHop(hops[0])
connection = provableStore.get(connectionPath(nextHopConnectionId))

abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)

client = queryClient(connection.clientIdentifier)
value = protobuf.marshal(initChannel)

if len(hops == 1) {
abortTransactionUnless(connection.verifyChannelState(
proofHeight[i],
proofInit[i],
counterpartyPortIdentifier,
counterpartyChannelIdentifier,
initChannel
))
} else {
counterpartyHops = splitRoute(initChannel.connectionHops[i])

// we prove that the last hop from the initChannel for this route is the counterparty for the first hop of our route
abortTransactionUnless(connection.counterpartyConnectionIdentifier == counterpartyHops[len(counterpartyHops)-1])

// we then prove that the next connection in this route stored
// the initChannel under the full connectionHops stored on initChannel
path = append(initChannel.connectionHops[i], channelPath(counterpartyPortIdentifier, counterpartyChannelIdentifier))

verifyMembership(client, proofHeight[i], 0, 0, proofInit[i], path, value)
}
}

channel = ChannelEnd{TRYOPEN, order, initChannel.portIdentifier,
initChannel.channelIdentifier, connectionHops, version}
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))

Expand All @@ -363,24 +412,54 @@ function chanOpenAck(
portIdentifier: Identifier,
channelIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
counterpartyVersion: string,
proofTry: CommitmentProof,
proofHeight: Height) {
tryChannel: ChannelEnd,
proofTry: [CommitmentProof],
proofHeight: [Height]) {
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel.state === INIT || channel.state === TRYOPEN)
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
connection = provableStore.get(connectionPath(channel.connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
expected = ChannelEnd{TRYOPEN, channel.order, portIdentifier,
channelIdentifier, [connection.counterpartyConnectionIdentifier], counterpartyVersion}
abortTransactionUnless(connection.verifyChannelState(
proofHeight,
proofTry,
channel.counterpartyPortIdentifier,
counterpartyChannelIdentifier,
expected
))

abortTransactionUnless(tryChannel.state == TRYOPEN)
abortTransactionUnless(tryChannel.order == channel.order)
abortTransactionUnless(tryChannel.counterpartyPortIdentifier == portIdentifier)
abortTransactionUnless(tryChannel.counterpartyChannelIdentifier == channelIdentifier)

// handshake messages must be proven against
// every possible route
for i := 0; i < len(connectionHops); i++ {
hops = splitRoute(connectionHops[i])

nextHopConnectionId = getNextHop(hops[0])
connection = provableStore.get(connectionPath(nextHopConnectionId))

abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)

client = queryClient(connection.clientIdentifier)
value = protobuf.marshal(tryChannel)

if len(hops == 1) {
abortTransactionUnless(connection.verifyChannelState(
proofHeight[i],
proofTry[i],
channel.counterpartyPortIdentifier,
channel.counterpartyChannelIdentifier,
tryChannel
))
} else {
counterpartyHops = splitRoute(tryChannel.connectionHops[i])

// we prove that the last hop from the tryChannel for this route is the counterparty for the first hop of our route
abortTransactionUnless(connection.counterpartyConnectionIdentifier == counterpartyHops[len(counterpartyHops)-1])

// we then prove that the next connection in this route stored
// the tryChannel under the full connectionHops stored on tryChannel
path = append(try.connectionHops[i], channelPath(channel.counterpartyPortIdentifier, channel.counterpartyChannelIdentifier))

verifyMembership(client, proofHeight[i], 0, 0, proofTry[i], path, value)
}
}

channel.state = OPEN
channel.version = counterpartyVersion
channel.counterpartyChannelIdentifier = counterpartyChannelIdentifier
Expand All @@ -395,24 +474,55 @@ of the handshake-originating module on the other chain and finish the channel op
function chanOpenConfirm(
portIdentifier: Identifier,
channelIdentifier: Identifier,
proofAck: CommitmentProof,
proofHeight: Height) {
ackChannel: ChannelEnd,
proofAck: [CommitmentProof],
proofHeight: [Height]) {
channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
abortTransactionUnless(channel !== null)
abortTransactionUnless(channel.state === TRYOPEN)
abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
connection = provableStore.get(connectionPath(channel.connectionHops[0]))
abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)
expected = ChannelEnd{OPEN, channel.order, portIdentifier,
channelIdentifier, [connection.counterpartyConnectionIdentifier], channel.version}
abortTransactionUnless(connection.verifyChannelState(
proofHeight,
proofAck,
channel.counterpartyPortIdentifier,
channel.counterpartyChannelIdentifier,
expected
))

abortTransactionUnless(ackChannel.state == OPEN)
abortTransactionUnless(ackChannel.order == order)
abortTransactionUnless(ackChannel.counterpartyPortIdentifier == portIdentifier)
abortTransactionUnless(ackChannel.counterpartyChannelIdentifier == channelIdentifier)

// handshake messages must be proven against
// every possible route
for i := 0; i < len(connectionHops); i++ {
hops = splitRoute(connectionHops[i])

nextHopConnectionId = getNextHop(hops[0])
connection = provableStore.get(connectionPath(nextHopConnectionId))

abortTransactionUnless(connection !== null)
abortTransactionUnless(connection.state === OPEN)

client = queryClient(connection.clientIdentifier)
value = protobuf.marshal(ackChannel)

if len(hops == 1) {
abortTransactionUnless(connection.verifyChannelState(
proofHeight[i],
proofAck[i],
channel.counterpartyPortIdentifier,
channel.counterpartyChannelIdentifier,
ackChannel
))
} else {
counterpartyHops = splitRoute(ackChannel.connectionHops[i])

// we prove that the last hop from the tryChannel for this route is the counterparty for the first hop of our route
abortTransactionUnless(connection.counterpartyConnectionIdentifier == counterpartyHops[len(counterpartyHops)-1])

// we then prove that the next connection in this route stored
// the tryChannel under the full connectionHops stored on tryChannel
path = append(ackChannel.connectionHops[i], channelPath(channel.counterpartyPortIdentifier, channel.counterpartyChannelIdentifier))

verifyMembership(client, proofHeight[i], 0, 0, proofAck[i], path, value)
}
}

channel.state = OPEN
provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
}
Expand Down Expand Up @@ -600,6 +710,7 @@ function recvPacket(
abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight)
abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp)

// only need to prove against one connection
abortTransactionUnless(connection.verifyPacketData(
proofHeight,
proof,
Expand Down
Loading