IF IBC 11
IF-IBC-11
ICS03/04 - Crossing hellos with fixed identifier are not live
Severity: Medium
Type: protocol/implementation bug
Difficulty: medium
Involved artifacts:
ICS 03 specification,
ICS 04 specification,
ibc/core/03-connection/keeper/handshake.go
ibc/core/04-channel/keeper/handshake.go
Description
When two chains want to open a connection (channel), they may
both initialize their connection (channel) ends by calling
connOpenInit (chanOpenInit), before a correct relayer creates
a ConnOpenTry (ChanOpenTry) datagram for either chain.
In this scenario, in both the code and specification
of the connection (channel) handshake protocols,
the two chains may initialize their connection (channel)
ends with parameters that do not match.
This causes both chains to abort when calling connOpenTry (chanOpenTry).
Thus, their already initialized connection (channel) ends
remain in state INIT forever,
which violates the following liveness property:
If a connection (channel) end is initialized on a chain, then eventually both the connection (channel) end, stored on the chain, and the connection (channel) end, stored at its counterparty, are open.
In the following, we discuss this issue by focusing on the
specification of the connection handshake; the discussion for
the channel handshake, as well as the implementations, is analogous.
We show a scenario where two chains want to open a connection but
have mismatched client identifiers.
Observe that this issue may arise not only in cases where client identifiers are
mismatched, but also when connection, channel, port identifiers, versions, prefixes,
or orderings coming from a ConnOpenTry (ChanOpenTry) datagram
do not match the values stored in the existing connection (channel) end on the receiving chain.
Problem Scenarios
When two chains want to open a connection, they may
both initialize their connection ends by calling connOpenInit,
which may result in a chain assigning values to the
fields in its connection end that do not match the
values of the respective fields in the counterparty connection end
(even if the connection and counterparty connection identifiers match).
In this scenario, once a correct relayer creates a ConnOpenTry datagram for
each chain, the call to connOpenTry fails on at least one of the chains.
This implies that the connection handshake does not progress,
and the above liveness property is violated.
In more detail, consider the scenario where two chains,
"ChainA" and "ChainB", want to open a connection.
Suppose that "ChainA" has two clients for "ChainB", identified by
the client identifiers "clientB1" and "clientB2".
Suppose that "ChainB" has a single client for "ChainA", identified by
the client identifier "clientA1".
To open a connection, both chains execute the following steps:
"ChainA"callsconnOpenInitand initializes its connection end with the following values:- connection identifier:
"connAtoB", - counterparty connection identifier:
"connBtoA", - client identifier:
"clientB1", - counterparty client identifier:
"clientA1",
- connection identifier:
"ChainB"callsconnOpenInitand initializes its connection end with the following values:- connection identifier:
"connBtoA", - counterparty connection identifier:
"connAtoB", - client identifier:
"clientA1", - counterparty client identifier:
"clientB2",
- connection identifier:
- a correct relayer creates a
ConnOpenTrydatagram for"ChainB"by scanning"ChainA"'s state, where the fieldclientIdentifieris set to"clientA1", and the fieldcounterpartyClientIdentiferis set to"clientB1". - a correct relayer creates a
ConnOpenTrydatagram for"ChainA"by scanning"ChainB"'s state, where the fieldclientIdentifieris set to"clientB2", and the fieldcounterpartyClientIdentiferis set to"clientA1". "ChainB"receives theConnOpenTrydatagram and callsconnOpenTry. A connection end on"ChainB"is already initialized, and the connection and counterparty connection identifiers match those from theConnOpenTrydatagram. However, the existing connection end hascounterpartyClientIdentiferset to"clientB2", which does not match the identifer"clientB1", coming from the fieldcounterpartyClientIdentiferfrom theConnOpenTrydatagram."ChainA"receives theConnOpenTrydatagram and callsconnOpenTry. A connection end on"ChainA"is already initialized, and the connection and counterparty connection identifiers match those from theConnOpenTrydatagram. However, the existing connection end hasclientIdentiferset to"clientB1", which does not match the identifer"clientB2", coming from the fieldcounterpartyClientIdentiferfrom theConnOpenTrydatagram.
The call to connOpenTry thus fails here, on both sides:
abortTransactionUnless(
(previous === null) ||
(previous.state === INIT &&
previous.counterpartyConnectionIdentifier ===
counterpartyConnectionIdentifier &&
previous.counterpartyPrefix === counterpartyPrefix &&
previous.clientIdentifier === clientIdentifier &&
previous.counterpartyClientIdentifier ===
counterpartyClientIdentifier))
where previous is the initialized connection end.
Recommendation
Add a mechanism in both the specification, and the implementation to deal with mismatched parameters.
Note
At the time of writing this report, the following issues were opened to address this problem in the case when the connection (channel) identifiers do not match: Cosmos-ICS #491 and Cosmos-SDK #7870.