Adding a Chain for IBC relaying is composed of two main components:
ChainProvider
implementationChainProcessor
implementation
The ChainProvider
implementation contains the methods required to query for relevant data, assemble IBC messages to be sent to the chain, and manage the keys for the wallets that will be sending transactions to the chain.
ChainProvider
non-imported methods are used for assembling messages with intention of sending to the chain. The PathProcessor
uses these during runtime. The CLI methods also use these for things such as linking paths and flushing packets and acks.
KeyProvider
methods are used for the key lifecycle, used by the CLI to manage relayer wallets.
QueryProvider
methods are all of the queries against blockchain nodes that are needed for relaying.
The ChainProcessor
implementation is responsible for staying in sync with the chain, either through polling or pub/sub, and sharing IBC messages and other relevant IBC information such as IBC headers, client states, connection states, and channel states with the PathProcessor
.
type ChainProcessor interface {
Run(ctx context.Context, initialBlockHistory uint64) error
Provider() provider.ChainProvider
SetPathProcessors(pathProcessors PathProcessors)
}
The implementation should use the ChainProvider
when possible to make queries.
At the beginning of the Run
method, the latest committed height of the chain should be queried with a retry on error. Once the latest height has been determined, the initialBlockHistory
parameter should be subtracted to determine which block should be queried first.
After this, before the main poll loop or subscriber begins, two ChainProcessor
caches should be initialized:
// holds open state for known connections
connectionStateCache processor.ConnectionStateCache
// holds open state for known channels
channelStateCache processor.ChannelStateCache
These caches are aliased types to map[ConnectionKey]bool
and map[ChannelKey]bool
respectively. The PathProcessor
needs to know which connections are open and which channels are open. A value of true
for the specific ConnectionKey
or ChannelKey
will inform the PathProcessor
that the connection or channel is open later on once these caches are shared with the PathProcessor
.
During the initalization of these caches, separate mappings should also be built for which connections belong to which clients and which channels belong to which connections. The example of these in the CosmosChainProcessor
are:
// map of connection ID to client ID
connectionClients map[string]string
// map of channel ID to connection ID
channelConnections map[string]string
These are used later on when sharing data with the PathProcessor
(s) to filter channels and connections for a single client ID, since a PathProcessor
is scoped to two chains, and a single client per chain.
After these four caches are initialized in Run
, the main poll loop or subscriber can begin.
The CosmosChainProcessor
uses a poll loop queryCycle
to stay in sync with the latest chain blocks and IBC messages in those blocks. This loop will run frequently to check for new blocks and parse any IBC messages in those blocks. This poll loop starts in the Run
function after the Startup
tasks above.
It stores any new IBCHeader
s into an IBCHeaderCache
(map[uint64]provider.IBCHeader
), which are necessary so that the PathProcessor
can update the light clients.
A chain-specific IBCHeader
implementation is required:
type IBCHeader interface {
Height() uint64
ConsensusState() ibcexported.ConsensusState
// require conversion implementation for third party chains
ToCosmosValidatorSet() (*tmtypes.ValidatorSet, error)
}
For reference, view the CosmosIBCHeader
implementation in the Cosmos Provider.
- Query latest committed chain height
- Initialize
IBCHeaderCache
andIBCMessagesCache
- Iterate for height
i
from last successfully processed height to latest height:
- a. Query transactions within block at height
i
- b. Query
IBCHeader
for heighti
(done in parallel with block transactions) - c. Save latest block height and time on
ChainProcessor
(typeprovider.LatestBlock
) - d. Cache
IBCHeader
inIBCHeaderCache
from step2
. - e. Iterate through transactions in block, and IBC messages within those transactions. Construct IBC message types that can be shared with the
PathProcessor
(provider.PacketInfo
,provider.ChannelInfo
,provider.ConnectionInfo
), and cache those messages on theIBCMessagesCache
from step2
. When observing these messages, theChainProcessor
caches should be updated, such as setting the value inconnectionStateCache
tofalse
if a connection open init or try event is observed, andtrue
if a connection open ack or confirm event is observed. For more information about these steps, see Event Parsers and Message Handlers below. - f. Save the latest successfully processed height
- If no new blocks were processed, but the
ChainProcessor
is now in sync with the latest height of the chain, trigger thePathProcessor
s withpp.ProcessBacklogIfReady()
- If new blocks were processed, iterate through the
PathProcessor
s and pass the relevant data to them:
- a. Latest block from
3c
- b. Latest
IBCHeader
from3b
for most recent successfully queried block - c. All new
IBCHeader
s in theIBCHeaderCache
(built by steps2
and3d
) - d. All new IBC messages in the
IBCMessagesCache
(built by steps2
and3e
) - e.
InSync
for whether the latest successfully processed block is the latest block of the chain - f.
ClientState
for the latestConsensusHeight
of the relevant client.CosmosChainProcessor
will query for this if it's not yet cached on thelatestClientState
, otherwise it will return the most recent cached value. - g.
ConnectionStateCache
with the connection states filtered for only the connections on the relevant client - h.
ChannelStateCache
with the channel states filtered for only the channels on the relevant client
For tendermint chains, the IBC messages are parsed in the CosmosChainProcessor
by parsing the tendermint events from every new block. This will be different for non-tendermint chains, but these items will need to be accounted for:
- For client IBC messages (e.g. MsgCreateClient, MsgUpdateClient, MsgUpgradeClient, MsgSubmitMisbehaviour), message should be parsed into
provider.ClientState
. - For connection handshake IBC messages (e.g. MsgConnectionOpenInit, MsgConnectionOpenTry, MsgConnectionOpenAck, MsgConnectionOpenConfirm), message should be parsed into
provider.ConnectionInfo
- For channel handshake IBC messages (e.g. MsgChannelOpenInit, MsgChannelOpenTry, MsgChannelOpenAck, MsgChannelOpenConfirm, MsgChannelCloseInit, MsgChannelCloseConfim), message should be parsed into
provider.ChannelInfo
- For packet-flow IBC messages (e.g. MsgTransfer, MsgRecvPacket, MsgAcknowledgement), message should be parsed into
provider.PacketInfo
After IBC messages have been parsed from the blocks, some actions are necessary to keep the ChainProcessor
local caches up to date and also construct the data that will be shared with the PathProcessor
(s):
- For new packet messages that have been parsed into
provider.PacketInfo
by the event parsers or similar, check if the packet is relevant to any of the connectedPathProcessor
(s) by callingIBCMessagesCache.PacketFlow.ShouldRetainSequence
. If true, retain the message withIBCMessagesCache.PacketFlow.Retain
. This allows the relayer to avoid unnecessary processing by dropping packets that will not be ignored by all connectedPathProcessor
s. - For client messages, update the
ChainProcessor
locallatestClientState
cache by storing the parsedprovider.ClientState
in the map for the client ID key. - For connection handshake messages that have been parsed into
provider.ConnectionInfo
, update theChainProcessor
connectionStateCache
. MsgConnectionOpenAck and MsgConnectionOpenConfirm mean the connection is open. MsgConnectionOpenInit and MsgConnectionOpenTry mean the connection is not open. Finally, retain the message unconditionally withIBCMessagesCache.ConnectionHandshake.Retain
- For channel handshake messages that have been parsed into
provider.ChannelInfo
, update theChainProcessor
channelConnections
cache to save the connection ID for the channel. Additionally, update theChainProcessor
channelStateCache
with the open state of the channel. MsgChannelOpenAck and MsgChannelOpenConfirm mean the channel is open. MsgChannelOpenInit, MsgChannelOpenTry, and MsgChannelCloseConfirm mean the channel is not open. Finally, retain the message unconditionally withIBCMessagesCache.ChannelHandshake.Retain