-
Notifications
You must be signed in to change notification settings - Fork 726
Connection handshake
This document attempts to explore IBC's connection handshake in detail, showing as much practical information as possible.
- Two chains (
chain1
andchain2
) running ib-gosimd
binary. - 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_id
is the client ID ofchain1
(07-tendermint-0
in this tutorial). -
counterparty
is constructed here:-
client_id
is the client ID ofchain2
(07-tendermint-0
in this tutorial). -
connection_id
is empty, sincechain2
does not have a connection ID yet. -
prefix
is 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
.
-
-
version
is 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_period
is assigned the value entered in the command line parameter of thecreate connection
command 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. -
signer
is 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
chain1
has stored a connection end with connection IDconnection-0
and stateINIT
. - A proof that
chain1
has stored for client ID07-tendermint-0
the client state forchain2
. - A proof that
chain1
has stored for client ID07-tendermint-0
the 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_id
is the client ID ofchain2
(07-tendermint-0
in this tutorial). -
previous_connection_id
is the connection ID ofchain2
in 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 thecounterparty
information with its respectiveconnection_id
. In this tutorial theprevious_connection_id
is empty, since there is no connection onchain2
in stateINIT
and therefore there is no connection end onchain2
. -
client_state
is the client state forchain2
thatchain1
has stored. It is retrieved here by performing an ABCI RPC query to theibc
store ofchain1
. We will see a sample of the client state data in theproof_client
item a bit further down. -
counterparty
is constructed here:-
client_id
is the client ID ofchain1
(07-tendermint-0
in this tutorial). -
connection_id
is the connection ID for the connection end onchain1
, which isconnection-0
in this tutorial. -
prefix
is determined here at the moment in a similar way as it was done forMsgConnectionOpenInit
(i.e. reading the valueibc
from the config file of hermes).
-
-
delay_period
is determined here. -
counterparty_versions
is assigned here the versions from the connection end onchain1
, if they exist; or the default, otherwise (as done similarly forMsgConnectionOpenInit
). -
proof_height
is the height for the commitment root for proving the proofs for init, client and consensus. When creating these proofs,chain1
is queried at heightproof_height
- 1 (i.e. 5 in this tutorial). -
proof_init
is obtained by performing an ABCI RPC query to theibc
store ofchain1
. We can make the same ABCI RPC query on the browser by simply using Tendermint's/abci_query
REST 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_client
is obtained by performing an ABCI RPC query to theibc
store ofchain1
. This query intends to check thatchain1
has stored for client ID07-tendermint-0
the client state forchain2
at a certain height (5 in this tutorial). We can make the same ABCI RPC query on the browser by simply using Tendermint's/abci_query
REST 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_consensus
is obtained by performing an ABCI RPC query to theibc
store ofchain1
. This query intends to check thatchain1
has stored at a certain height (5 in this tutorial) for client ID07-tendermint-0
the 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_query
REST endpoint. Thedata
query parameter follows the formatclients/{client-id}/consensusStates/{epoch}-{height}
.epoch
is the revision number (0 in this tutorial) andheight
is the revision height (3 in this tutorial), both retrieved from thelatest_height
field 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_height
is the latest height ofchain2
whichchain1
has stored in itschain2
light client. In this tutorial it is 3 (i.e. the value forlatest_height
in the stored client state). -
signer
is 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}