Synopsis
This standard document specifies the interfaces and state machine logic that IBC implementations must implement in order to enable existing channels to upgrade after the initial channel handshake.Motivation
As new features get added to IBC, chains may wish to take advantage of new channel features without abandoning the accumulated state and network effect(s) of an already existing channel. The upgrade protocol proposed would allow chains to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing packet state processed on the channel.Desired Properties
- Both chains MUST agree to the renegotiated channel parameters.
- Channel state and logic on both chains SHOULD either be using the old parameters or the new parameters, but MUST NOT be in an in-between state, e.g., it MUST NOT be possible for an application to run v2 logic, while its counterparty is still running v1 logic.
- The channel upgrade protocol is atomic, i.e.,
- either it is unsuccessful and then the channel MUST fall-back to the original channel parameters;
- or it is successful and then both channel ends MUST adopt the new channel parameters and the applications must process packet data appropriately.
- Packets sent under the previously negotiated parameters must be processed under the previously negotiated parameters, packets sent under the newly negotiated parameters must be processed under the newly negotiated parameters. Thus, in-flight packets sent before the upgrade handshake is complete will be processed according to the original parameters.
- The channel upgrade protocol MUST NOT modify the channel identifiers.
Technical Specification
Data Structures
TheChannelState and ChannelEnd are defined in ICS-4, they are reproduced here for the reader’s convenience. FLUSHING and FLUSHCOMPLETE are additional states added to enable the upgrade feature.
ChannelState
- In
ChanUpgradeInit, the initializing chain that is proposing the upgrade should store the channel upgrade. - The counterparty chain executing
ChanUpgradeTrythat accepts the upgrade should store the channel upgrade, set the channel state fromOPENtoFLUSHING, and start the flushing timer by storing an upgrade timeout. - Once the initiating chain verifies the counterparty is in
FLUSHING, it must also move toFLUSHINGunless all in-flight packets are already flushed on its end, in which case it must move directly toFLUSHCOMPLETE. The initiator will also store the counterparty timeout to ensure it does not move toFLUSHCOMPLETEafter the counterparty timeout has passed. - The counterparty chain must prove that the initiator is also in
FLUSHINGor completed flushing inFLUSHCOMPLETE. The counterparty will store the initiator timeout to ensure it does not move toFLUSHCOMPLETEafter the initiator timeout has passed.
FLUSHING is a “blocking” state that prevents a channel end from advancing to FLUSHCOMPLETE unless the in-flight packets on its channel end are flushed and both channel ends have already moved to FLUSHING. Once both sides have moved to FLUSHCOMPLETE, a relayer can prove this on both ends with ChanUpgradeOpen to open the channel on both sides with the new parameters.
ChannelEnd
state: The state is specified by the handshake steps of the upgrade protocol and will be mutated in place during the handshake. It will be inFLUSHINGmode when the channel end is flushing in-flight packets. The state will change toFLUSHCOMPLETEonce there are no in-flight packets left and the channelEnd is ready to move toOPEN.upgradeSequence: The upgrade sequence will be incremented and agreed upon during the upgrade handshake and will be mutated in place.
OPEN on a successful upgrade handshake, the fields on the channel end will be switched over to the UpgradeFields specified in the Upgrade.
UpgradeFields
version: The version MAY be modified by the upgrade protocol. The same version negotiation that happens in the initial channel handshake can be employed for the upgrade handshake.ordering: The ordering MAY be modified by the upgrade protocol so long as the new ordering is supported by underlying connection.connectionHops: The connectionHops MAY be modified by the upgrade protocol.
counterpartyChannelIdentifier: The counterparty channel identifier MUST NOT be modified by the upgrade protocol.counterpartyPortIdentifier: The counterparty port identifier MUST NOT be modified by the upgrade protocol
ChannelEnd these are by default modifiable, and can be arbitrarily chosen by an Actor (e.g. chain governance) which has permission to initiate the upgrade.
Timeout
timeoutHeight: Timeout height indicates the height at which the counterparty must no longer proceed with the upgrade handshake. The chains will then preserve their original channel and the upgrade handshake is aborted.timeoutTimestamp: Timeout timestamp indicates the time on the counterparty at which the counterparty must no longer proceed with the upgrade handshake. The chains will then preserve their original channel and the upgrade handshake is aborted.
timeoutHeight or timeoutTimestamp MUST be non-zero.
Upgrade
The upgrade type will represent a particular upgrade attempt on a channel end.
nextSequenceSend allows the counterparty to know which packets need to be flushed before the channel can reopen with the newly negotiated parameters. Any packet sent to the channel end with a packet sequence greater than or equal to the nextSequenceSend will be rejected until the upgrade is complete. The nextSequenceSend will also be used to set the new sequences for the counterparty when it opens for a new upgrade.
ErrorReceipt
sequencecontains theupgradeSequenceat which the error occurred.errorMsgcontains an arbitrary string which chains may use to provide additional information as to why the upgrade was aborted.
Store Paths
Channel Upgrade Path
The chain must store the proposed upgrade upon initiating an upgrade. The proposed upgrade must be stored in the provable store. It may be deleted once the upgrade is successful or has been aborted.CounterpartyUpgrade Path
The chain must store the counterparty upgrade onchanUpgradeAck and chanUpgradeConfirm. This will be stored in the counterpartyUpgrade path on the private store.
Upgrade Error Path
The upgrade error path is a public path that can signal an error of the upgrade to the counterparty for the given upgrade attempt. It does not store anything in the successful case, but it will store theErrorReceipt in the case that a chain does not accept the proposed upgrade.
Sub-Protocols
The channel upgrade process consists of the following sub-protocols:initUpgradeHandshake, startFlushUpgradeHandshake, openUpgradeHandshake, cancelChannelUpgrade, and timeoutChannelUpgrade. In the case where both chains approve of the proposed upgrade, the upgrade handshake protocol should complete successfully and the ChannelEnd should upgrade to the new parameters in OPEN state.
Utility Functions
initUpgradeHandshake is a sub-protocol that will initialize the channel end for the upgrade handshake. It will validate the upgrade parameters and store the channel upgrade. All packet processing will continue according to the original channel parameters, as this is a signalling mechanism that can remain indefinitely. The new proposed upgrade will be stored in the provable store for counterparty verification. If it is called again before the handshake starts, then the current proposed upgrade will be replaced with the new one and the channel upgrade sequence will be incremented.
isCompatibleUpgradeFields will return true if two upgrade field structs are mutually compatible as counterparties, and false otherwise. The first field must be the upgrade fields on the executing chain, the second field must be the counterparty upgrade fields. This function will also check that the proposed connection hops exists, is OPEN, and is mutually compatible with the counterparty connection hops.
startFlushUpgradeHandshake will block the upgrade from continuing until all in-flight packets have been flushed. It will set the channel state to FLUSHING and block sendPacket. During this time; receivePacket, acknowledgePacket and timeoutPacket will still be allowed and processed according to the original channel parameters. The state machine will set a timer for how long the other side can take before it completes flushing and moves to FLUSHCOMPLETE. The new proposed upgrade will be stored in the public store for counterparty verification.
openUpgradeHandshake will open the channel and switch the existing channel parameters to the newly agreed-upon upgraded channel fields.
restoreChannel will write an ErrorReceipt, set the channel back to its original state and delete upgrade information when the executing channel needs to abort the upgrade handshake and return to the original parameters.
pendingInflightPackets will return the list of in-flight packet sequences sent from this ChannelEnd. This can be monitored since the packet commitments are deleted when the packet lifecycle is complete. Thus if the packet commitment exists on the sender chain, the packet lifecycle is incomplete. The pseudocode is not provided in this spec since it will be dependent on the state machine in-question. The ibc-go implementation will use the store iterator to implement this functionality. The function signature is provided below:
isAuthorizedUpgrader will return true if the provided address is authorized to initialize, modify, and cancel upgrades. Chains may permission a set of addresses that can signal which upgrade a channel is willing to upgrade to.
getUpgradeTimeout will return the upgrade timeout specified for the given channel. This may be a chain-wide parameter, or it can be a parameter chosen per channel. This is an implementation-level detail, so only the function signature is specified here. Note this should retrieve some stored timeout delta for the channel and add it to the current height and time to get the absolute timeout values.
Upgrade Handshake
The upgrade handshake defines seven datagrams: ChanUpgradeInit, ChanUpgradeTry, ChanUpgradeAck, ChanUpgradeConfirm, ChanUpgradeOpen, ChanUpgradeTimeout, and ChanUpgradeCancel A successful protocol execution flows as follows (note that all calls are made through modules per ICS 25):| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
|---|---|---|---|---|
| Actor | ChanUpgradeInit | A | (OPEN, OPEN) | (OPEN, OPEN) |
| Relayer | ChanUpgradeTry | B | (OPEN, OPEN) | (OPEN, FLUSHING) |
| Relayer | ChanUpgradeAck | A | (OPEN, FLUSHING) | (FLUSHING/FLUSHCOMPLETE, FLUSHING) |
| Relayer | ChanUpgradeConfirm | B | (FLUSHING/FLUSHCOMPLETE, FLUSHING) | (FLUSHING/FLUSHCOMPLETE, FLUSHING/FLUSHCOMPLETE/OPEN) |
OPEN. Authorized upgraders are at risk of having the channel halt during the upgrade process if the prior state before channel upgrades on one of the ends is not OPEN.
Refer to the diagram below for a possible channel upgrade flow. Multiple channel states are shown on steps 5 and 7 where the channel end can move to either one of those possible states upon executing the handshake. Note that in this example, the channel end on chain B moves to OPEN with the new parameters on ChanUpgradeConfirm (step 7).

FLUSHING and both sides have stored each others upgrade timeouts, both sides can move to FLUSHCOMPLETE by clearing their in-flight packets. Once both sides have complete flushing, a relayer may submit a ChanUpgradeOpen datagram to both ends proving that the counterparty has also completed flushing in order to move the channelEnd to OPEN.
ChanUpgradeOpen is only necessary to call on chain B if the chain was not moved to OPEN on ChanUpgradeConfirm which may happen if all packets on both ends are already flushed.
At the end of a successful upgrade handshake between two chains implementing the sub-protocol, the following properties hold:
- Each chain is running their new upgraded channel end and is processing upgraded logic and state according to the upgraded parameters.
- Each chain has knowledge of and has agreed to the counterparty’s upgraded channel parameters.
- All packets sent before the handshake have been completely flushed (acked or timed out) with the old parameters.
- All packets sent after a channel end moves to OPEN will either timeout using new parameters on sending channelEnd or will be received by the counterparty using new parameters.
ChannelEnd, it may abort the upgrade handshake by writing an ErrorReceipt into the channelUpgradeErrorPath and restoring the original channel. The ErrorReceipt must contain the current upgrade sequence on the erroring chain’s channel end.
channelUpgradeErrorPath(portID, channelID) => ErrorReceipt(sequence, msg)
A relayer may then submit a ChanUpgradeCancel datagram to the counterparty. Upon receiving this message a chain must verify that the counterparty wrote an ErrorReceipt into its channelUpgradeErrorPath with a sequence greater than or equal to its own ChannelEnd’s upgrade sequence. If successful, it will restore its original channel as well, thus cancelling the upgrade.
If a chain does not reach FLUSHCOMPLETE within the counterparty specified timeout, then it MUST NOT move to FLUSHCOMPLETE and should instead abort the upgrade. A relayer may submit a proof of this to the counterparty chain in a ChanUpgradeTimeout datagram so that counterparty cancels the upgrade and restores its original channel as well.
chanUpgradeInit function. E.g. chain governance, permissioned actor, DAO, etc.
ChanUpgradeInit with mutually compatible parameters in order for ChanUpgradeTry to succeed. Implementations that want to be permissive towards counterparty-initiated upgrades may allow moving from OPEN to FLUSHING without having an upgrade previously stored on the executing chain.
chanUpgradeConfirm is called on the chain which is on FLUSHING after chanUpgradeAck is called on the counterparty. This will inform the TRY chain of the timeout set on ACK by the counterparty. If the timeout has already exceeded, we will write an error receipt and restore. If packets on both sides have already been flushed and timeout is not exceeded, then we can open the channel. Otherwise, we set the counterparty timeout in the private store and wait for packet flushing to complete.
chanUpgradeOpen may only be called once both sides have moved to FLUSHCOMPLETE. If there exists unprocessed packets in the queue when the handshake goes into FLUSHING mode, then the packet handlers must move the channel end to FLUSHCOMPLETE once the last packet on the channel end has been processed.
Cancel Upgrade Process
During the upgrade handshake a chain may cancel the upgrade by writing an error receipt into the upgrade error path and restoring the original channel toOPEN. The counterparty must then restore its channel to OPEN as well. A relayer can facilitate this by sending ChanUpgradeCancel datagram to the handler:
Timeout Upgrade Process
It is possible for the channel upgrade process to stall indefinitely while trying to flush the existing packets. To protect against this, each chain sets a timeout when it moves intoFLUSHING. If the counterparty has not completed flushing within the expected time window, then the relayer can submit a timeout message to restore the channel to OPEN with the original parameters. It will also write an error receipt so that the counterparty which has not moved to FLUSHCOMPLETE can also restore channel to OPEN with the original parameters.
FLUSHCOMPLETE if the counterparty upgrade timeout has already passed. This will prevent the channel ends from reaching incompatible states.
Considerations
Note that a channel upgrade handshake may never complete successfully if the in-flight packets cannot successfully be cleared. This can happen if the timeout value of a packet is too large, or an acknowledgement never arrives, or if there is a bug that makes acknowledging or timing out a packet impossible. In these cases, some out-of-protocol mechanism (e.g. governance) must step in to clear the packets “manually” perhaps by forcefully clearing the packet commitments before restarting the upgrade handshake.Migrations
A chain may have to update its internal state to be consistent with the new upgraded channel. In this case, a migration handler should be a part of the chain binary before the upgrade process so that the chain can properly migrate its state once the upgrade is successful. If a migration handler is necessary for a given upgrade but is not available, then the executing chain must reject the upgrade so as not to enter into an invalid state. This state migration will not be verified by the counterparty since it will just assume that if the channel is upgraded to a particular channel version, then the auxiliary state on the counterparty will also be updated to match the specification for the given channel version. The migration must only run once the upgrade has successfully completed and the new channel isOPEN (ie. on ChanUpgradeConfirm or ChanUpgradeOpen).
Example Implementations
- Implementation of channel upgrade in Go can be found in ibc-go repository.
History
Feb 1, 2024 - Spec as implemented in ibc-go Jul 24, 2024 - Add upgrade compatibility check inchanUpgradeConfirm