Skip to content

Commit c941996

Browse files
authored
ICS4: Add ORDERED_ALLOW_TIMEOUT channel type (#636)
* add ORDERED_ALLOW_TIMEOUT channel type * increment next sequence recv on timeout on ordered allow timeout channel * fix issue with incrementing next sequence recv * fix switch statements * fix conditional bug * return on receiving timed out packet * address most of reviews * final fixes * address small issues
1 parent cddb495 commit c941996

File tree

1 file changed

+130
-54
lines changed
  • spec/core/ics-004-channel-and-packet-semantics

1 file changed

+130
-54
lines changed

spec/core/ics-004-channel-and-packet-semantics/README.md

+130-54
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,17 @@ A *bidirectional* channel is a channel where packets can flow in both directions
4444

4545
A *unidirectional* channel is a channel where packets can only flow in one direction: from `A` to `B` (or from `B` to `A`, the order of naming is arbitrary).
4646

47-
An *ordered* channel is a channel where packets are delivered exactly in the order which they were sent.
47+
An *ordered* channel is a channel where packets are delivered exactly in the order which they were sent. This channel type offers a very strict guarantee of ordering. Either, the packets are received in the order they were sent, or if a packet in the sequence times out; then all future packets are also not receivable and the channel closes.
48+
49+
An *ordered_allow_timeout* channel is a less strict version of the *ordered* channel. Here, the channel logic will take a *best effort* approach to delivering the packets in order. In a stream of packets, the channel will relay all packets in order and if a packet in the stream times out, the timeout logic for that packet will execute and the rest of the later packets will continue processing in order. Thus, we **do not close** the channel on a timeout with this channel type.
4850

4951
An *unordered* channel is a channel where packets can be delivered in any order, which may differ from the order in which they were sent.
5052

5153
```typescript
5254
enum ChannelOrder {
5355
ORDERED,
5456
UNORDERED,
57+
ORDERED_ALLOW_TIMEOUT,
5558
}
5659
```
5760

@@ -75,7 +78,7 @@ interface ChannelEnd {
7578
```
7679

7780
- The `state` is the current state of the channel end.
78-
- The `ordering` field indicates whether the channel is ordered or unordered.
81+
- The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`.
7982
- The `counterpartyPortIdentifier` identifies the port on the counterparty chain which owns the other end of the channel.
8083
- The `counterpartyChannelIdentifier` identifies the channel end on the counterparty chain.
8184
- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent.
@@ -132,6 +135,15 @@ An `OpaquePacket` is a packet, but cloaked in an obscuring data type by the host
132135
type OpaquePacket = object
133136
```
134137
138+
In order to enable new channel types (e.g. ORDERED_ALLOW_TIMEOUT), the protocol introduces standardized packet receipts that will serve as sentinel values for the receiving chain to expliclity write to its store the outcome of a `recvPacket`.
139+
140+
```typescript
141+
enum PacketReceipt {
142+
SUCCESSFUL_RECEIPT,
143+
TIMEOUT_RECEIPT,
144+
}
145+
```
146+
135147
### Desired Properties
136148

137149
#### Efficiency
@@ -147,8 +159,9 @@ type OpaquePacket = object
147159

148160
#### Ordering
149161

150-
- On ordered channels, packets should be sent and received in the same order: if packet *x* is sent before packet *y* by a channel end on chain `A`, packet *x* must be received before packet *y* by the corresponding channel end on chain `B`.
151-
- On unordered channels, packets may be sent and received in any order. Unordered packets, like ordered packets, have individual timeouts specified in terms of the destination chain's height.
162+
- On *ordered* channels, packets should be sent and received in the same order: if packet *x* is sent before packet *y* by a channel end on chain `A`, packet *x* must be received before packet *y* by the corresponding channel end on chain `B`. If packet *x* is sent before packet *y* by a channel and packet *x* is timed out; then packet *y* and any packet sent after *x* cannot be received.
163+
- On *ordered_allow_timeout* channels, packets should be sent and received in the same order: if packet *x* is sent before packet *y* by a channel end on chain `A`, packet *x* must be received **or** timed out before packet *y* by the corresponding channel end on chain `B`.
164+
- On *unordered* channels, packets may be sent and received in any order. Unordered packets, like ordered packets, have individual timeouts specified in terms of the destination chain's height.
152165

153166
#### Permissioning
154167

@@ -209,7 +222,8 @@ function packetCommitmentPath(portIdentifier: Identifier, channelIdentifier: Ide
209222

210223
Absence of the path in the store is equivalent to a zero-bit.
211224

212-
Packet receipt data are stored under the `packetReceiptPath`
225+
Packet receipt data are stored under the `packetReceiptPath`. In the case of a successful receive, the destination chain writes a sentinel success value of `SUCCESSFUL_RECEIPT`.
226+
Some channel types MAY write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout.
213227

214228
```typescript
215229
function packetReceiptPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
@@ -577,11 +591,11 @@ The IBC handler performs the following steps in order:
577591
- Checks that the channel & connection are open to receive packets
578592
- Checks that the calling module owns the receiving port
579593
- Checks that the packet metadata matches the channel & connection information
580-
- Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered channels)
581-
- Checks that the timeout height has not yet passed
594+
- Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered and ordered_allow_timeout channels)
595+
- Checks that the timeout height and timestamp have not yet passed
582596
- Checks the inclusion proof of packet data commitment in the outgoing chain's state
583597
- Sets a store path to indicate that the packet has been received (unordered channels only)
584-
- Increments the packet receive sequence associated with the channel end (ordered channels only)
598+
- Increments the packet receive sequence associated with the channel end (ordered and ordered_allow_timeout channels only)
585599

586600
We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard).
587601

@@ -602,9 +616,6 @@ function recvPacket(
602616
abortTransactionUnless(connection !== null)
603617
abortTransactionUnless(connection.state === OPEN)
604618

605-
abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight)
606-
abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp)
607-
608619
abortTransactionUnless(connection.verifyPacketData(
609620
proofHeight,
610621
proof,
@@ -614,24 +625,59 @@ function recvPacket(
614625
concat(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)
615626
))
616627

617-
// all assertions passed (except sequence check), we can alter state
628+
// do sequence check before any state changes
629+
if channel.order == ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT {
630+
nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel))
631+
abortTransactionUnless(packet.sequence === nextSequenceRecv)
632+
}
618633

619-
if (channel.order === ORDERED) {
620-
nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel))
621-
abortTransactionUnless(packet.sequence === nextSequenceRecv)
622-
nextSequenceRecv = nextSequenceRecv + 1
623-
provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv)
624-
} else {
625-
// for unordered channels we must set the receipt so it can be verified on the other side
626-
// this receipt does not contain any data, since the packet has not yet been processed
627-
// it's just a single store key set to an empty string to indicate that the packet has been received
628-
abortTransactionUnless(provableStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence) === null))
629-
provableStore.set(
630-
packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence),
631-
"1"
632-
)
634+
switch channel.order {
635+
case ORDERED:
636+
case UNORDERED:
637+
abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight)
638+
abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp)
639+
break;
640+
641+
case ORDERED_ALLOW_TIMEOUT:
642+
// for ORDERED_ALLOW_TIMEOUT, we do not abort on timeout
643+
// instead increment next sequence recv and write the sentinel timeout value in packet receipt
644+
// then return
645+
if (getConsensusHeight() >= packet.timeoutHeight && packet.timeoutHeight != 0) || (currentTimestamp() >= packet.timeoutTimestamp && packet.timeoutTimestamp != 0) {
646+
nextSequenceRecv = nextSequenceRecv + 1
647+
provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv)
648+
provableStore.set(
649+
packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence),
650+
TIMEOUT_RECEIPT
651+
)
652+
}
653+
return;
654+
655+
default:
656+
// unsupported channel type
657+
abortTransactionUnless(false)
633658
}
634659

660+
// all assertions passed (except sequence check), we can alter state
661+
662+
switch channel.order {
663+
case ORDERED:
664+
case ORDERED_ALLOW_TIMEOUT:
665+
nextSequenceRecv = nextSequenceRecv + 1
666+
provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv)
667+
break;
668+
669+
case UNORDERED:
670+
// for unordered channels we must set the receipt so it can be verified on the other side
671+
// this receipt does not contain any data, since the packet has not yet been processed
672+
// it's the sentinel success receipt: []byte{0x01}
673+
abortTransactionUnless(provableStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence) === null))
674+
provableStore.set(
675+
packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence),
676+
SUCCESFUL_RECEIPT
677+
)
678+
break;
679+
}
680+
635681
// log that a packet has been received
636682
emitLogEntry("recvPacket", {sequence: packet.sequence, timeoutHeight: packet.timeoutHeight, port: packet.destPort, channel: packet.destChannel,
637683
timeoutTimestamp: packet.timeoutTimestamp, data: packet.data})
@@ -724,7 +770,7 @@ function acknowledgePacket(
724770
))
725771

726772
// abort transaction unless acknowledgement is processed in order
727-
if (channel.order === ORDERED) {
773+
if (channel.order === ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT) {
728774
nextSequenceAck = provableStore.get(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel))
729775
abortTransactionUnless(packet.sequence === nextSequenceAck)
730776
nextSequenceAck = nextSequenceAck + 1
@@ -816,37 +862,71 @@ function timeoutPacket(
816862
abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
817863
=== hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))
818864

819-
if channel.order === ORDERED {
820-
// ordered channel: check that packet has not been received
821-
abortTransactionUnless(nextSequenceRecv <= packet.sequence)
822-
// ordered channel: check that the recv sequence is as claimed
823-
abortTransactionUnless(connection.verifyNextSequenceRecv(
824-
proofHeight,
825-
proof,
826-
packet.destPort,
827-
packet.destChannel,
828-
nextSequenceRecv
829-
))
830-
} else
831-
// unordered channel: verify absence of receipt at packet index
832-
abortTransactionUnless(connection.verifyPacketReceiptAbsence(
833-
proofHeight,
834-
proof,
835-
packet.destPort,
836-
packet.destChannel,
837-
packet.sequence
838-
))
865+
switch channel.order {
866+
case ORDERED:
867+
// ordered channel: check that packet has not been received
868+
// only allow timeout on next sequence so all sequences before the timed out packet are processed (received/timed out)
869+
// before this packet times out
870+
abortTransactionUnless(nextSequenceRecv == packet.sequence)
871+
// ordered channel: check that the recv sequence is as claimed
872+
abortTransactionUnless(connection.verifyNextSequenceRecv(
873+
proofHeight,
874+
proof,
875+
packet.destPort,
876+
packet.destChannel,
877+
nextSequenceRecv
878+
))
879+
break;
880+
881+
case UNORDERED:
882+
// unordered channel: verify absence of receipt at packet index
883+
abortTransactionUnless(connection.verifyPacketReceiptAbsence(
884+
proofHeight,
885+
proof,
886+
packet.destPort,
887+
packet.destChannel,
888+
packet.sequence
889+
))
890+
break;
891+
892+
// NOTE: For ORDERED_ALLOW_TIMEOUT, the relayer must first attempt the receive on the destination chain
893+
// before the timeout receipt can be written and subsequently proven on the sender chain in timeoutPacket
894+
case ORDERED_ALLOW_TIMEOUT:
895+
// ordered channel: check that packet has not been received
896+
// only allow timeout on next sequence so all sequences before the timed out packet are processed (received/timed out)
897+
// before this packet times out
898+
abortTransactionUnless(nextSequenceRecv == packet.sequence)
899+
abortTransactionUnless(connection.verifyPacketReceipt(
900+
proofHeight,
901+
proof,
902+
packet.destPort,
903+
packet.destChannel,
904+
packet.sequence
905+
TIMEOUT_RECEIPT,
906+
))
907+
break;
908+
909+
default:
910+
// unsupported channel type
911+
abortTransactionUnless(true)
912+
}
839913

840914
// all assertions passed, we can alter state
841915

842916
// delete our commitment
843917
provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
844918

919+
// only close on strictly ORDERED channels
845920
if channel.order === ORDERED {
846921
// ordered channel: close the channel
847922
channel.state = CLOSED
848923
provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel)
849924
}
925+
// on ORDERED_ALLOW_TIMEOUT, increment NextSequenceAck so that next packet can be acknowledged after this packet timed out.
926+
if channel.order === ORDERED_ALLOW_TIMEOUT {
927+
nextSequenceAck = nextSequenceAck + 1
928+
provableStore.set(nextSequenceAckPath(packet.srcPort, packet.srcChannel), nextSequenceAck)
929+
}
850930

851931
// return transparent packet
852932
return packet
@@ -896,7 +976,7 @@ function timeoutOnClose(
896976
expected
897977
))
898978

899-
if channel.order === ORDERED {
979+
if channel.order === ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT {
900980
// ordered channel: check that the recv sequence is as claimed
901981
abortTransactionUnless(connection.verifyNextSequenceRecv(
902982
proofHeight,
@@ -922,12 +1002,6 @@ function timeoutOnClose(
9221002
// delete our commitment
9231003
provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
9241004

925-
if channel.order === ORDERED {
926-
// ordered channel: close the channel
927-
channel.state = CLOSED
928-
provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel)
929-
}
930-
9311005
// return transparent packet
9321006
return packet
9331007
}
@@ -1005,6 +1079,8 @@ Aug 13, 2019 - Various edits
10051079

10061080
Aug 25, 2019 - Cleanup
10071081

1082+
Jan 10, 2022 - Add ORDERED_ALLOW_TIMEOUT channel type and appropriate logic
1083+
10081084
## Copyright
10091085

10101086
All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).

0 commit comments

Comments
 (0)