diff --git a/spec/core/ics-004-channel-and-packet-semantics/README.md b/spec/core/ics-004-channel-and-packet-semantics/README.md index eb8686efe..0576fcd07 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/ics-004-channel-and-packet-semantics/README.md @@ -39,6 +39,8 @@ In order to provide the desired ordering, exactly-once delivery, and module perm `Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements). +See [upgrades spec](./UPGRADES.md) for definition of `pendingInflightPackets`. + A *channel* is a pipeline for exactly-once packet delivery between specific modules on separate blockchains, which has at least one end capable of sending packets and one end capable of receiving packets. A *bidirectional* channel is a channel where packets can flow in both directions: from `A` to `B` and from `B` to `A`. @@ -75,6 +77,8 @@ interface ChannelEnd { counterpartyChannelIdentifier: Identifier connectionHops: [Identifier] version: string + upgradeSequence: uint64 + flushStatus: FlushStatus } ``` @@ -88,6 +92,8 @@ interface ChannelEnd { - The `connectionHops` stores the list of connection identifiers, in order, along which packets sent on this channel will travel. At the moment this list must be of length 1. In the future multi-hop channels may be supported. - The `version` string stores an opaque channel version, which is agreed upon during the handshake. This can determine module-level configuration such as which packet encoding is used for the channel. This version is not used by the core IBC protocol. If the version string contains structured metadata for the application to parse and interpret, then it is considered best practice to encode all metadata in a JSON struct and include the marshalled string in the version field. +See the [upgrade spec](./UPGRADES.md) for details on `sequence` and `flushStatus`. + Channel ends have a *state*: ```typescript @@ -487,6 +493,7 @@ function chanCloseInit( abortTransactionUnless(connection !== null) abortTransactionUnless(connection.state === OPEN) channel.state = CLOSED + channel.flushStatus = NOTINFLUSH provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) } ``` @@ -527,6 +534,7 @@ function chanCloseConfirm( expected )) channel.state = CLOSED + channel.flushStatus = NOTINFLUSH provableStore.set(channelPath(portIdentifier, channelIdentifier), channel) } ``` @@ -589,7 +597,7 @@ function sendPacket( // check that the channel is not closed to send packets; abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state !== CLOSED) + abortTransactionUnless(channel.state !== CLOSED && channel.flushStatus === NOTINFLUSH) connection = provableStore.get(connectionPath(channel.connectionHops[0])) abortTransactionUnless(connection !== null) @@ -653,7 +661,8 @@ function recvPacket( channel = provableStore.get(channelPath(packet.destPort, packet.destChannel)) abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) + counterpartyLastPacketSent = privateStore.get(channelCounterpartyLastPacketSequencePath(packet.destPort, packet.destChannel) + abortTransactionUnless(channel.state === OPEN || (channel.flushStatus === FLUSHING && packet.sequence <= counterpartyLasPacketSent)) abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability)) abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier) abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier) @@ -843,7 +852,7 @@ function acknowledgePacket( // abort transaction unless that channel is open, calling module owns the associated port, and the packet fields match channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel)) abortTransactionUnless(channel !== null) - abortTransactionUnless(channel.state === OPEN) + abortTransactionUnless(channel.state === OPEN || channel.flushStatus === FLUSHING) abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability)) abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier) abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier) @@ -879,6 +888,12 @@ function acknowledgePacket( // delete our commitment so we can't "acknowledge" again provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + // if there are no in-flight packets on our end, we can automatically go to FLUSHCOMPLETE + if channel.flushStatus === FLUSHING && pendingInflightPackets(packet.sourcePort, packet.sourceChannel) == nil { + channel.flushStatus = FLUSHCOMPLETE + provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) + } + // return transparent packet return packet } @@ -1013,10 +1028,17 @@ function timeoutPacket( // delete our commitment provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) + // if there are no in-flight packets on our end, we can automatically go to FLUSHCOMPLETE + if channel.flushStatus === FLUSHING && pendingInflightPackets(packet.sourcePort, packet.sourceChannel) == nil { + channel.flushStatus = FLUSHCOMPLETE + provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) + } + // only close on strictly ORDERED channels if channel.order === ORDERED { - // ordered channel: close the channel + // ordered channel: close the channel and reset flushStatus channel.state = CLOSED + channel.flushStatus = NOTINFLUSH provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel) } // on ORDERED_ALLOW_TIMEOUT, increment NextSequenceAck so that next packet can be acknowledged after this packet timed out.