Today, we’re excited to announce the release of channel upgradability in ibc-go v8.1.0. Channel upgradability allows IBC channels to upgrade and leverage new features without having to coordinate a network upgrade or open a new channel and thereby forego token fungibility.
By enabling this feature, chains can:
Add fee middleware on existing channels to incentivize IBC relayers.
Adopt ICS-20 v2 (planned for later this year).
Migrate from ordered Interchain Accounts (ICA) channels to unordered ones.
Change connection hops if the application stack allows it.
Prune stale acknowledgements and packet receipts to reduce disk overhead.
In this blog, we cover why channel upgradability is essential, what it can be used for, and how it works.
In the initial version of ibc-go, there was no way to adopt new channel features without opening a new channel, where ‘channel feature’ here refers to any feature for which a new version string needs to be negotiated as part of the channel handshake protocol, or one that requires a middleware to be added to both channel ends.
For example, if a chain wanted to use relayer fee middleware, it could only be added to a new channel. High-volume channels have a lot of state and network effects that have accumulated over time (e.g. channel-0 and channel-141 between Osmosis and Cosmos Hub). It was impractical for chains to discard that to adopt a new IBC-level channel feature.
By using channel upgradability, chains can now add fee middleware not just to new channels but also to existing ones.
Apart from using it to enable fee middleware, channel upgradability allows you to upgrade application modules, such as upgrading ICS-20 token transfer from v1 to v2.
We are currently in the process of researching ICS-20 v2 which might include features like supporting multiple denoms within a single packet and having path unwinding natively within the protocol. By leveraging channel upgradability, your chain can use new ICS-20 features upon launch (planned for later this year).
As part of the packet lifecycle in ibc-go, when chain A sends a message to chain B, it writes a packet commitment to state. Upon receiving the message, chain B writes a packet receipt and commits a hash of MsgAcknowledgement. Once the packet has been processed (error/success ack or timeout), and the packet commitment has been deleted from chain A, the packet receipt and acknowledgement stored on B are no longer necessary. These can accumulate over time and contribute to state bloat.
For instance, in an Osmosis state export last year, the IBC portion of state took 1.5GB out of a total export of 2.2GB, with acknowledgements and receipts accounting for 593MB and 417MB respectively.
As part of the design of channel upgradability, before a successful channel upgrade can occur, all pending in-flight packets are flushed, meaning an acknowledgement or timeout for these packets is processed before both ends of the channel can upgrade to the new parameters. Therefore, once a channel has successfully upgraded, all stale acknowledgements and packet receipts can be safely discarded. One can use the PruneAcknowledgements method for this purpose.
Interchain Accounts (ICA) could up until now only be used with ordered channels. This was a frequently mentioned pain point by IBC developers because a timeout occurring on an ordered channel would cause that channel to close. A new ICA channel then needed to be opened.
As of ibc-go v8.1, ICA can also be used with unordered channels. Chains on v8.1 or above can use channel upgradability to upgrade their existing ordered ICA channels to unordered ones, and new ICA channels can be opened as unordered from the start!
Connection hop is the ID of the connection between two chains along a channel path. Till now, the connectionHops field could only be fixed to one, indicating that all chains had a point-to-point connection between them.
The Multi-hop feature being developed by Polymer Labs will enable chains to define an IBC channel over a pre-existing connection. This for example would allow chains A and B to open a channel between them via an intermediate chain C, without a 1:1 connection between A and B.
With the release of multi-hop, existing channels can use channel upgradability to set connectionHops
to be greater than one. Channels can currently swap out existing connection hop to a different one using channel upgradability.
The channel upgrade process is a multi-step procedure similar to a standard IBC channel handshake, with a few additional subprotocols. The following steps outline the channel upgrade process:
ChanUpgradeInit
: The ChanUpgradeInit function is triggered by a governance proposal that submits MsgChannelUpgradeInit message on the initiating chain, referred to as chain A in this example.
This function performs some basic checks to see if the channel exists, whether the channel state is OPEN, and validates the UpgradeFields provided in the message against the existing channel parameters.
WriteUpgradeInitChannel is called after a successful ChanUpgradeInit
step. It sets the upgrade to the store and emits an event using which relayers construct MsgChannelUpgradeTry.
ChanUpgradeTry
: Triggered by MsgChannelUpgradeTry, this function is called by a module on the counterparty, referred to as chain B. It performs verification logic that includes:
Verifying that the initiating chain has stored the proposed upgrade in its upgrade path. See here for the verification logic.
Checks for compatibility between the UpgradeFields proposed on chain A (the initiating chain) and chain B (the counterparty).
Starts the flushing process to flush any in-flight packets. When one end of a channel is in FLUSHING
mode, packets can be received, timed out, or acknowledged but they cannot be sent from that channel end.
StartFlushing also sets a timeoutTimestamp within which the upgrade should occur. If chain A does not reach FLUSHCOMPLETE
within the counterparty specified timeout, which chain A is made aware of upon executing ChanUpgradeAck
in the next step, it will never move to FLUSHCOMPLETE
and will instead abort the upgrade. In this scenario, a relayer submits a proof of this to chain A using ChanUpgradeTimeout so that A cancels the upgrade and restores its original channel (more on timeouts below).
The WriteUpgradeTryChannel function is called after successfully passing the ChanUpgradeTry
handshake step. It assigns the upgrade fields to the counterparty upgrade version, sets the upgrade to store, and logs that the channel state has been updated.
ChanUpgradeAck
: This method acknowledges the upgrade attempt from the counterparty chain B. Among others, it performs the following checks:
Verifies a proof that a proposed upgrade has been stored in the counterparty upgrade path.
Starts the flushing process and moves the channel state from OPEN
to FLUSHING
. If all in-flight packets have already been flushed, the channel state can move to FLUSHCOMPLETE
. At this point, packets can only be received since all packets sent from this channel end have completed their lifecycle.
Checks for a timeout in the counterparty's upgrade and whether or not the timeout has elapsed.
ChanUpgradeConfirm
: This function is called when MsgChannelUpgradeConfirm is executed on chain B. It finalizes the upgrade process by confirming that both chains have agreed to the new Upgrade type and are ready to move the channel to the new upgraded parameters.
It checks for a timeout in chain A’s upgrade and whether or not the timeout has elapsed. If the channel has no more in-flight packets and the timeout has not passed, chain B can move its channel state from FLUSHING
to FLUSHCOMPLETE
or OPEN
. Note that it would only move to OPEN
if:
The channel end is in FLUSHCOMPLETE
. Moving from FLUSHING
to OPEN
is not allowed.
The counterparty is also in FLUSHCOMPLETE
.
ChanUpgradeOpen
: Triggered in response to executing MsgChannelUpgradeOpen on chain A, this step completes the upgrade process and moves the channel state back to OPEN
. It should only be called after both channels have flushed all in-flight packets.
Note that ChanUpgradeOpen
must also be called on chain B if it did not move to OPEN
after ChanUpgradeConfirm
ChanUpgradeCancel
and WriteUpgradeCancelChannel
: During the upgrade handshake, a chain may cancel the upgrade by writing an ErrorReceipt to its store, and subsequently fall back to the previous channel parameters. A relayer can then send a MsgChannelUpgradeCancel to the counterparty. After ensuring that a proof of a valid ErrorReceipt
exists in the counterparty store, it restores its channel to the original parameters.
ChanUpgradeTimeout
and WriteUpgradeTimeoutChannel
: Used to handle the timeout of a channel upgrade process. ChanUpgradeTimeout is called by a chain when the counterparty has not responded to an upgrade proposal within the specified timeout period.
The ChanUpgradeTimeout
method retrieves the timestamp of the block using the GetTimestampAtHeight method, fetches the channel upgrade timeout timestamp, and then checks if the proof's timestamp has passed the specified timestamp. If the proof is from a time before the timeout, it means that the timeout has not yet been reached, and the function returns an error.
Note: Timeout height is currently not allowed within channel upgradability as block times can vary between chains. The default timeout is set to 10 minutes. This means that a channel end in FLUSHING
must move to FLUSHCOMPLETE
within 10 minutes. If not, the handshake times out. The default timeout parameter can be changed by the authority
using UpdateChannelParams.
In summary, the channel upgrade process looks as follows:
Chain A initiates the upgrade process.
Chain B accepts the upgrade proposal and starts flushing all in-flight packets. This means that packets can be received or acknowledged by B but will not be sent from B.
Chain A also begins flushing any packets on its end.
Once steps 2. and 3. have been completed successfully, then both channel ends can be opened and process packets using the upgraded parameters.
Only the authority
of the IBCKeeper
can initiate a channel upgrade, meaning that only this authority
has access control to execute MsgChanUpgradeInit
.
A chain that wants to initiate upgrades can optionally assign a technical body/DAO responsible for making decisions on channel upgrades (using x/groups for instance). This entity can then upgrade a channel on behalf of the chain by passing a governance proposal with MsgChanUpgradeInit
message.
To this end, we’ve added a CLI that allows a chain to:
Upgrade multiple channels using a single proposal. See the example below that submits a governance proposal with multiple MsgChanUpgradeInit
messages for all OPEN
channels with the transfer
port ID.
simd tx ibc channel upgrade-channels "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" \
--deposit "10stake" \
--title "Channel Upgrades Governance Proposal" \
--summary "Upgrade all transfer channels to be fee enabled" \
--port-pattern "transfer"
Generate a proposal.json
file with proposal contents to be edited/submitted later. Here’s an example that generates the contents of a proposal.json
file that attempts to upgrade channels with a port ID of transfer
and a channel ID of channel-0
, channel-1
, or channel-2
.
simd tx ibc channel upgrade-channels "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" \
--deposit "10stake" \
--title "Channel Upgrades Governance Proposal" \
--summary "Upgrade all transfer channels to be fee enabled" \
--port-pattern "transfer" \
--channel-ids "channel-0,channel-1,channel-2" \
--json
Once ChanUpgradeInit
has been triggered on the initiating chain, any relayer can submit MsgChanUpgradeTry
to a counterparty. It is worth noting here that a relayer cannot force a counterparty chain to upgrade because the version negotiation would not succeed unless the counterparty has implemented the necessary application callbacks (OnChanUpgradeTry
, OnChanUpgradeOpen
).
Consider an example where chains A and B want to upgrade their channels to use fee middleware. Once both chains have integrated the middleware and are both on ibc-go v8.1 or above, then either A or B can initiate the channel upgrade via governance proposal. Once the proposal passes, a relayer can permissionlessly send MsgChanUpgradeTry
to the counterparty. A governance proposal does not need to be passed on the counterparty as well for this purpose.
Upgradability is a key element within any software product. It ensures that the product can leverage new features and improvements, thereby evolving according to user needs.
With the introduction of channel upgradability, existing channels can upgrade to leverage new application and channel features without having to open a new channel or coordinate a network-wide upgrade. Whether it’s integrating fee middleware, upgrading to unordered ICA channels, or adopting ICS-20 v2, the ability to seamlessly upgrade channels is a significant milestone in the development of IBC.
Adi Ravi Raj works at Interchain GmbH and is the protocol analyst for the IBC team.
Thank you to Susannah Evans, Carlos Rodriguez, and Mary McGilvray for the review.
The Inter-Blockchain Communication Protocol (IBC) is the most widely adopted trust-minimized, permissionless, and general messaging-passing protocol. IBC connects 100+ chains for cross-chain activities such as token transfers, inter-chain account management, shared security, data queries, and much more. Build using IBC or join us on Discord.