From b8e9826ef2156621e37c4692c090244426743e77 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 8 Jun 2019 21:45:35 +0200 Subject: [PATCH] Merge PR #34: ICS 4: Channel & Packet Semantics --- Makefile | 6 +- README.md | 1 + deps.png | 4 +- scripts/check_links.py | 4 +- scripts/check_sections.py | 2 +- spec/ics-2-consensus-verification/README.md | 14 +- spec/ics-23-vector-commitments/README.md | 1 + spec/ics-24-host-requirements/README.md | 2 +- spec/ics-3-connection-semantics/README.md | 15 +- spec/ics-3-connection-semantics/state.png | 2 +- .../Makefile | 15 + .../README.md | 758 ++++++++++++++++++ .../channel-state-machine.png | 3 + .../channel-state-machine.tex | 55 ++ .../dataflow.png | 3 + .../dataflow.tex | 66 ++ .../packet-state-machine.png | 3 + .../packet-state-machine.tex | 41 + 18 files changed, 978 insertions(+), 17 deletions(-) create mode 100644 spec/ics-4-channel-and-packet-semantics/Makefile create mode 100644 spec/ics-4-channel-and-packet-semantics/README.md create mode 100644 spec/ics-4-channel-and-packet-semantics/channel-state-machine.png create mode 100644 spec/ics-4-channel-and-packet-semantics/channel-state-machine.tex create mode 100644 spec/ics-4-channel-and-packet-semantics/dataflow.png create mode 100644 spec/ics-4-channel-and-packet-semantics/dataflow.tex create mode 100644 spec/ics-4-channel-and-packet-semantics/packet-state-machine.png create mode 100644 spec/ics-4-channel-and-packet-semantics/packet-state-machine.tex diff --git a/Makefile b/Makefile index 37bd6bae0936..4206ab1f600a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SUBDIRS := spec/ics-3-connection-semantics +SUBDIRS := spec/ics-3-connection-semantics spec/ics-4-channel-and-packet-semantics TOPTARGETS := all clean $(TOPTARGETS): $(SUBDIRS) @@ -8,6 +8,8 @@ $(SUBDIRS): setup_dependencies: pip install matplotlib networkx +check: check_links check_dependencies check_syntax check_sections + check_links: python ./scripts/check_links.py @@ -20,4 +22,4 @@ check_syntax: check_sections: python ./scripts/check_sections.py -.PHONY: $(TOPTARGETS) $(SUBDIRS) setup_dependencies check_links check_dependencies check_syntax check_sections +.PHONY: $(TOPTARGETS) $(SUBDIRS) setup_dependencies check check_links check_dependencies check_syntax check_sections diff --git a/README.md b/README.md index 1fd0be642b13..0ee67c68b42a 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ All standards in the "draft" stage are listed here in order of their ICS numbers | --------------------------------------------------- | ---------------------------------- | ----- | | [2](spec/ics-2-consensus-verification) | Consensus Verification | Draft | | [3](spec/ics-3-connection-semantics) | Connection Semantics | Draft | +| [4](spec/ics-4-channel-and-packet-semantics) | Channel & Packet Semantics | Draft | | [18](spec/ics-18-relayer-algorithms) | Relayer Algorithms | Draft | | [23](spec/ics-23-vector-commitments) | Vector Commitments | Draft | | [24](spec/ics-24-host-requirements) | Host Requirements | Draft | diff --git a/deps.png b/deps.png index 62dfc988d5f4..2db0853ee169 100644 --- a/deps.png +++ b/deps.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0e6064f2a943eb119c76675defb8e5d7cd6c0f98ca2c17aca3c7fa437757982 -size 13269 +oid sha256:7942cc5b9c4a1a2a96e400293af3e4746f304a4ad281c32e5364e4955eb30b11 +size 36640 diff --git a/scripts/check_links.py b/scripts/check_links.py index b5333c6fea1e..7ee1af0bb86a 100755 --- a/scripts/check_links.py +++ b/scripts/check_links.py @@ -2,7 +2,7 @@ import re, os, sys -link_regex = re.compile('\[(.*)\]\(../ics([^\)]*)\)') +link_regex = re.compile('\[ICS ([0-9]+)\]\(([^\)]*)\)') title_regex = re.compile('ICS ([0-9]+)([ .:])') specs = [f.path for f in os.scandir('./spec') if f.is_dir()] @@ -13,7 +13,7 @@ for fn in files: print('Checking links in {}'.format(fn)) data = open(fn).read() - links = ['ics' + l[1] for l in link_regex.findall(data)] + links = [l[1][3:] for l in link_regex.findall(data)] for link in links: found = link in specs_cut if not found: diff --git a/scripts/check_sections.py b/scripts/check_sections.py index f97602915804..751501b93bc3 100755 --- a/scripts/check_sections.py +++ b/scripts/check_sections.py @@ -7,7 +7,7 @@ sub_sub_section_regex = re.compile('[^#]### (.*)') specs = [f.path for f in os.scandir('./spec') if f.is_dir()] -files = [f.path for spec in specs for f in os.scandir(spec) if f.is_file() and f.path == 'README.md' and 'ics-1-ics-standard' not in f.path] +files = [f.path for spec in specs for f in os.scandir(spec) if f.is_file() and f.path[-9:] == 'README.md' and 'ics-1-ics-standard' not in f.path] expected_sub_sections = ['Synopsis', 'Technical Specification', 'Backwards Compatibility', 'Forwards Compatibility', 'Example Implementation', 'Other Implementations', 'History', 'Copyright'] expected_sub_sub_sections = ['Motivation', 'Definitions', 'Desired Properties'] diff --git a/spec/ics-2-consensus-verification/README.md b/spec/ics-2-consensus-verification/README.md index c18120cf57e8..14326d40f331 100644 --- a/spec/ics-2-consensus-verification/README.md +++ b/spec/ics-2-consensus-verification/README.md @@ -382,7 +382,19 @@ Not applicable. In a future version, this ICS will define a new function `unfreezeClient` that can be called when the application logic resolves an equivocation event. +## Example Implementation + +Coming soon. + +## Other Implementations + +Coming soon. + ## History -March 5th 2019: Initial draft finished and submitted as a PR. +March 5th 2019: Initial draft finished and submitted as a PR May 29 2019: Various revisions, notably multiple commitment-roots + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/spec/ics-23-vector-commitments/README.md b/spec/ics-23-vector-commitments/README.md index 9ba097b86a37..2a3a8c36783e 100644 --- a/spec/ics-23-vector-commitments/README.md +++ b/spec/ics-23-vector-commitments/README.md @@ -2,6 +2,7 @@ ics: 23 title: Vector Commitments stage: draft +required-by: 2, 3, 4 category: ibc-core author: Christopher Goes created: 2019-04-16 diff --git a/spec/ics-24-host-requirements/README.md b/spec/ics-24-host-requirements/README.md index 9b8f18ec2957..c76f0728904b 100644 --- a/spec/ics-24-host-requirements/README.md +++ b/spec/ics-24-host-requirements/README.md @@ -4,7 +4,7 @@ title: Host State Machine Requirements stage: draft category: ibc-core requires: 2 -required-by: 2, 3, 18 +required-by: 2, 3, 4, 18 author: Christopher Goes created: 2019-04-16 modified: 2019-05-11 diff --git a/spec/ics-3-connection-semantics/README.md b/spec/ics-3-connection-semantics/README.md index 1ee4657dbec2..9891353c9041 100644 --- a/spec/ics-3-connection-semantics/README.md +++ b/spec/ics-3-connection-semantics/README.md @@ -4,6 +4,7 @@ title: Connection Semantics stage: draft category: ibc-core requires: 2, 23, 24 +required-by: 4 author: Christopher Goes , Juwoon Yun created: 2019-03-07 modified: 2019-05-17 @@ -15,15 +16,15 @@ This standards document describes the abstraction of an IBC *connection*: two st ### Motivation -The core IBC protocol provides *authorization* and *ordering* semantics for packets: guarantees, respectively, that packets have been committed on the sending blockchain (and according state transitions executed, such as escrowing tokens), and that they have been committed exactly once in a particular order and can be delivered exactly once in that same order. The *connection* abstraction specified in this standard, in conjunction with the *client* abstraction specified in [ICS 2](../spec-ics-2-consensus-verification), defines the *authorization* semantics of IBC. Ordering semantics are described in [ICS 4](../spec/ics-4-channel-packet-semantics)). +The core IBC protocol provides *authorization* and *ordering* semantics for packets: guarantees, respectively, that packets have been committed on the sending blockchain (and according state transitions executed, such as escrowing tokens), and that they have been committed exactly once in a particular order and can be delivered exactly once in that same order. The *connection* abstraction specified in this standard, in conjunction with the *client* abstraction specified in [ICS 2](../ics-2-consensus-verification), defines the *authorization* semantics of IBC. Ordering semantics are described in [ICS 4](../ics-4-channel-and-packet-semantics)). ### Definitions -`ConsensusState`, `Header`, and `updateConsensusState` are as defined in [ICS 2](../spec/ics-2-consensus-verification). +`ConsensusState`, `Header`, and `updateConsensusState` are as defined in [ICS 2](../ics-2-consensus-verification). -`CommitmentProof`, `verifyMembership`, and `verifyNonMembership` are as defined in [ICS 23](../spec/ics-23-vector-commitments). +`CommitmentProof`, `verifyMembership`, and `verifyNonMembership` are as defined in [ICS 23](../ics-23-vector-commitments). -`Identifier` and other host state machine requirements are as defined in [ICS 24](../spec/ics-24-host-requirements). The identifier is not necessarily intended to be a human-readable name (and likely should not be, to discourage squatting or racing for identifiers). +`Identifier` and other host state machine requirements are as defined in [ICS 24](../ics-24-host-requirements). The identifier is not necessarily intended to be a human-readable name (and likely should not be, to discourage squatting or racing for identifiers). The opening handshake protocol allows each chain to verify the identifier used to reference the connection on the other chain, enabling modules on each chain to reason about the reference on the other chain. @@ -87,7 +88,7 @@ interface Connection { ### Subprotocols -This ICS defines two subprotocols: opening handshake and closing handshake. Header tracking and closing-by-equivocation are defined in [ICS 2](../spec/ics-2-consensus-verification). Datagrams defined herein are handled as external messages by the IBC relayer module defined in ICS 26. +This ICS defines two subprotocols: opening handshake and closing handshake. Header tracking and closing-by-equivocation are defined in [ICS 2](../ics-2-consensus-verification). Datagrams defined herein are handled as external messages by the IBC relayer module defined in ICS 26. ![State Machine Diagram](state.png) @@ -274,7 +275,7 @@ function connOpenTimeout(identifier: Identifier, proofTimeout: CommitmentProof, #### Header Tracking -Headers are tracked at the client level. See [ICS 2](../spec/ics-2-consensus-verification). +Headers are tracked at the client level. See [ICS 2](../ics-2-consensus-verification). #### Closing Handshake @@ -406,7 +407,7 @@ function connCloseTimeout(identifier: Identifier, proofTimeout: CommitmentProof, #### Freezing by Equivocation -The equivocation detection subprotocol is defined in [ICS 2](../spec/ics-2-consensus-verification). If a client is frozen by equivocation, all associated connections are immediately frozen as well. +The equivocation detection subprotocol is defined in [ICS 2](../ics-2-consensus-verification). If a client is frozen by equivocation, all associated connections are immediately frozen as well. Implementing chains may want to allow applications to register handlers to take action upon discovery of an equivocation. Further discussion is deferred to ICS 12. diff --git a/spec/ics-3-connection-semantics/state.png b/spec/ics-3-connection-semantics/state.png index 313931a40c70..084f5b567b86 100644 --- a/spec/ics-3-connection-semantics/state.png +++ b/spec/ics-3-connection-semantics/state.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7baa84583cfcef9b7d4c4f6dc05fc86d4a169f42edf2cabf37273300fc4d51a +oid sha256:eccc68322c50e782089a702e773c4f504eb079a5895c0b5d2d70b48fedf075c7 size 346289 diff --git a/spec/ics-4-channel-and-packet-semantics/Makefile b/spec/ics-4-channel-and-packet-semantics/Makefile new file mode 100644 index 000000000000..6e91233dd91c --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/Makefile @@ -0,0 +1,15 @@ +all: $(addsuffix .png, $(basename $(wildcard *.tex))) cleanup + +%.png : %.tex + pdflatex "\input{$<}" + convert -resize 50% -chop 26x0 -density 400 $(basename $<).pdf -quality 100 $@ + +cleanup: + rm -f *.log + rm -f *.aux + rm -f *.pdf + +clean: + rm -f *.png + +.PHONY: all clean cleanup diff --git a/spec/ics-4-channel-and-packet-semantics/README.md b/spec/ics-4-channel-and-packet-semantics/README.md new file mode 100644 index 000000000000..d10f8ea6ee2b --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/README.md @@ -0,0 +1,758 @@ +--- +ics: 4 +title: Channel & Packet Semantics +stage: draft +category: ibc-core +requires: 2, 3, 23, 24 +author: Christopher Goes +created: 2019-03-07 +modified: 2019-06-05 +--- + +## Synopsis + +The "channel" abstraction provides message delivery semantics to the interblockchain communication protocol, in three categories: ordering, exactly-once delivery, and module permissioning. A channel serves as a conduit for packets passing between a module on one chain and a module on another, ensuring that packets are executed only once, delivered in the order in which they were sent (if necessary), and delivered only to the corresponding module owning the other end of the channel on the destination chain. Each channel is associated with a particular connection, and a connection may have any number of associated channels, allowing the use of common identifiers and amortizing the cost of header verification across all the channels utilizing a connection & light client. + +Channels are payload-agnostic. The modules which send and receive IBC packets decide how to construct packet data and how to act upon the incoming packet data, and must utilize their own application logic to determine which state transactions to apply according to what data the packet contains. + +### Motivation + +The interblockchain communication protocol uses a cross-chain message passing model which makes no assumptions about network synchrony. IBC *packets* are relayed from one blockchain to the other by external relayer processes. Chain `A` and chain `B` confirm new blocks independently, and packets from one chain to the other may be delayed, censored, or re-ordered arbitrarily. Packets are public and can be read from a blockchain by any relayer and submitted to any other blockchain. + +The IBC protocol must provide ordering and exactly-once delivery guarantees in order to allow applications to reason about the combined state of connected modules on two chains. For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`. + +In order to provide the desired ordering, exactly-once delivery, and module permissioning semantics to the application layer, the interblockchain communication protocol must implement an abstraction to enforce these semantics — channels are this abstraction. + +### Definitions + +`ConsensusState` is as defined in [ICS 2](../ics-2-consensus-verification). + +`Connection` is as defined in [ICS 3](../ics-3-connection-semantics). + +`Commitment`, `CommitmentProof`, and `CommitmentRoot` are as defined in [ICS 23](../ics-23-vector-commitments). + +`commit` is a generic collision-resistant hash function, the specifics of which must be agreed on by the modules utilizing the channel. + +`Identifier`, `get`, `set`, `delete`, `getConsensusState`, and module-system related primitives are as defined in [ICS 24](../ics-24-host-requirements). + +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`. + +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). + +An *ordered* channel is a channel where packets are delivered exactly in the order which they were sent. + +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. + +Directionality and ordering are independent, so one can speak of a bidirectional unordered channel, a unidirectional ordered channel, etc. + +All channels provide exactly-once packet delivery, meaning that a packet sent on one end of a channel is delivered no more and no less than once, eventually, to the other end. + +This specification only concerns itself with *bidirectional ordered* channels. *Unidirectional* and *unordered* channels can use almost exactly the same protocol and will be outlined in a future ICS. + +An *end* of a channel is a data structure on one chain storing channel metadata: + +```typescript +interface ChannelEnd { + state: ChannelEndState + counterpartyChannelIdentifier: Identifier + moduleIdentifier: Identifier + counterpartyModuleIdentifier: Identifier + nextTimeoutHeight: uint64 +} +``` + +- The `state` is the current state of the channel end. +- The `counterpartyChannelIdentifier` identifies the channel end on the counterparty chain. +- The `moduleIdentifier` identifies the module which owns this channel end. +- The `counterpartyModuleIdentifier` identifies the module on the counterparty chain which owns the other end of the channel. +- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent. +- The `nextSequenceRecv`, stored separately, tracks the sequence number for the next packet to be received. +- The `nextTimeoutHeight` stores the timeout height for the next stage of the handshake, used only in channel opening and closing handshakes. + +Channel ends have a *state*: + +```typescript +enum ChannelEndState { + INIT, + OPENTRY, + OPEN, + CLOSETRY, + CLOSED, +} +``` + +- A channel end in `INIT` state has just started the opening handshake. +- A channel end in `OPENTRY` state has acknowledged the handshake step on the counterparty chain. +- A channel end in `OPEN` state has completed the handshake and is ready to send and receive packets. +- A channel end in `CLOSETRY` state has just started the closing handshake. +- A channel end in `CLOSED` state has been closed and can no longer be used to send or receive packets. + +A *packet*, in the interblockchain communication protocol, is a particular datagram, defined as follows: + +```typescript +interface Packet { + sequence: uint64 + timeoutHeight: uint64 + sourceConnection: Identifier + sourceChannel: Identifier + destConnection: Identifier + destChannel: Identifier + data: bytes +} +``` + +- The `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number must be sent and received before a packet with a later sequence number. +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out. +- The `sourceConnection` identifies the connection end on the sending chain. +- The `sourceChannel` identifies the channel end on the sending chain. +- The `destConnection` identifies the connection end on the receiving chain. +- The `destChannel` identifies the channel end on the receiving chain. +- The `data` is an opaque value which can be defined by the application logic of the associated modules. + +### Desired Properties + +#### Efficiency + +- The speed of packet transmission and confirmation should be limited only by the speed of the underlying chains. + Proofs should be batcheable where possible. + +#### Exactly-once delivery + +- IBC packets sent on one end of a channel should be delivered exactly once to the other end. +- No network synchrony assumptions should be required for safety of exactly-once delivery. + If one or both of the chains should halt, packets should be delivered no more than once, and once the chains resume packets should be able to flow again. + +#### Ordering + +- 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`. + +#### Permissioning + +- Channels should be permissioned to one module on each end, determined during the handshake and immutable afterwards (higher-level logic could tokenize channel ownership). + Only the module associated with a channel end should be able to send or receive on it. + +## Technical Specification + +### Dataflow visualization + +The architecture of clients, connections, channels and packets: + +![dataflow](dataflow.png) + +### Preliminaries + +#### Store keys + +Channel structures are stored under a store key prefix unique to a combination of a connection identifier and channel identifier: + +```typescript +function channelKey(connectionIdentifier: Identifier, channelIdentifier: Identifier) { + return "connections/{connectionIdentifier}/channels/{channelIdentifier}" +} +``` + +The `nextSequenceSend` and `nextSequenceRecv` unsigned integer counters are stored separately so they can be proved individually: + +```typescript +function nextSequenceSendKey(connectionIdentifier: Identifier, channelIdentifier: Identifier) { + return channelKey(connectionIdentifier, channelIdentifier) + "/nextSequenceSend" +} + +function nextSequenceRecvKey(connectionIdentifier: Identifier, channelIdentifier: Identifier) { + return channelKey(connectionIdentifier, channelIdentifier) + "/nextSequenceRecv" +} +``` + +Succint commitments to packet data fields are stored under the packet sequence number: + +```typescript +function packetCommitmentKey(connectionIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64) { + return channelKey(connectionIdentifier, channelIdentifier) + "/packets/" + sequence +} +``` + +An additional bit is stored to indicate whether a packet has timed-out: + +```typescript +function packetTimeoutKey(connectionIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64) { + return channelKey(connectionIdentifier, channelIdentifier) + "/packets/" + sequence + "/timeout" +} +``` + +Absence of the key in the store is equivalent to a zero-bit. + +### Subprotocols + +#### Channel lifecycle management + +![channel-state-machine](channel-state-machine.png) + +##### Opening handshake + +The `chanOpenInit` function is called by a module to initiate a channel opening handshake with a module on another chain. +The opening channel must provide the identifiers of the local channel end, local connection, and desired remote channel end. + +When the opening handshake is complete, the module which initiates the handshake will own the end of the created channel on the host ledger, and the counterparty module which +it specifies will own the other end of the created channel on the counterparty chain. Once a channel is created, ownership cannot be changed (although higher-level abstractions +could be implemented to provide this). + +```typescript +interface ChanOpenInit { + connectionIdentifier: Identifier + channelIdentifier: Identifier + counterpartyChannelIdentifier: Identifier + counterpartyModuleIdentifier: Identifier + nextTimeoutHeight: uint64 +} +``` + +```typescript +function chanOpenInit( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, counterpartyModuleIdentifier: Identifier, nextTimeoutHeight: uint64) { + assert(get(channelKey(connectionIdentifier, channelIdentifier)) === nil) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + moduleIdentifier = getCallingModule() + channel = Channel{INIT, moduleIdentifier, counterpartyModuleIdentifier, + counterpartyChannelIdentifier, nextTimeoutHeight} + set(channelKey(connectionIdentifier, channelIdentifier), channel) + set(nextSequenceSendKey(connectionIdentifier, channelIdentifier), 0) + set(nextSequenceRecvKey(connectionIdentifier, channelIdentifier), 0) +} +``` + +The `chanOpenTry` function is called by a module to accept the first step of a chanel opening handshake initiated by a module on another chain. + +```typescript +interface ChanOpenTry { + connectionIdentifier: Identifier + channelIdentifier: Identifier + counterpartyChannelIdentifier: Identifier + moduleIdentifier: Identifier + counterpartyModuleIdentifier: Identifier + timeoutHeight: uint64 + nextTimeoutHeight: uint64 + proofInit: CommitmentProof +} +``` + +```typescript +function chanOpenTry( + connectionIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, + moduleIdentifier: Identifier, counterpartyModuleIdentifier: Identifier, + timeoutHeight: uint64, nextTimeoutHeight: uint64, proofInit: CommitmentProof) { + assert(getConsensusState().height < timeoutHeight) + assert(get(channelKey(connectionIdentifier, channelIdentifier)) === null) + assert(getCallingModule() === moduleIdentifier) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(verifyMembership( + consensusState, + proofInit, + channelKey(connection.counterpartyConnectionIdentifier, counterpartyChannelIdentifier), + Channel{INIT, counterpartyModuleIdentifier, moduleIdentifier, channelIdentifier, timeoutHeight} + )) + channel = Channel{OPENTRY, moduleIdentifier, counterpartyModuleIdentifier, + counterpartyChannelIdentifier, nextTimeoutHeight} + set(channelKey(connectionIdentifier, channelIdentifier), channel) + set(nextSequenceSendKey(connectionIdentifier, channelIdentifier), 0) + set(nextSequenceRecvKey(connectionIdentifier, channelIdentifier), 0) +} +``` + +The `chanOpenAck` is called by the handshake-originating module to acknowledge the acceptance of the initial request by the +counterparty module on the other chain. + +```typescript +interface ChanOpenAck { + connectionIdentifier: Identifier + channelIdentifier: Identifier + timeoutHeight: uint64 + nextTimeoutHeight: uint64 + proofTry: CommitmentProof +} +``` + +```typescript +function chanOpenAck( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + timeoutHeight: uint64, nextTimeoutHeight: uint64, proofTry: CommitmentProof) { + assert(getConsensusState().height < timeoutHeight) + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + assert(channel.state === INIT) + assert(getCallingModule() === channel.moduleIdentifier) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(verifyMembership( + consensusState.getRoot(), + proofTry, + "connections/{connection.counterpartyConnectionIdentifier}/channels/{channel.counterpartyChannelIdentifier}", + Channel{OPENTRY, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + )) + channel.state = OPEN + channel.nextTimeoutHeight = nextTimeoutHeight + set(channelKey(connectionIdentifier, channelIdentifier), channel) +} +``` + +The `chanOpenConfirm` function is called by the handshake-accepting module to acknowledge the acknowledgement +of the handshake-originating module on the other chain and finish the channel opening handshake. + +```typescript +interface ChanOpenConfirm { + connectionIdentifier: Identifier + channelIdentifier: Identifier + timeoutHeight: uint64 + proofAck: CommitmentProof +} +``` + +```typescript +function chanOpenConfirm( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + timeoutHeight: uint64, proofAck: CommitmentProof) { + assert(getConsensusState().height < timeoutHeight) + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + assert(channel.state === OPENTRY) + assert(getCallingModule() === channel.moduleIdentifier) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(verifyMembership( + consensusState.getRoot(), + proofAck, + "connections/{connection.counterpartyConnectionIdentifier}/channels/{channel.counterpartyChannelIdentifier}", + Channel{OPEN, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + )) + channel.state = OPEN + channel.nextTimeoutHeight = 0 + set(channelKey(connectionIdentifier, channelIdentifier), channel) +} +``` + +The `chanOpenTimeout` function is called by either the handshake-originating +module or the handshake-confirming module to prove that a timeout has occurred and reset the state. + +```typescript +interface ChanOpenTimeout { + connectionIdentifier: Identifier + channelIdentifier: Identifier + timeoutHeight: uint64 + proofTimeout: CommitmentProof +} +``` + +```typescript +function chanOpenTimeout( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + timeoutHeight: uint64, proofTimeout: CommitmentProof) { + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(consensusState.height >= connection.nextTimeoutHeight) + switch channel.state { + case INIT: + assert(verifyNonMembership( + consensusState, proofTimeout, + channelKey(connection.counterpartyIdentifier, channel.counterpartyIdentifier) + )) + case OPENTRY: + assert( + verifyNonMembership( + consensusState, proofTimeout, + channelKey(connection.counterpartyIdentifier, channel.counterpartyIdentifier) + ) + || + verifyMembership( + consensusState, proofTimeout, + channelKey(connection.counterpartyIdentifier, channel.counterpartyIdentifier), + Channel{INIT, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + ) + ) + case OPEN: + expected = Channel{OPENTRY, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + assert(verifyMembership( + consensusState, proofTimeout, + channelKey(connection.counterpartyIdentifier, channel.counterpartyIdentifier), + expected + )) + } + delete(channelKey(connectionIdentifier, channelIdentifier)) +} +``` + +##### Closing handshake + +The `chanCloseInit` function is called by either module to initiate +the channel-closing handshake. + +```typescript +interface ChanCloseInit { + connectionIdentifier: Identifier + channelIdentifier: Identifier + nextTimeoutHeight: uint64 +} +``` + +```typescript +function chanCloseInit( + connectionIdentifier: Identifier, channelIdentifier: Identifier, nextTimeoutHeight: uint64) { + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + assert(channel.state === OPEN) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + channel.state = CLOSETRY + channel.nextTimeoutHeight = nextTimeoutHeight + set(channelKey(connectionIdentifier, channelIdentifier), channel) +} +``` + +The `chanCloseTry` function is called by the handshake-accepting module +to acknowledge the channel close request and continue the closing process. + +```typescript +interface ChanCloseTry { + connectionIdentifier: Identifier + channelIdentifier: Identifier + timeoutHeight: uint64 + nextTimeoutHeight: uint64 + proofInit: CommitmentProof +} +``` + +```typescript +function chanCloseTry( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + timeoutHeight: uint64, nextTimeoutHeight: uint64, proofInit: CommitmentProof) { + assert(getConsensusState().getHeight() < timeoutHeight) + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + assert(channel.state === OPEN) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + expected = Channel{INIT, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channel.channelIdentifier, timeoutHeight} + assert(verifyMembership( + consensusState, + proofInit, + "connections/{connection.counterpartyIdentifier}/channels/{channel.counterpartyChannelIdentifier}", + expected + )) + channel.state = CLOSED + channel.nextTimeoutHeight = nextTimeoutHeight + set(channelKey(connectionIdentifier, channelIdentifier), channel) +} +``` + +The `chanCloseAck` function is called by the handshake-originating module +to acknowledge the closing acknowledgement and finalize channel closure. + +```typescript +interface ChanCloseAck { + connectionIdentifier: Identifier + channelIdentifier: Identifier + timeoutHeight: uint64 + proofTry: CommitmentProof +} +``` + +```typescript +function chanCloseAck( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + timeoutHeight: uint64, proofTry: CommitmentProof) { + assert(getConsensusState().getHeight() < timeoutHeight) + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + assert(channel.state === OPEN) + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + expected = Channel{CLOSED, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + assert(verifyMembership( + consensusState.getRoot(), + proofInit, + channelKey(connection.counterpartyIdentifier, channel.counterpartyChannelIdentifier), + expected + )) + channel.state = CLOSED + channel.nextTimeoutHeight = 0 + set(channelKey(connectionIdentifier, channelIdentifier), channel) +} +``` + +The `chanCloseTimeout` function is called by either the handshake-originating +or handshake-accepting module to prove a timeout and reset state. + +```typescript +interface ChanCloseTimeout { + connectionIdentifier: Identifier + channelIdentifier: Identifier + timeoutHeight: uint64 + proofTimeout: CommitmentProof +} +``` + +```typescript +function chanCloseTimeout( + connectionIdentifier: Identifier, channelIdentifier: Identifier, + timeoutHeight: uint64, proofTimeout: CommitmentProof) { + channel = get(channelKey(connectionIdentifier, channelIdentifier)) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(consensusState.getHeight() >= connection.nextTimeoutHeight) + switch channel.state { + case CLOSETRY: + expected = Channel{OPEN, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + case CLOSED: + expected = Channel{CLOSETRY, channel.counterpartyModuleIdentifier, channel.moduleIdentifier, + channelIdentifier, timeoutHeight} + } + verifyMembership( + consensusState, + proofTimeout, + channelKey(connection.counterpartyIdentifier, channel.counterpartyIdentifier), + expected + ) + channel.state = OPEN + channel.nextTimeoutHeight = 0 + set(channelKey(connectionIdentifier, channelIdentifier), channel) +} +``` + +#### Packet flow & handling + +![packet-state-machine](packet-state-machine.png) + +##### Sending packets + +The `sendPacket` function is called by a module in order to send an IBC packet on a channel end owned by the calling module to the corresponding module the counterparty chain. + +Calling modules MUST execute application logic atomically in conjunction with calling `sendPacket`. + +The IBC handler performs the following steps in order: +- Checks that the channel & connection are open to send packets +- Checks that the calling module owns the channel end +- Checks that the packet metadata matches the channel & connection information +- Checks that the timeout height specified has not already passed on the destination chain +- Increments the send sequence counter associated with the channel +- Stores a succinct hash commitment to the packet data + +Note that the full packet is not stored in the state of the chain - merely a short hash-commitment. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index. + +```typescript +function sendPacket(packet: Packet) { + channel = get(channelKey(packet.sourceConnection, packet.sourceChannel)) + assert(channel.state === OPEN) + assert(getCallingModule() === channel.moduleIdentifier) + assert(packet.destChannel === channel.counterpartyChannelIdentifier) + connection = get("connections/{packet.sourceConnection}") + assert(connection.state === OPEN) + assert(packet.destConnection === connection.counterpartyConnectionIdentifier) + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(consensusState.getHeight() < packet.timeoutHeight) + nextSequenceSend = get(nextSequenceSendKey(packet.sourceConnection, packet.sourceChannel)) + assert(packet.sequence === nextSequenceSend) + nextSequenceSend = nextSequenceSend + 1 + set(nextSequenceSendKey(packet.sourceConnection, packet.sourceChannel), nextSequenceSend) + set(packetCommitmentKey(packet.sourceConnection, packet.sourceChannel, sequence), commit(packet.data)) +} +``` + +#### Receiving packets + +The `recvPacket` function is called by a module in order to receive & process an IBC packet sent on the corresponding channel end on the counterparty chain. + +Calling modules MUST execute application logic atomically in conjunction with calling `recvPacket`. + +The IBC handler performs the following steps in order: +- Checks that the channel & connection are open to receive packets +- Checks that the calling module owns the channel end +- Checks that the packet metadata matches the channel & connection information +- Checks that the packet sequence is the next sequence the channel end expects to receive +- Checks that the timeout height has not yet passed +- Checks the inclusion proof of packet data commitment in the outgoing chain's state +- Increments the packet receive sequence associated with the channel end + +```typescript +function recvPacket(packet: Packet, proof: CommitmentProof) { + channel = get(channelKey(packet.destConnection, packet.destChannel)) + assert(channel.state === OPEN) + assert(getCallingModule() === channel.moduleIdentifier) + assert(packet.sourceChannel === channel.counterpartyChannelIdentifier) + nextSequenceRecv = get(nextSequenceRecvKey(packet.destConnection, packet.destChannel)) + assert(packet.sequence === nextSequenceRecv) + connection = get("connections/{connectionIdentifier}") + assert(packet.sourceConnection === connection.counterpartyConnectionIdentifier) + assert(connection.state === OPEN) + consensusState = getConsensusState() + assert(consensusState.getHeight() < packet.timeoutHeight) + assert(verifyMembership( + consensusState.getRoot(), + proof, + packetCommitmentKey(packet.sourceConnection, packet.sourceChannel, packet.sequence), + commit(packet.data) + )) + nextSequenceRecv = nextSequenceRecv + 1 + set(nextSequenceRecvKey(packet.destConnection, packet.destChannel), nextSequenceRecv) +} +``` + +#### Timeouts + +Application semantics may require some timeout: an upper limit to how long the chain will wait for a transaction to be processed before considering it an error. Since the two chains have different local clocks, this is an obvious attack vector for a double spend - an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout - so applications cannot safely implement naive timeout logic themselves. + +Note that in order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination chain is running and reachable. One can prove nothing in a complete network partition, and must wait to connect; the timeout must be proven on the recipient chain, not simply the absence of a response on the sending chain. + +##### Sending end + +The `timeoutPacket` function is called by a module which originally attempted to send a packet to a counterparty module, +where the timeout height has passed on the counterparty chain without the packet being committed, to prove that the packet +can no longer be executed and to allow the calling module to safely perform appropriate state transitions. + +Calling modules MUST atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`. + +```typescript +function timeoutPacket(packet: Packet, proof: CommitmentProof, nextSequenceRecv: uint64) { + channel = get(channelKey(packet.sourceConnection, packet.sourceChannel)) + assert(channel.state === OPEN) + assert(getCallingModule() === channel.moduleIdentifier) + assert(packet.destChannel === channel.counterpartyChannelIdentifier) + + connection = get("connections/{packet.sourceConnection}") + assert(connection.state === OPEN) + assert(packet.destConnection === connection.counterpartyIdentifier) + + // check that timeout height has passed on the other end + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + assert(consensusState.getHeight() >= timeoutHeight) + + // check that packet has not been received + assert(nextSequenceRecv < packet.sequence) + + // verify we actually sent this packet, check the store + assert(get(packetCommitmentKey(packet.sourceConnection, packet.sourceChannel, sequence)) === commit(packet.data)) + + // assert we haven't "timed-out" already + assert(get(packetTimeoutKey(packet.sourceConnection, packet.sourceChannel, sequence)) === nil) + + // check that the recv sequence is as claimed + assert(verifyMembership( + consensusState.getRoot(), + proof, + nextSequenceRecvKey(packet.destConnection, packet.destChannel), + nextSequenceRecv + )) + + // mark the store so we can't "timeout" again + set(packetTimeoutKey(packet.sourceConnection, packet.sourceChannel, sequence), "1") +} +``` + +If relations are enforced between timeout heights of subsequent packets, safe bulk timeouts of all packets prior to a timed-out packet can be performed. +This specification omits details for now. + +###### Receiving end + +The `recvTimeoutPacket` function is called by a module in order to process an IBC packet sent on the corresponding channel which has timed out. +This must be done in order to safely increment the received packet sequence and move on to future packets. + +Calling modules MUST NOT execute any application logic in conjunction with calling `recvTimeoutPacket`. + +```typescript +function recvTimeoutPacket(packet: Packet, proof: CommitmentProof) { + channel = get(channelKey(packet.destConnection, packet.destChannel)) + assert(channel.state === OPEN) + assert(getCallingModule() === channel.moduleIdentifier) + assert(packet.sourceChannel === channel.counterpartyChannelIdentifier) + + connection = get("connections/{connectionIdentifier}") + assert(connection.state === OPEN) + assert(packet.sourceConnection === connection.counterpartyConnectionIdentifier) + + nextSequenceRecv = get(nextSequenceRecvKey(packet.destConnection, packet.destChannel)) + assert(packet.sequence === nextSequenceRecv) + + consensusState = getConsensusState() + assert(consensusState.getHeight() >= packet.timeoutHeight) + + assert(verifyMembership( + consensusState.getRoot(), + proof, + packetCommitmentKey(packet.sourceConnection, packet.sourceChannel, sequence), + commit(packet.data) + )) + + nextSequenceRecv = channel.nextSequenceRecv + 1 + set(nextSequenceRecvKey(packet.destConnection, packet.destChannel), nextSequenceRecv) +} +``` + +##### Cleaning up state + +`cleanupPacket` is called by a module to remove a received packet commitment from storage. The receiving end must have already processed the packet (whether regularly or past timeout). + +```typescript +function cleanupPacket(packet: Packet, proof: CommitmentProof, nextSequenceRecv: uint64) { + channel = get(channelKey(packet.sourceConnection, packet.sourceChannel)) + assert(channel.state === OPEN) + assert(getCallingModule() === channel.moduleIdentifier) + assert(packet.destChannel === channel.counterpartyChannelIdentifier) + + connection = get("connections/{packet.sourceConnection}") + assert(connection.state === OPEN) + assert(packet.destConnection === connection.counterpartyIdentifier) + + // assert packet has been received on the other end + assert(nextSequenceRecv > packet.sequence) + + consensusState = get("clients/{connection.clientIdentifier}/consensusState") + + // check that the recv sequence is as claimed + assert(verifyMembership( + consensusState.getRoot(), + proof, + nextSequenceRecvKey(packet.destConnection, packet.destChannel), + nextSequenceRecv + )) + + // verify we actually sent the packet, check the store + assert(get(packetCommitmentKey(packet.sourceConnection, packet.sourceChannel, sequence)) === commit(packet.data)) + + // clear the store + delete(packetCommitmentKey(packet.sourceConnection, packet.sourceChannel, sequence)) +} +``` + +## Backwards Compatibility + +Not applicable. + +## Forwards Compatibility + +Data structures & encoding can be versioned at the connection or channel level. Channel logic is completely agnostic to packet data formats, which can be changed by the modules any way they like at any time. + +## Example Implementation + +Coming soon. + +## Other Implementations + +Coming soon. + +## History + +5 June 2019 - Draft submitted + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/spec/ics-4-channel-and-packet-semantics/channel-state-machine.png b/spec/ics-4-channel-and-packet-semantics/channel-state-machine.png new file mode 100644 index 000000000000..70b1e223bf8d --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/channel-state-machine.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3313a9411f8be69a4f82d0288d3f1a2919274539faeaf243c0e8b03895e23502 +size 250240 diff --git a/spec/ics-4-channel-and-packet-semantics/channel-state-machine.tex b/spec/ics-4-channel-and-packet-semantics/channel-state-machine.tex new file mode 100644 index 000000000000..85e1bfc8ea8b --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/channel-state-machine.tex @@ -0,0 +1,55 @@ +\documentclass[tikz,10pt,border=10pt]{standalone} +\usepackage{textcomp} +\usepackage{xcolor} +\usetikzlibrary{shapes,arrows} +\begin{document} +\tikzset{% + state/.style = {draw, thick, rectangle, node distance = 6cm} +} +\newcommand{\CA}[1]{\it{A} \textcolor{black}{\textsc{#1}}} +\newcommand{\CB}[1]{\it{B} \textcolor{black}{\textsc{#1}}} +\newcommand{\CS}[2]{\begin{tabular}{l} +\CA{#1} \\ +\CB{#2} +\end{tabular}} + +\begin{tikzpicture}[auto, thick, node distance=2cm, >=triangle 45] +\draw + node [state, name=empty, right = 0.5mm] {\CS{Uninitialized}{Uninitialized}} + node [state, below of=empty] (init) {\CS{Init}{Uninitialized}} + node [state, right of=init] (tryopen) {\CS{Init}{TryOpen}} + node [state, right of=tryopen] (tryopen1) {\CS{Open}{TryOpen}} + node [state, right of=tryopen1] (open) {\CS{Open}{Open}} + node [state, below of=open] (tryclose) {\CS{TryClose}{Open}} + node [state, left of=tryclose] (tryclose1) {\CS{TryClose}{Closed}} + node [state, left of=tryclose1] (closed) {\CS{Closed}{Closed}}; + + \draw node [state, name = key, right = 18cm, above = 2mm] {\begin{tabular}{l} + \it{Key} \\ + \textcolor{blue}{Opening Handshake} \\ + \textcolor{green}{Closing Handshake} \\ + \textcolor{red}{Timeouts} \\ + \end{tabular}}; + + \draw[->, blue, bend right](empty) edge node[yshift=3mm, xshift=1mm] {\tiny{\textsc{ChanInit}}} (init); + \draw[->, blue](init) edge node {\tiny{\textsc{ChanOpenTry}}} (tryopen); + \draw[->, blue](tryopen) edge node {\tiny{\textsc{ChanOpenAck}}} (tryopen1); + \draw[->, blue](tryopen1) edge node {\tiny{\textsc{ChanOpenConfirm}}} (open); + + \draw[->, green, bend right](open) edge node[yshift=3mm, xshift=1mm] {\tiny{\textsc{ChanCloseTry}}} (tryclose); + \draw[->, green](tryclose) edge node {\tiny{\textsc{ChanCloseAck}}} (tryclose1); + \draw[->, green](tryclose1) edge node {\tiny{\textsc{ChanCloseConfirm}}} (closed); + + \draw[->, red, bend right](init) edge node {\tiny{\textsc{ChanOpenTimeout}}} (empty); + \draw[->, red](tryopen) edge node {\tiny{\textsc{ChanOpenTimeout}}} (empty); + \draw[->, red](tryopen1) edge node {\tiny{\textsc{ChanOpenTimeout}}} (empty); + + \draw[->, red, bend right](tryclose) edge node {\tiny{\textsc{ChanCloseTimeout}}} (open); + \draw[->, red](tryclose1) edge node {\tiny{\textsc{ChanCloseTimeout}}} (open); + + \draw[color=black,thick](-0.5,-14) rectangle (22,2.8); + + \node at (-0.5,1.5) [above=0.5mm, right=5mm] {\huge{\textsc{Channel State Machine}}}; + +\end{tikzpicture} +\end{document} diff --git a/spec/ics-4-channel-and-packet-semantics/dataflow.png b/spec/ics-4-channel-and-packet-semantics/dataflow.png new file mode 100644 index 000000000000..273da75e9d66 --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/dataflow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:275be29bcb7438a3895383dbc4bee0591ccbbe608dfa13beee26b36f75a07684 +size 55196 diff --git a/spec/ics-4-channel-and-packet-semantics/dataflow.tex b/spec/ics-4-channel-and-packet-semantics/dataflow.tex new file mode 100644 index 000000000000..0f7eb7b2f8df --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/dataflow.tex @@ -0,0 +1,66 @@ +\documentclass[tikz,10pt,border=10pt]{standalone} +\usepackage{textcomp} +\usepackage{xcolor} +\usetikzlibrary{shapes,arrows} +\begin{document} +\tikzset{% + state/.style = {draw, thick, rectangle, node distance = 6cm} +} +\tikzstyle{arrow} = [thick,->,>=stealth] +\begin{tikzpicture}[auto, thick, node distance=2cm, >=triangle 45] + +\draw (0,0) -- (4,0) -- (4,8) -- (0,8) -- (0,0); + +\node (clientB) [draw] at (2,1) {\textsc{Client 'B'}}; + +\node (conn123a) [draw] at (2,2) {\textsc{Conn '123a'}}; + +\node (chan0x1a) [draw] at (2,3) {\textsc{Chan '0x1a'}}; +\node (chan0x2a) [draw] at (2,4) {\textsc{Chan '0x2a'}}; +\node (chan0x3a) [draw] at (2,5) {\textsc{Chan '0x3a'}}; +\node (chan0x4a) [draw] at (2,6) {\textsc{Chan '0x4a'}}; + +\draw[arrow, bend right] (chan0x1a.west) to (conn123a.west); +\draw[arrow, bend right] (chan0x2a.west) to (conn123a.west); +\draw[arrow, bend right] (chan0x3a.west) to (conn123a.west); +\draw[arrow, bend right] (chan0x4a.west) to (conn123a.west); + +\draw (10,0) -- (14,0) -- (14,8) -- (10,8) -- (10,0); + +\node (clientA) [draw] at (12,1) {\textsc{Client 'A'}}; + +\node (conn123b) [draw] at (12,2) {\textsc{Conn '123b'}}; + +\node (chan0x1b) [draw] at (12,3) {\textsc{Chan '0x1b'}}; +\node (chan0x2b) [draw] at (12,4) {\textsc{Chan '0x2b'}}; +\node (chan0x3b) [draw] at (12,5) {\textsc{Chan '0x3b'}}; +\node (chan0x4b) [draw] at (12,6) {\textsc{Chan '0x4b'}}; + +\draw[arrow, bend left] (chan0x1b.east) to (conn123b.east); +\draw[arrow, bend left] (chan0x2b.east) to (conn123b.east); +\draw[arrow, bend left] (chan0x3b.east) to (conn123b.east); +\draw[arrow, bend left] (chan0x4b.east) to (conn123b.east); + +\draw[arrow] (conn123a) -- (conn123b); +\draw[arrow] (conn123b) -- node[anchor=south] {\textsc{\tiny{Connection Handshake (once)}}} (conn123a); + +\draw[arrow] (conn123a) -- node[anchor=east] {\textsc{\tiny{Verification}}} (clientB); +\draw[arrow] (conn123b) -- node[anchor=west] {\textsc{\tiny{Verification}}} (clientA); + +\draw[arrow] (8,1) -- node[anchor=south east] {\textsc{\tiny{Headers from 'A'}}} (clientA); +\draw[arrow] (6,1) -- node[anchor=south west] {\textsc{\tiny{Headers from 'B'}}} (clientB); + +\draw[arrow] (chan0x1b) -- (chan0x1a); +\draw[arrow] (chan0x1a) -- node[anchor=south] {\textsc{\tiny{Channel Handshake (once), packets (many)}}} (chan0x1b); + +\draw[arrow] (chan0x2b) -- (chan0x2a); +\draw[arrow] (chan0x2a) -- node[anchor=south] {\textsc{\tiny{Channel Handshake (once), packets (many)}}} (chan0x2b); + +\draw[arrow] (chan0x3b) -- (chan0x3a); +\draw[arrow] (chan0x3a) -- node[anchor=south] {\textsc{\tiny{Channel Handshake (once), packets (many)}}} (chan0x3b); + +\draw[arrow] (chan0x4b) -- (chan0x4a); +\draw[arrow] (chan0x4a) -- node[anchor=south] {\textsc{\tiny{Channel Handshake (once), packets (many)}}} (chan0x4b); + +\end{tikzpicture} +\end{document} diff --git a/spec/ics-4-channel-and-packet-semantics/packet-state-machine.png b/spec/ics-4-channel-and-packet-semantics/packet-state-machine.png new file mode 100644 index 000000000000..ae8c54342087 --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/packet-state-machine.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2eb6839f73cf92ab674aa714df494bda52285116765e4e3bdb02337eeeca2364 +size 139070 diff --git a/spec/ics-4-channel-and-packet-semantics/packet-state-machine.tex b/spec/ics-4-channel-and-packet-semantics/packet-state-machine.tex new file mode 100644 index 000000000000..9d2615f7f8be --- /dev/null +++ b/spec/ics-4-channel-and-packet-semantics/packet-state-machine.tex @@ -0,0 +1,41 @@ +\documentclass[tikz,10pt,border=10pt]{standalone} +\usepackage{textcomp} +\usepackage{xcolor} +\usetikzlibrary{shapes,arrows} +\begin{document} +\tikzset{% + state/.style = {draw, thick, rectangle, node distance = 6cm} +} +\newcommand{\CA}[1]{\it{A} \textcolor{black}{\textsc{#1}}} +\newcommand{\CB}[1]{\it{B} \textcolor{black}{\textsc{#1}}} +\newcommand{\CS}[2]{\begin{tabular}{l} +\CA{#1} \\ +\CB{#2} +\end{tabular}} + +\begin{tikzpicture}[auto, thick, node distance=2cm, >=triangle 45] +\draw + node [state, name=start, right = 0.5mm] {\CS{Unsent}{Unreceived}} + node [state, right of=start] (send) {\CS{Sent}{Unreceived}} + node [state, right of=send] (recv) {\CS{Sent}{Received}} + node [state, below of=send] (timeout) {\CS{Sent,TimedOut}{Unreceived}} + node [state, right of=timeout] (timeoutfinal) {\CS{Sent,TimedOut}{TimedOut}}; + + \draw node [state, name = key, right = 18cm, above = 2mm] {\begin{tabular}{l} + \it{Key} \\ + \textcolor{blue}{Standard Flow} \\ + \textcolor{red}{Timeout Flow} \\ + \end{tabular}}; + + \draw[->, blue](start) edge node[yshift=3mm, xshift=1mm] {\tiny{\textsc{sendPacket}}} (send); + \draw[->, blue](send) edge node {\tiny{\textsc{recvPacket}}} (recv); + + \draw[->, red](send) edge node {\tiny{\textsc{timeoutPacket}}} (timeout); + \draw[->, red](timeout) edge node {\tiny{\textsc{recvTimeoutPacket}}} (timeoutfinal); + + \draw[color=black,thick](-1.5,-8) rectangle (20,2); + + \node at (-0.5,1.5) [above=0.5mm, right=5mm] {\huge{\textsc{Packet State Machine}}}; + +\end{tikzpicture} +\end{document}