-
Notifications
You must be signed in to change notification settings - Fork 733
Connection handshake
This document attempts to explore IBC's connection handshake in detail, showing as much practical information as possible.
- Two chains (
chain1andchain2) running ib-gosimdbinary. - hermes relayer.
This document is updated for ibc-go v3.0.0 and hermes 0.14.0.
This tutorial assumes that both chains have already gone through the creation of a light client for the counterparty chain. In this tutorial the client ID of the light client on both chains is 07-tendermint-0.
Connection-related information is stored in the IBC store:
| key | value |
|---|---|
[]byte("nextConnectionSequence") |
Next available connection sequence. |
[]byte("connections/" + {connection-id}) |
Connection end |
[]byte("clients/" + {clientID} + "/connections") |
Set of connection IDs associated with a particular client ID |
> ./hermes -c config.toml create connection chain1 chain2
Connection handshake in hermes.
In the first step of the connection handshake the relayer submits MsgConnectionOpenInit on chain1 (see the message proto definition).
In hermes MsgConnectionOpenInit is constructed and sent in function build_conn_init_and_send. More specifically, the message is constructed in build_conn_init.
The parameters needed to construct MsgConnectionOpenInit are:
-
client_idis the client ID ofchain1(07-tendermint-0in this tutorial). -
counterpartyis constructed here:-
client_idis the client ID ofchain2(07-tendermint-0in this tutorial). -
connection_idis empty, sincechain2does not have a connection ID yet. -
prefixis assigned at the moment the value read from the config file of hermes. In the future this value could be queried fromchain2. This prefix is the store prefix used by the on-chain IBC module for Cosmos-SDK chains and at the moment isibc.
-
-
versionis assigned the default value. In the future this value could also be queried fromchain2. This default value matches ibc-go'sDefaultIBCVersion("identifier": "1", "features": ["ORDER_ORDERED", "ORDER_UNORDERED"]). -
delay_periodis assigned the value entered in the command line parameter of thecreate connectioncommand of hermes, if any; otherwise it takes the default value 0. The delay period is the time that must pass before a consensus state can be used for packet verification. -
signeris the address of the relayer that submits the message.
From the hermes log we can retrieve the hash of the transaction that executed MsgConnectionOpenInit:
2022-04-23T19:52:36.928880Z DEBUG ThreadId(07) send_tx_commit{id=ConnectionOpenInit}:send_tx{id=chain1}: broadcast_tx_sync: Response { code: Ok, data: Data([]), log: Log("[]"), hash: transaction::Hash(E5078546601DC954E2B3461834B52AA4A2E6ED53EE947E8D6D4F29388120FFF7) }
And we can use this hash to get the transaction information using Tendermint's /tx RPC endpoint:
http://localhost:27000/tx?hash=0xE5078546601DC954E2B3461834B52AA4A2E6ED53EE947E8D6D4F29388120FFF7
See sample JSON result.
The value in the field result.tx is a base64-encoded string of the bytes of the messages that were executed as part of the transaction. This transaction contains only one message (MsgConnectionOpenInit) and if we decode these bytes we can retrieve back the message data that the relayer submitted:
client_id:"07-tendermint-0"
counterparty:
<
client_id:"07-tendermint-0"
prefix:
<
key_prefix:"ibc"
>
>
version:
<
identifier:"1"
features:"ORDER_ORDERED"
features:"ORDER_UNORDERED"
>
signer:"cosmos1cq6cexgmryrsjnghlu3fjjzfnyy07z8ap0cfhm"
The counterparty here is chain2.
This picture shows the execution flow of MsgConnectionOpenInit in ibc-go:

After the message reaches the message server the execution continues in ConnectionKeeper's ConnOpenInit function, to which the message fields are passed in. The picture above shows also the correspondance between the implementation and the spec for handling a connection open init request. The steps in ConnOpenInit consist of:
types.GetCompatibleVersions returns the latest supported version of IBC used in connection version negotiation. These versions will be used in the creation of the connection end if the message does not provide any or the one provided is not supported. types.IsSupportedVersion returns true if the proposed version has a matching version in the list of compatible versions.
A new connection identifier is generated by retrieving the next connection sequence from the store. In this tutorial, the generated connection ID is connection-0.
A new ConnectionEnd is instantiated with the information from the message and with the state INIT.
The connection end is stored under the key connections/connection-0. The connection ID is also added to the set of connections associated with a client (which is stored under key clients/"07-tendermint-0/connections in this tutorial).
An event is emitted signalling that the connection open init has finished successfully. From the event information (which can also be found in the field result.tx_result.log of the transaction that included MsgConnectionOpenInit) it is possible to figure out what connection ID this new connection end has:
{
"events": [
{
"type": "connection_open_init",
"attributes": [
{
"key": "connection_id",
"value": "connection-0"
},
{
"key": "client_id",
"value": "07-tendermint-0"
},
{
"key": "counterparty_client_id",
"value": "07-tendermint-0"
},
{
"key": "counterparty_connection_id"
}
]
},
{
"type": "message",
"attributes": [
{
"key": "action",
"value": "/ibc.core.connection.v1.MsgConnectionOpenInit"
},
{
"key": "module",
"value": "ibc_connection"
}
]
}
]
}After MsgConnectionOpenInit successfully executes, there is a connection end stored on chain1. We can use ibc-go's REST interface to check the existence of the connection end with connection ID connection-0:
http://localhost:27001/ibc/core/connection/v1/connections/connection-0
{
"connection": {
"client_id": "07-tendermint-0",
"versions": [
{
"identifier": "1",
"features": [
"ORDER_ORDERED",
"ORDER_UNORDERED"
]
}
],
"state": "STATE_OPEN",
"counterparty": {
"client_id": "07-tendermint-0",
"connection_id": "connection-0",
"prefix": {
"key_prefix": "aWJj"
}
},
"delay_period": "0"
},
"proof": null,
"proof_height": {
"revision_number": "0",
"revision_height": "10008"
}
}After MsgConnectionOpenInit succeeds on chain1, then the relayer submits MsgConnectionOpenTry on chain2 (see the message proto definition.
In hermes MsgConnectionOpenTry is constructed and sent in function build_conn_try_and_send. More specifically, the message is constructed in build_conn_try.
To build MsgConnectionOpenInit hermes queries chain1 at a certain height to get proofs for the following things:
- A proof that
chain1has stored a connection end with connection IDconnection-0and stateINIT. - A proof that
chain1has stored for client ID07-tendermint-0the client state forchain2. - A proof that
chain1has stored for client ID07-tendermint-0the consensus state forchain2.
In this tutorial the height at which hermes is going to query chain1 for these proofs is 5. The query height is obtained here by querying Tendermint's /status RPC endpoint and
reading the values for result.node_info.network (which is the revision number, chain1) and result.sync_info.latest_block_height (which is the revision height, 5).
The parameters needed to construct MsgConnectionOpenInit are thus:
-
client_idis the client ID ofchain2(07-tendermint-0in this tutorial). -
previous_connection_idis the connection ID ofchain2in case there exists a connection already in stateINIT. The way hermes figures this out is by sending a query request to ibc-go's gRPC query endpoint for a single connection. This query returns the connection end onchain1, which includes thecounterpartyinformation with its respectiveconnection_id. In this tutorial theprevious_connection_idis empty, since there is no connection onchain2in stateINITand therefore there is no connection end onchain2. -
client_stateis the client state forchain2thatchain1has stored. It is retrieved here by performing an ABCI RPC query to theibcstore ofchain1. We will see a sample of the client state data in theproof_clientitem a bit further down. -
counterpartyis constructed here:-
client_idis the client ID ofchain1(07-tendermint-0in this tutorial). -
connection_idis the connection ID for the connection end onchain1, which isconnection-0in this tutorial. -
prefixis determined here at the moment in a similar way as it was done forMsgConnectionOpenInit(i.e. reading the valueibcfrom the config file of hermes).
-
-
delay_periodis determined here. -
counterparty_versionsis assigned here the versions from the connection end onchain1, if they exist; or the default, otherwise (as done similarly forMsgConnectionOpenInit). -
proof_heightis the height for the commitment root for proving the proofs for init, client and consensus. When creating these proofs,chain1is queried at heightproof_height- 1 (i.e. 5 in this tutorial). -
proof_initis obtained by performing an ABCI RPC query to theibcstore ofchain1. We can make the same ABCI RPC query on the browser by simply using Tendermint's/abci_queryREST endpoint:
path: `store/ibc/key`
data: `connections/connection-0` (in hex: 0x636f6e6e656374696f6e732f636f6e6e656374696f6e2d30)
prove: true (so that the response contains the merkle proof)
height: 5
URL: http://localhost:27000/abci_query?path="store/ibc/key"&data=0x636f6e6e656374696f6e732f636f6e6e656374696f6e2d30&prove=true&height=5
See sample JSON output.
If we take base64-encoded byte string from the value for the field result.response.value and we decode it, then we can inspect the information for the connection end stored in chain1:
{
ClientId:07-tendermint-0
Versions:[
identifier:"1"
features:"ORDER_ORDERED"
features:"ORDER_UNORDERED"
]
State:STATE_INIT
Counterparty:{
ClientId:07-tendermint-0
ConnectionId:
Prefix: {
KeyPrefix:[105 98 99]
}
}
DelayPeriod:0
}
The key_prefix is byte representation of the string "ibc".
Out of curiosity we can also take the value for the result.response.proofOps field and decode it to retrieve the merkle proof:
{
[
exist:
<
key:"connections/connection-0" value:"\n\01707-tendermint-0\022#\n\0011\022\rORDER_ORDERED\022\017ORDER_UNORDERED\030\001\"\030\n\01707-tendermint-0\032\005\n\003ibc"
leaf:
<
hash:SHA256
prehash_value:SHA256
length:VAR_PROTO
prefix:"\000\002\010"
>
path:
<
hash:SHA256
prefix:"\004\006\n {\351\212\t$\037\326\325\251\210\025/\271\243\335\200M\221\327\023\006\204Vo\217\242\237\270\220\303R\n "
>
path:
<
hash:SHA256
prefix:"\006\014\n "
suffix:" \"\014\317[\032e\236\\Vj\252\227\036rO\371\247@\013\267\022\234\001\372m\245\027\377\3717~K"
>
path:
<
hash:SHA256
prefix:"\n\034\n \026\t\324\340\335\2416\232\370f_\261{\321\375\326\345\n\025\365\214\303r\250\374\213\341\002\243\2402t "
>
>
exist:
<
key:"ibc"
value:"\277\364\362YM\200\266\241\232}\377F\220g9cqxc\232N\313L)\307\231\345)]=\225\364"
leaf:
<
hash:SHA256
prehash_value:SHA256
length:VAR_PROTO
prefix:"\000"
>
path:
<
hash:SHA256
prefix:"\001"
suffix:"\305\005\300\375H\261\317+ea\237\022\263\024N\031\306\013NkbR^\3516\276\223\320Ab>\277"
>
path:
<
hash:SHA256
prefix:"\001"
suffix:"@\251\313\2625\350\307\246\356 \356\305\247\231 @E\275\016\3256<6\270\036X;\241\023\230_\372"
>
path:
<
hash:SHA256
prefix:"\001"
suffix:"\323C\255q0\267:\211\365Q\203\221\013\260k\237\255\245\206<\235M2y\254\236\233\352\373S\241:"
>
path:
<
hash:SHA256
prefix:"\001\304\300j8q~\n{V\031\252\277\"{l=V\344\200\365\377F|D\\\260D\207\023\023J\202"
>
path:
<
hash:SHA256
prefix:"\001"
suffix:"\367\235\232\0273q2\204\220#\225k\302\334\350\370\262\206\366@4\024\004_\037\243\037M\332K\272i"
>
>
]
}
-
proof_clientis obtained by performing an ABCI RPC query to theibcstore ofchain1. This query intends to check thatchain1has stored for client ID07-tendermint-0the client state forchain2at a certain height (5 in this tutorial). We can make the same ABCI RPC query on the browser by simply using Tendermint's/abci_queryREST endpoint:
path: store/ibc/key
data: clients/07-tendermint-0/clientState (in hex: 0x636c69656e74732f30372d74656e6465726d696e742d302f636c69656e745374617465)
prove: true (so that the response contains the merkle proof)
height: 5
http://localhost:27000/abci_query?path="store/ibc/key"&data=0x636c69656e74732f30372d74656e6465726d696e742d302f636c69656e745374617465&prove=true&height=5
See sample JSON output.
If we take base64-encoded byte string from the value for the field result.response.value and we decode it, then we can inspect the information for the client state of chain2 stored in chain1:
chain_id:"chain2"
trust_level:
<
numerator:1
denominator:3
>
trusting_period:
<
seconds:1209600
>
unbonding_period:
<
seconds:1814400
>
max_clock_drift:
<
seconds:20
>
frozen_height:
<
>
latest_height:
<
revision_height:3
>
proof_specs:
<
leaf_spec:
<
hash:SHA256
prehash_value:SHA256
length:VAR_PROTO
prefix:"\000"
>
inner_spec:
<
child_order:0
child_order:1
child_size:33
min_prefix_length:4
max_prefix_length:12
hash:SHA256
>
>
proof_specs:
<
leaf_spec:
<
hash:SHA256
prehash_value:SHA256
length:VAR_PROTO
prefix:"\000"
>
inner_spec:
<
child_order:0
child_order:1
child_size:32
min_prefix_length:1
max_prefix_length:1
hash:SHA256
>
>
upgrade_path:"upgrade"
upgrade_path:"upgradedIBCState"
allow_update_after_expiry:true
allow_update_after_misbehaviour:true
We see that the latest_height has a revision_height of 3, which means that the client state for chain2 was lastt updated for block height 3 of chain2.
-
proof_consensusis obtained by performing an ABCI RPC query to theibcstore ofchain1. This query intends to check thatchain1has stored at a certain height (5 in this tutorial) for client ID07-tendermint-0the consensus state at a given height (3 in this tutorial) forchain2. We can make the same ABCI RPC query on the browser by simply using Tendermint's/abci_queryREST endpoint. Thedataquery parameter follows the formatclients/{client-id}/consensusStates/{epoch}-{height}.epochis the revision number (0 in this tutorial) andheightis the revision height (3 in this tutorial), both retrieved from thelatest_heightfield of the client state stored onchain1.
path: store/ibc/key
data: clients/07-tendermint-0/consensusStates/0-3 (in hex 0x636c69656e74732f30372d74656e6465726d696e742d302f636f6e73656e7375735374617465732f302d33)
prove: true
height: 5
http://localhost:27000/abci_query?path="store/ibc/key"&data=0x636c69656e74732f30372d74656e6465726d696e742d302f636f6e73656e7375735374617465732f302d33&prove=true&height=5
See sample JSON output.
If we take base64-encoded byte string from the value for the field result.response.value and we decode it, then we can inspect the information for the consensus state of chain2 (at block height 3) stored in chain1 (at block height 5):
timestamp:
<
seconds:1650743551
nanos:180695000
>
root:
<
hash:"4\261\263\352|=\367D\001\325\001\310o\347n\242\220\226z\377Ds\366\247\006cM\371\326\3379\206"
>
next_validators_hash:"d\323Z2\216/\0039\002\351\311\351m\017.\002o\342g\311s\223\rm\202\336\026\006\242\032\354\240"
The timestamp corresponds to the block height in which the consensus state was stored. root is the app hash for block 3 of chain2 and next_validators_hash is the hash of the next validator set. If we query chain2 locally for block at height 3 by going on the browser to http://localhost:27010/block?height=3 we can check that the value in the response for the field result.block.header.app_hash matches the root hash from the consensus state (see the sample JSON output).
-
consensus_heightis the latest height ofchain2whichchain1has stored in itschain2light client. In this tutorial it is 3 (i.e. the value forlatest_heightin the stored client state). -
signeris the address of the relayer that submits the message.
MsgConnectionOpenTry
{ClientId:07-tendermint-0 Versions:[identifier:"1" features:"ORDER_ORDERED" features:"ORDER_UNORDERED" ] State:STATE_INIT Counterparty:{ClientId:07-tendermint-0 ConnectionId: Prefix:{KeyPrefix:[105 98 99]}} DelayPeriod:0}