From d5408ab77ef91897c1855015dc496067bc9b58b0 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 7 Mar 2022 16:52:52 +0100 Subject: [PATCH 01/12] copy over connection upgrade spec and replace connection with channel --- .../UPGRADES.md | 488 ++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md new file mode 100644 index 000000000..50d08fee8 --- /dev/null +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -0,0 +1,488 @@ +# Upgrading Channels + +### 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 the 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 channels that built on top of 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 a chain to write state to an old proof path, while the counterparty expects a new proof path. +- 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 process IBC data appropriately. +- The channel upgrade protocol should have the ability to change all channel-related parameters; however the channel upgrade protocol MUST NOT be able to change the underlying `ClientState`. +The channel upgrade protocol MUST NOT modify the channel identifiers. + +## Technical Specification + +### Data Structures + +The `ChannelState` and `ChannelEnd` are defined in [ICS-3](./README.md), they are reproduced here for the reader's convenience. `UPGRADE_INIT`, `UPGRADE_TRY`, `UPGRADE_ERR` are additional states added to enable the upgrade feature. + +```typescript +enum ChannelState { + INIT, + TRYOPEN, + OPEN, + UPGRADE_INIT, + UPGRADE_TRY, + UPGRADE_ERR, +} +``` + +- The chain that is proposing the upgrade should set the channel state from `OPEN` to `UPGRADE_INIT` +- The counterparty chain that accepts the upgrade should set the channel state from `OPEN` to `UPGRADE_TRY` + +```typescript +interface ChannelEnd { + state: ChannelState + counterpartyChannelIdentifier: Identifier + counterpartyPrefix: CommitmentPrefix + clientIdentifier: Identifier + counterpartyClientIdentifier: Identifier + version: string | []string + delayPeriodTime: uint64 + delayPeriodBlocks: uint64 +} +``` + +The desired property that the channel upgrade protocol MUST NOT modify the underlying clients or channel identifiers, means that only some fields of `ChannelEnd` are upgradable by the upgrade protocol. + +- `state`: The state is specified by the handshake steps of the upgrade protocol. + +MAY BE MODIFIED: +- `counterpartyPrefix`: The prefix MAY be modified in the upgrade protocol. The counterparty must accept the new proposed prefix value, or it must return an error during the upgrade handshake. +- `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. +- `delayPeriodTime`: The delay period MAY be modified by the upgrade protocol. The counterparty MUST accept the new proposed value or return an error during the upgrade handshake. +- `delayPeriodBlocks`: The delay period MAY be modified by the upgrade protocol. The counterparty MUST accept the new proposed value or return an error during the upgrade handshake. + +MUST NOT BE MODIFIED: +- `counterpartyChannelIdentifier`: The counterparty channel identifier CAN NOT be modified by the upgrade protocol. +- `clientIdentifier`: The client identifier CAN NOT be modified by the upgrade protocol +- `counterpartyClientIdentifier`: The counterparty client identifier CAN NOT be modified by the upgrade protocol + +NOTE: If the upgrade adds any fields to the `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. + +```typescript +interface UpgradeTimeout { + timeoutHeight: Height + timeoutTimestamp: uint64 +} +``` + +- `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. + +At least one of the timeoutHeight or timeoutTimestamp MUST be non-zero. + +### Store Paths + +#### Restore Channel Path + +The chain must store the previous channel end so that it may restore it if the upgrade handshake fails. This may be stored in the private store. + +```typescript +function restorePath(id: Identifier): Path { + return "channels/{id}/restore" +} +``` + +#### UpgradeError Path + +The upgrade error path is a public path that can signal an error of the upgrade to the counterparty. It does not store anything in the successful case, but it will store a sentinel abort value in the case that a chain does not accept the proposed upgrade. + +```typescript +function errorPath(id: Identifier): Path { + return "channels/{id}/upgradeError" + +} +``` + +The UpgradeError MUST have an associated verification function added to the channel and client interfaces so that a counterparty may verify that chain has stored an error in the UpgradeError path. + +```typescript +// Channel VerifyUpgradeError method +function verifyUpgradeError( + channel: ChannelEnd, + height: Height, + proof: CommitmentProof, + upgradeErrorReceipt: []byte, +) { + client = queryClient(channel.clientIdentifier) + client.verifyUpgradeError(height, channel.counterpartyPrefix, proof, channel.counterpartyChannelIdentifier, upgradeErrorReceipt) +} +``` + +```typescript +// Client VerifyUpgradeError +function verifyUpgradeError( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + counterpartyChannelIdentifier: Identifier, + upgradeErrorReceipt []byte, +) { + path = applyPrefix(prefix, errorPath(counterpartyChannelIdentifier)) + abortTransactionUnless(!clientState.frozen) + return clientState.verifiedRoots[height].verifyMembership(path, upgradeErrorReceipt, proof) +} +``` + +#### TimeoutPath + +The timeout path is a public path set by the upgrade initiator to determine when the TRY step should timeout. It stores the `timeoutHeight` and `timeoutTimestamp` by which point the counterparty must have progressed to the TRY step. This path will be proven on the counterparty chain in case of a successful TRY, to ensure timeout has not passed. Or in the case of a timeout, in which case counterparty proves that the timeout has passed on its chain and restores the channel. + +```typescript +function timeoutPath(id: Identifier) Path { + return "channels/{id}/upgradeTimeout" +} +``` + +The timeout path MUST have associated verification methods on the channel and client interfaces in order for a counterparty to prove that a chain stored a particular `UpgradeTimeout`. + +```typescript +// Channel VerifyUpgradeTimeout method +function verifyUpgradeTimeout( + channel: ChannelEnd, + height: Height, + proof: CommitmentProof, + upgradeTimeout: UpgradeTimeout, +) { + client = queryClient(channel.clientIdentifier) + client.verifyUpgradeTimeout(height, channel.counterpartyPrefix, proof, channel.counterpartyChannelIdentifier, upgradeTimeout) +} +``` + +```typescript +// Client VerifyUpgradeTimeout +function verifyUpgradeTimeout( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + counterpartyChannelIdentifier: Identifier, + upgradeTimeout: UpgradeTimeout, +) { + path = applyPrefix(prefix, timeoutPath(counterpartyChannelIdentifier)) + abortTransactionUnless(!clientState.frozen) + timeoutBytes = protobuf.marshal(upgradeTimeout) + return clientState.verifiedRoots[height].verifyMembership(path, timeoutBytes, proof) +} +``` + +## Sub-Protocols + +The Channel Upgrade process consists of three sub-protocols: `UpgradeHandshake`, `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 successf + +### Upgrade Handshake + +The upgrade handshake defines four datagrams: *ChanUpgradeInit*, *ChanUpgradeTry*, *ChanUpgradeAck*, and *ChanUpgradeConfirm* + +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) | (UPGRADE_INIT, OPEN) | +| Actor | `ChanUpgradeTry` | B | (UPGRADE_INIT, OPEN) | (UPGRADE_INIT, UPGRADE_TRY) | +| Relayer | `ChanUpgradeAck` | A | (UPGRADE_INIT, UPGRADE_TRY) | (OPEN, UPGRADE_TRY) | +| Relayer | `ChanUpgradeConfirm` | B | (OPEN, UPGRADE_TRY) | (OPEN, OPEN) | + +At the end of an opening 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. + +If a chain does not agree to the proposed counterparty `UpgradedChannel`, it may abort the upgrade handshake by writing an error receipt into the `errorPath` and restoring the original channel. The error receipt MAY be arbitrary bytes and MUST be non-empty. + +`errorPath(id) => error_receipt` + +A relayer may then submit a `CancelChannelUpgradeMsg` to the counterparty. Upon receiving this message a chain must verify that the counterparty wrote a non-empty error receipt into its `UpgradeError` and if successful, it will restore its original channel as well thus cancelling the upgrade. + +If an upgrade message arrives after the specified timeout, then the message MUST NOT execute successfully. Again a relayer may submit a proof of this in a `CancelChannelUpgradeTimeoutMsg` so that counterparty cancels the upgrade and restores it original channel as well. + +```typescript +function connUpgradeInit( + identifier: Identifier, + proposedUpgradeChannel: ChannelEnd, + counterpartyTimeoutHeight: Height, + counterpartyTimeoutTimestamp: uint64, +) { + // current channel must be OPEN + currentChannel = provableStore.get(channelPath(identifier)) + abortTransactionUnless(channel.state == OPEN) + + // abort transaction if an unmodifiable field is modified + // upgraded channel state must be in `UPGRADE_INIT` + // NOTE: Any added fields are by default modifiable. + abortTransactionUnless( + proposedUpgradeChannel.state == UPGRADE_INIT && + proposedUpgradeChannel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier && + proposedUpgradeChannel.clientIdentifier == currentChannel.clientIdentifier && + proposedUpgradeChannel.counterpartyClientIdentifier == currentChannel.counterpartyClientIdentifier + ) + + // either timeout height or timestamp must be non-zero + abortTransactionUnless(counterpartyTimeoutHeight != 0 || counterpartyTimeoutTimestamp != 0) + + upgradeTimeout = UpgradeTimeout{ + timeoutHeight: counterpartyTimeoutHeight, + timeoutTimestamp: counterpartyTimeoutTimestamp, + } + + provableStore.set(timeoutPath(identifier), upgradeTimeout) + provableStore.set(channelPath(identifier), proposedUpgrade.channel) + privateStore.set(restorePath(identifier), currentChannel) +} +``` + +NOTE: It is up to individual implementations how they will provide access-control to the `ChanUpgradeInit` function. E.g. chain governance, permissioned actor, DAO, etc. +Access control on counterparty should inform choice of timeout values, i.e. timeout value should be large if counterparty's `UpgradeTry` is gated by chain governance. + +```typescript +function connUpgradeTry( + identifier: Identifier, + proposedUpgrade: UpgradeChannelState, + counterpartyChannel: ChannelEnd, + timeoutHeight: Height, + timeoutTimestamp: uint64, + UpgradeTimeout: UpgradeTimeout, + proofChannel: CommitmentProof, + proofUpgradeTimeout: CommitmentProof, + proofHeight: Height +) { + // current channel must be OPEN or UPGRADE_INIT (crossing hellos) + currentChannel = provableStore.get(channelPath(identifier)) + abortTransactionUnless(currentChannel.state == OPEN || currentChannel.state == UPGRADE_INIT) + + if currentChannel.state == UPGRADE_INIT { + // if there is a crossing hello, ie an UpgradeInit has been called on both channelEnds, + // then we must ensure that the proposedUpgrade by the counterparty is the same as the currentChannel + // except for the channel state (upgrade channel will be in UPGRADE_TRY and current channel will be in UPGRADE_INIT) + // if the proposed upgrades on either side are incompatible, then we will restore the channel and cancel the upgrade. + currentChannel.state = UPGRADE_TRY + restoreChannelUnless(currentChannel.IsEqual(proposedUpgrade.channel)) + } else { + // this is first message in upgrade handshake on this chain so we must store original channel in restore path + // in case we need to restore channel later. + privateStore.set(restorePath(identifier), currentChannel) + } + + // abort transaction if an unmodifiable field is modified + // upgraded channel state must be in `UPGRADE_TRY` + // NOTE: Any added fields are by default modifiable. + abortTransactionUnless( + proposedUpgrade.channel.state == UPGRADE_TRY && + proposedUpgrade.channel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier && + proposedUpgrade.channel.clientIdentifier == currentChannel.clientIdentifier && + proposedUpgrade.channel.counterpartyClientIdentifier == currentChannel.counterpartyClientIdentifier + ) + + + // either timeout height or timestamp must be non-zero + // if the upgrade feature is implemented on the TRY chain, then a relayer may submit a TRY transaction after the timeout. + // this will restore the channel on the executing chain and allow counterparty to use the CancelUpgradeMsg to restore their channel. + restoreChannelUnless(timeoutHeight != 0 || timeoutTimestamp != 0) + upgradeTimeout = UpgradeTimeout{ + timeoutHeight: timeoutHeight, + timeoutTimestamp: timeoutTimestamp, + } + + // verify that counterparty channel unmodifiable fields have not changed and counterparty state + // is UPGRADE_INIT + restoreChannelUnless( + counterpartyChannel.state == UPGRADE_INIT && + counterpartyChannel.counterpartyChannelIdentifier == identifier && + counterpartyChannel.clientIdentifier == currentChannel.counterpartyClientIdentifier && + counterpartyChannel.counterpartyClientIdentifier == currentChannel.clientIdentifier + ) + + // counterparty-specified timeout must not have exceeded + restoreChannelUnless( + timeoutHeight < currentHeight() && + timeoutTimestamp < currentTimestamp() + ) + + // verify chosen versions are compatible + versionsIntersection = intersection(counterpartyChannel.version, proposedUpgrade.Channel.version) + version = pickVersion(versionsIntersection) // aborts transaction if there is no intersection + + // both channel ends must be mutually compatible. + // this function has been left unspecified since it will depend on the specific structure of the new channel. + // It is the responsibility of implementations to make sure that verification that the proposed new channels + // on either side are correctly constructed according to the new version selected. + restoreChannelUnless(IsCompatible(counterpartyChannel, proposedUpgrade.Channel)) + + // verify proofs of counterparty state + abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) + + provableStore.set(channelPath(identifier), proposedUpgrade.channel) +} +``` + +NOTE: It is up to individual implementations how they will provide access-control to the `ChanUpgradeTry` function. E.g. chain governance, permissioned actor, DAO, etc. A chain may decide to have permissioned **or** permissionless `UpgradeTry`. In the permissioned case, both chains must explicitly consent to the upgrade, in the permissionless case; one chain initiates the upgrade and the other chain agrees to the upgrade by default. In the permissionless case, a relayer may submit the `ChanUpgradeTry` datagram. + + +```typescript +function onChanUpgradeAck( + identifier: Identifier, + counterpartyChannel: ChannelEnd, + counterpartyStatus: UpgradeError, + proofChannel: CommitmentProof, + proofUpgradeError: CommitmentProof, + proofHeight: Height +) { + // current channel is in UPGRADE_INIT or UPGRADE_TRY (crossing hellos) + currentChannel = provableStore.get(channelPath(identifier)) + abortTransactionUnless(currentChannel.state == UPGRADE_INIT || currentChannel.state == UPGRADE_TRY) + + // counterparty must be in TRY state + restoreChannelUnless(counterpartyChannel.State == UPGRADE_TRY) + + // verify channels are mutually compatible + // this will also check counterparty chosen version is valid + // this function has been left unspecified since it will depend on the specific structure of the new channel. + // It is the responsibility of implementations to make sure that verification that the proposed new channels + // on either side are correctly constructed according to the new version selected. + restoreChannelUnless(IsCompatible(counterpartyChannel, channel)) + + // verify proofs of counterparty state + abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + + // upgrade is complete + // set channel to OPEN and remove unnecessary state + currentChannel.state = OPEN + provableStore.set(channelPath(identifier), currentChannel) + provableStore.delete(timeoutPath(identifier)) + privateStore.delete(restorePath(identifier)) +} +``` + +```typescript +function onChanUpgradeConfirm( + identifier: Identifier, + counterpartyChannel: ChannelEnd, + proofChannel: CommitmentProof, + proofHeight: Height, +) { + // current channel is in UPGRADE_TRY + currentChannel = provableStore.get(channelPath(identifier)) + abortTransactionUnless(channel.state == UPGRADE_TRY) + + // counterparty must be in OPEN state + abortTransactionUnless(counterpartyChannel.State == OPEN) + + // verify proofs of counterparty state + abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + + // upgrade is complete + // set channel to OPEN and remove unnecessary state + currentChannel.state = OPEN + provableStore.set(channelPath(identifier), currentChannel) + provableStore.delete(timeoutPath(identifier)) + privateStore.delete(restorePath(identifier)) +} +``` + +```typescript +function restoreChannelUnless(condition: bool) { + if !condition { + // cancel upgrade + // write an error receipt into the error path + // and restore original channel + errorReceipt = []byte{1} + provableStore.set(errorPath(identifier), errorReceipt) + originalChannel = privateStore.get(restorePath(identifier)) + provableStore.set(channelPath(identifier), originalChannel) + provableStore.delete(timeoutPath(identifier)) + privateStore.delete(restorePath(identifier)) + // caller should return as well + } else { + // caller should continue execution + } +} +``` + +### Cancel Upgrade Process + +During the upgrade handshake a chain may cancel the upgrade by writing an error receipt into the error path and restoring the original channel to `OPEN`. The counterparty must then restore its channel to `OPEN` as well. A relayer can facilitate this by calling `CancelChannelUpgrade`: + +```typescript +function cancelChannelUpgrade( + identifier: Identifer, + errorReceipt: []byte, + counterpartyUpgradeError: UpgradeError, + proofUpgradeError: CommitmentProof, + proofHeight: Height, +) { + // current channel is in UPGRADE_INIT or UPGRADE_TRY + currentChannel = provableStore.get(channelPath(identifier)) + abortTransactionUnless(channel.state == UPGRADE_INIT || channel.state == UPGRADE_TRY) + + abortTransactionUnless(!isEmpty(errorReceipt)) + + abortTransactionUnless(verifyUpgradeError(currentChannel, proofHeight, proofUpgradeError, currentChannel.counterpartyChannelIdentifier, counterpartyUpgradeError)) + + // cancel upgrade + // and restore original conneciton + // delete unnecessary state + originalChannel = privateStore.get(restorePath(identifier)) + provableStore.set(channelPath(identifier), originalChannel) + + // delete auxilliary upgrade state + provableStore.delete(timeoutPath(identifier)) + privateStore.delete(restorePath(identifier)) +} +``` + +### Timeout Upgrade Process + +It is possible for the channel upgrade process to stall indefinitely on UPGRADE_TRY if the UPGRADE_TRY transaction simply cannot pass on the counterparty; for example, the upgrade feature may not be enabled on the counterparty chain. + +In this case, we do not want the initializing chain to be stuck indefinitely in the `UPGRADE_INIT` step. Thus, the `UpgradeInit` message will contain a `TimeoutHeight` and `TimeoutTimestamp`. The counterparty chain is expected to reject `UpgradeTry` message if the specified timeout has already elapsed. + +A relayer must then submit an `UpgradeTimeout` message to the initializing chain which proves that the counterparty is still in its original state. If the proof succeeds, then the initializing chain shall also restore its original channel and cancel the upgrade. + +```typescript +function timeoutChannelUpgrade( + identifier: Identifier, + counterpartyChannel: ChannelEnd, + proofChannel: CommitmentProof, + proofHeight: Height, +) { + // current channel must be in UPGRADE_INIT + currentChannel = provableStore.get(channelPath(identifier)) + abortTransactionUnles(currentChannel.state == UPGRADE_INIT) + + upgradeTimeout = provableStore.get(timeoutPath(identifier)) + + // proof must be from a height after timeout has elapsed. Either timeoutHeight or timeoutTimestamp must be defined. + // if timeoutHeight is defined and proof is from before timeout height + // then abort transaction + abortTransactionUnless(upgradeTimeout.timeoutHeight.IsZero() || proofHeight >= upgradeTimeout.timeoutHeight) + // if timeoutTimestamp is defined then the consensus time from proof height must be greater than timeout timestamp + consensusState = queryConsensusState(currentChannel.clientIdentifer, proofHeight) + abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || consensusState.getTimestamp() >= upgradeTimeout.timestamp) + + // counterparty channel must be proved to still be in OPEN state + abortTransactionUnless(counterpartyChannel.State === OPEN) + abortTransactionUnless(channel.client.verifyChannelState(proofHeight, proofChannel, counterpartyChannel)) + + // we must restore the channel since the timeout verification has passed + restoreChannelUnless(false) +} +``` + +Note that the timeout logic only applies to the INIT step. This is to protect an upgrading chain from being stuck in a non-OPEN state if the counterparty cannot execute the TRY successfully. Once the TRY step succeeds, then both sides are guaranteed to have the upgrade feature enabled. Liveness is no longer an issue, because we can wait until liveness is restored to execute the ACK step which will move the channel definitely into an OPEN state (either a successful upgrade or a rollback). + +The TRY chain will receive the timeout parameters chosen by the counterparty on INIT, so that it can reject any TRY message that is received after the specified timeout. This prevents the handshake from entering into an invalid state, in which the INIT chain processes a timeout successfully and restores its channel to `OPEN` while the TRY chain at a later point successfully writes a `TRY` state. + +### 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 th 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 auxilliary 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 is `OPEN` (ie. on `ACK` and `CONFIRM`). \ No newline at end of file From c76e1f3e66fa8c549bb91ac42fbf23f643961774 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 7 Mar 2022 18:25:20 +0100 Subject: [PATCH 02/12] do core changes, TODO: app changes and callbacks --- .../UPGRADES.md | 197 +++++++++--------- 1 file changed, 99 insertions(+), 98 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 50d08fee8..268bcaf5b 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -11,10 +11,10 @@ As new features get added to IBC, chains may wish the take advantage of new chan ### 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 a chain to write state to an old proof path, while the counterparty expects a new proof path. +- 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 process IBC data appropriately. + - or it is successful and then both channel ends MUST adopt the new channel parameters and the applications must process packet data appropriately. - The channel upgrade protocol should have the ability to change all channel-related parameters; however the channel upgrade protocol MUST NOT be able to change the underlying `ClientState`. The channel upgrade protocol MUST NOT modify the channel identifiers. @@ -22,7 +22,7 @@ The channel upgrade protocol MUST NOT modify the channel identifiers. ### Data Structures -The `ChannelState` and `ChannelEnd` are defined in [ICS-3](./README.md), they are reproduced here for the reader's convenience. `UPGRADE_INIT`, `UPGRADE_TRY`, `UPGRADE_ERR` are additional states added to enable the upgrade feature. +The `ChannelState` and `ChannelEnd` are defined in [ICS-4](./README.md), they are reproduced here for the reader's convenience. `UPGRADE_INIT`, `UPGRADE_TRY`, `UPGRADE_ERR` are additional states added to enable the upgrade feature. ```typescript enum ChannelState { @@ -31,7 +31,6 @@ enum ChannelState { OPEN, UPGRADE_INIT, UPGRADE_TRY, - UPGRADE_ERR, } ``` @@ -41,13 +40,11 @@ enum ChannelState { ```typescript interface ChannelEnd { state: ChannelState + ordering: ChannelOrder + counterpartyPortIdentifier: Identifier counterpartyChannelIdentifier: Identifier - counterpartyPrefix: CommitmentPrefix - clientIdentifier: Identifier - counterpartyClientIdentifier: Identifier - version: string | []string - delayPeriodTime: uint64 - delayPeriodBlocks: uint64 + connectionHops: [Identifier] + version: string } ``` @@ -56,15 +53,13 @@ The desired property that the channel upgrade protocol MUST NOT modify the under - `state`: The state is specified by the handshake steps of the upgrade protocol. MAY BE MODIFIED: -- `counterpartyPrefix`: The prefix MAY be modified in the upgrade protocol. The counterparty must accept the new proposed prefix value, or it must return an error during the upgrade handshake. - `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. -- `delayPeriodTime`: The delay period MAY be modified by the upgrade protocol. The counterparty MUST accept the new proposed value or return an error during the upgrade handshake. -- `delayPeriodBlocks`: The delay period MAY be modified by the upgrade protocol. The counterparty MUST accept the new proposed value or return an error during the upgrade handshake. +- `ordering`: The ordering MAY be modified by the upgrade protocol. However, it MUST be the case that the previous ordering is a valid subset of the new ordering. Thus, the only supported change is from stricter ordering rules to less strict ordering. Ex: Switching from ORDERED to UNORDERED is supported, switching from UNORDERED to ORDERED is **unsupported**. +- `connectionHops`: The connectionHops MAY be modified by the upgrade protocol. MUST NOT BE MODIFIED: -- `counterpartyChannelIdentifier`: The counterparty channel identifier CAN NOT be modified by the upgrade protocol. -- `clientIdentifier`: The client identifier CAN NOT be modified by the upgrade protocol -- `counterpartyClientIdentifier`: The counterparty client identifier CAN NOT be modified by the upgrade protocol +- `counterpartyChannelIdentifier`: The counterparty channel identifier MAY NOT be modified by the upgrade protocol. +- `counterpartyPortIdentifier`: The counterparty port identifier MAY NOT be modified by the upgrade protocol NOTE: If the upgrade adds any fields to the `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. @@ -87,8 +82,8 @@ At least one of the timeoutHeight or timeoutTimestamp MUST be non-zero. The chain must store the previous channel end so that it may restore it if the upgrade handshake fails. This may be stored in the private store. ```typescript -function restorePath(id: Identifier): Path { - return "channels/{id}/restore" +function restorePath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { + return "channelUpgrade/ports/{portIdentifier}/channels/{channelIdentifier}/restore" } ``` @@ -97,8 +92,8 @@ function restorePath(id: Identifier): Path { The upgrade error path is a public path that can signal an error of the upgrade to the counterparty. It does not store anything in the successful case, but it will store a sentinel abort value in the case that a chain does not accept the proposed upgrade. ```typescript -function errorPath(id: Identifier): Path { - return "channels/{id}/upgradeError" +function errorPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path { + return "channelUpgrade/ports/{portIdentifier}/channels/{channelIdentifier}/upgradeError" } ``` @@ -106,15 +101,17 @@ function errorPath(id: Identifier): Path { The UpgradeError MUST have an associated verification function added to the channel and client interfaces so that a counterparty may verify that chain has stored an error in the UpgradeError path. ```typescript -// Channel VerifyUpgradeError method -function verifyUpgradeError( - channel: ChannelEnd, +// Connection VerifyChannelUpgradeError method +function verifyChannelUpgradeError( + connection: ConnectionEnd, height: Height, proof: CommitmentProof, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, upgradeErrorReceipt: []byte, ) { - client = queryClient(channel.clientIdentifier) - client.verifyUpgradeError(height, channel.counterpartyPrefix, proof, channel.counterpartyChannelIdentifier, upgradeErrorReceipt) + client = queryClient(connection.clientIdentifier) + client.verifyChannelUpgradeError(height, connection.counterpartyPrefix, proof, counterpartyPortIdentifer, counterpartyChannelIdentifier, upgradeErrorReceipt) } ``` @@ -125,10 +122,11 @@ function verifyUpgradeError( height: Height, prefix: CommitmentPrefix, proof: CommitmentProof, + counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, upgradeErrorReceipt []byte, ) { - path = applyPrefix(prefix, errorPath(counterpartyChannelIdentifier)) + path = applyPrefix(prefix, errorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) abortTransactionUnless(!clientState.frozen) return clientState.verifiedRoots[height].verifyMembership(path, upgradeErrorReceipt, proof) } @@ -139,23 +137,25 @@ function verifyUpgradeError( The timeout path is a public path set by the upgrade initiator to determine when the TRY step should timeout. It stores the `timeoutHeight` and `timeoutTimestamp` by which point the counterparty must have progressed to the TRY step. This path will be proven on the counterparty chain in case of a successful TRY, to ensure timeout has not passed. Or in the case of a timeout, in which case counterparty proves that the timeout has passed on its chain and restores the channel. ```typescript -function timeoutPath(id: Identifier) Path { - return "channels/{id}/upgradeTimeout" +function timeoutPath(portIdentifier: Identifier, channelIdentifier: Identifier) Path { + return "channelUpgrade/ports/{portIdentifier}/channelIdentifier/{channelIdentifier}/upgradeTimeout" } ``` The timeout path MUST have associated verification methods on the channel and client interfaces in order for a counterparty to prove that a chain stored a particular `UpgradeTimeout`. ```typescript -// Channel VerifyUpgradeTimeout method -function verifyUpgradeTimeout( - channel: ChannelEnd, +// Connection VerifyChannelUpgradeTimeout method +function verifyChannelUpgradeTimeout( + connection: ConnectionEnd, height: Height, proof: CommitmentProof, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, upgradeTimeout: UpgradeTimeout, ) { - client = queryClient(channel.clientIdentifier) - client.verifyUpgradeTimeout(height, channel.counterpartyPrefix, proof, channel.counterpartyChannelIdentifier, upgradeTimeout) + client = queryClient(connection.clientIdentifier) + client.verifyChannelUpgradeTimeout(height, connection.counterpartyPrefix, proof, counterpartyPortIdentifier, counterpartyChannelIdentifier, upgradeTimeout) } ``` @@ -166,10 +166,11 @@ function verifyUpgradeTimeout( height: Height, prefix: CommitmentPrefix, proof: CommitmentProof, + counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, upgradeTimeout: UpgradeTimeout, ) { - path = applyPrefix(prefix, timeoutPath(counterpartyChannelIdentifier)) + path = applyPrefix(prefix, timeoutPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) abortTransactionUnless(!clientState.frozen) timeoutBytes = protobuf.marshal(upgradeTimeout) return clientState.verifiedRoots[height].verifyMembership(path, timeoutBytes, proof) @@ -178,7 +179,7 @@ function verifyUpgradeTimeout( ## Sub-Protocols -The Channel Upgrade process consists of three sub-protocols: `UpgradeHandshake`, `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 successf +The Channel Upgrade process consists of three sub-protocols: `UpgradeChannelHandshake`, `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 successfully. ### Upgrade Handshake @@ -207,14 +208,15 @@ A relayer may then submit a `CancelChannelUpgradeMsg` to the counterparty. Upon If an upgrade message arrives after the specified timeout, then the message MUST NOT execute successfully. Again a relayer may submit a proof of this in a `CancelChannelUpgradeTimeoutMsg` so that counterparty cancels the upgrade and restores it original channel as well. ```typescript -function connUpgradeInit( - identifier: Identifier, +function chanUpgradeInit( + portIdentifier: Identifier, + channelIdentifier: Identifier, proposedUpgradeChannel: ChannelEnd, counterpartyTimeoutHeight: Height, counterpartyTimeoutTimestamp: uint64, ) { // current channel must be OPEN - currentChannel = provableStore.get(channelPath(identifier)) + currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel.state == OPEN) // abort transaction if an unmodifiable field is modified @@ -222,9 +224,15 @@ function connUpgradeInit( // NOTE: Any added fields are by default modifiable. abortTransactionUnless( proposedUpgradeChannel.state == UPGRADE_INIT && - proposedUpgradeChannel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier && - proposedUpgradeChannel.clientIdentifier == currentChannel.clientIdentifier && - proposedUpgradeChannel.counterpartyClientIdentifier == currentChannel.counterpartyClientIdentifier + proposedUpgradeChannel.counterpartyPortIdentier == currentChannel.counterpartyPortIdentifier && + proposedUpgradeChannel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier + ) + + // current ordering must be a valid ordering of packets + // in the proposed ordering + // e.g. ORDERED -> UNORDERED, ORDERED -> DAG + abortTransactionUnless( + currentChannel.ordering.subsetOf(proposedUpgradeChannel.ordering) ) // either timeout height or timestamp must be non-zero @@ -235,9 +243,9 @@ function connUpgradeInit( timeoutTimestamp: counterpartyTimeoutTimestamp, } - provableStore.set(timeoutPath(identifier), upgradeTimeout) - provableStore.set(channelPath(identifier), proposedUpgrade.channel) - privateStore.set(restorePath(identifier), currentChannel) + provableStore.set(timeoutPath(portIdentifier, channelIdentifier), upgradeTimeout) + provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgrade.channel) + privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) } ``` @@ -245,10 +253,10 @@ NOTE: It is up to individual implementations how they will provide access-contro Access control on counterparty should inform choice of timeout values, i.e. timeout value should be large if counterparty's `UpgradeTry` is gated by chain governance. ```typescript -function connUpgradeTry( - identifier: Identifier, - proposedUpgrade: UpgradeChannelState, - counterpartyChannel: ChannelEnd, +function chanUpgradeTry( + portIdentifier: Identifier, + channelIdentifier: Identifier, + proposedUpgradeChannel: ChannelEnd, timeoutHeight: Height, timeoutTimestamp: uint64, UpgradeTimeout: UpgradeTimeout, @@ -257,7 +265,7 @@ function connUpgradeTry( proofHeight: Height ) { // current channel must be OPEN or UPGRADE_INIT (crossing hellos) - currentChannel = provableStore.get(channelPath(identifier)) + currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(currentChannel.state == OPEN || currentChannel.state == UPGRADE_INIT) if currentChannel.state == UPGRADE_INIT { @@ -270,19 +278,24 @@ function connUpgradeTry( } else { // this is first message in upgrade handshake on this chain so we must store original channel in restore path // in case we need to restore channel later. - privateStore.set(restorePath(identifier), currentChannel) + privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) } // abort transaction if an unmodifiable field is modified // upgraded channel state must be in `UPGRADE_TRY` // NOTE: Any added fields are by default modifiable. abortTransactionUnless( - proposedUpgrade.channel.state == UPGRADE_TRY && - proposedUpgrade.channel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier && - proposedUpgrade.channel.clientIdentifier == currentChannel.clientIdentifier && - proposedUpgrade.channel.counterpartyClientIdentifier == currentChannel.counterpartyClientIdentifier + proposedUpgradeChannel.state == UPGRADE_TRY && + proposedUpgradeChannel.counterpartyPortIdentifier == currentChannel.counterpartyPortIdentifier && + proposedUpgradeChannel.counterpartyChannelIdentifier == currentChannel.counterpartyChannelIdentifier ) + // current ordering must be a valid ordering of packets + // in the proposed ordering + // e.g. ORDERED -> UNORDERED, ORDERED -> DAG + abortTransactionUnless( + currentChannel.ordering.subsetOf(proposedUpgradeChannel.ordering) + ) // either timeout height or timestamp must be non-zero // if the upgrade feature is implemented on the TRY chain, then a relayer may submit a TRY transaction after the timeout. @@ -293,25 +306,12 @@ function connUpgradeTry( timeoutTimestamp: timeoutTimestamp, } - // verify that counterparty channel unmodifiable fields have not changed and counterparty state - // is UPGRADE_INIT - restoreChannelUnless( - counterpartyChannel.state == UPGRADE_INIT && - counterpartyChannel.counterpartyChannelIdentifier == identifier && - counterpartyChannel.clientIdentifier == currentChannel.counterpartyClientIdentifier && - counterpartyChannel.counterpartyClientIdentifier == currentChannel.clientIdentifier - ) - // counterparty-specified timeout must not have exceeded restoreChannelUnless( timeoutHeight < currentHeight() && timeoutTimestamp < currentTimestamp() ) - // verify chosen versions are compatible - versionsIntersection = intersection(counterpartyChannel.version, proposedUpgrade.Channel.version) - version = pickVersion(versionsIntersection) // aborts transaction if there is no intersection - // both channel ends must be mutually compatible. // this function has been left unspecified since it will depend on the specific structure of the new channel. // It is the responsibility of implementations to make sure that verification that the proposed new channels @@ -322,7 +322,7 @@ function connUpgradeTry( abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) - provableStore.set(channelPath(identifier), proposedUpgrade.channel) + provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgrade.channel) } ``` @@ -330,16 +330,15 @@ NOTE: It is up to individual implementations how they will provide access-contro ```typescript -function onChanUpgradeAck( - identifier: Identifier, +function chanUpgradeAck( + portIdentifier: Identifier, + channelIdentifier: Identifier, counterpartyChannel: ChannelEnd, - counterpartyStatus: UpgradeError, proofChannel: CommitmentProof, - proofUpgradeError: CommitmentProof, proofHeight: Height ) { // current channel is in UPGRADE_INIT or UPGRADE_TRY (crossing hellos) - currentChannel = provableStore.get(channelPath(identifier)) + currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(currentChannel.state == UPGRADE_INIT || currentChannel.state == UPGRADE_TRY) // counterparty must be in TRY state @@ -358,21 +357,22 @@ function onChanUpgradeAck( // upgrade is complete // set channel to OPEN and remove unnecessary state currentChannel.state = OPEN - provableStore.set(channelPath(identifier), currentChannel) - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) + provableStore.set(channelPath(portIdentifier, channelIdentifier), currentChannel) + provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) + privateStore.delete(restorePath(portIdentifier, channelIdentifier)) } ``` ```typescript -function onChanUpgradeConfirm( - identifier: Identifier, +function chanUpgradeConfirm( + portIdentifier: Identifier, + channelIdentifier: Identifier, counterpartyChannel: ChannelEnd, proofChannel: CommitmentProof, proofHeight: Height, ) { // current channel is in UPGRADE_TRY - currentChannel = provableStore.get(channelPath(identifier)) + currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel.state == UPGRADE_TRY) // counterparty must be in OPEN state @@ -384,9 +384,9 @@ function onChanUpgradeConfirm( // upgrade is complete // set channel to OPEN and remove unnecessary state currentChannel.state = OPEN - provableStore.set(channelPath(identifier), currentChannel) - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) + provableStore.set(channelPath(portIdentifier, channelIdentifier), currentChannel) + provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) + privateStore.delete(restorePath(portIdentifier, channelIdentifier)) } ``` @@ -397,11 +397,11 @@ function restoreChannelUnless(condition: bool) { // write an error receipt into the error path // and restore original channel errorReceipt = []byte{1} - provableStore.set(errorPath(identifier), errorReceipt) - originalChannel = privateStore.get(restorePath(identifier)) - provableStore.set(channelPath(identifier), originalChannel) - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) + provableStore.set(errorPath(portIdentifier, channelIdentifier), errorReceipt) + originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) + provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) + provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) + privateStore.delete(restorePath(portIdentifier, channelIdentifier)) // caller should return as well } else { // caller should continue execution @@ -415,29 +415,29 @@ During the upgrade handshake a chain may cancel the upgrade by writing an error ```typescript function cancelChannelUpgrade( - identifier: Identifer, + portIdentifier: Identifier, + channelIdentifier: Identifier, errorReceipt: []byte, - counterpartyUpgradeError: UpgradeError, proofUpgradeError: CommitmentProof, proofHeight: Height, ) { // current channel is in UPGRADE_INIT or UPGRADE_TRY - currentChannel = provableStore.get(channelPath(identifier)) + currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel.state == UPGRADE_INIT || channel.state == UPGRADE_TRY) abortTransactionUnless(!isEmpty(errorReceipt)) - abortTransactionUnless(verifyUpgradeError(currentChannel, proofHeight, proofUpgradeError, currentChannel.counterpartyChannelIdentifier, counterpartyUpgradeError)) + abortTransactionUnless(verifyUpgradeError(currentChannel, proofHeight, proofUpgradeError, currentChannel.counterpartyChannelIdentifier, errorReceipt)) // cancel upgrade // and restore original conneciton // delete unnecessary state - originalChannel = privateStore.get(restorePath(identifier)) - provableStore.set(channelPath(identifier), originalChannel) + originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) + provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) // delete auxilliary upgrade state - provableStore.delete(timeoutPath(identifier)) - privateStore.delete(restorePath(identifier)) + provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) + privateStore.delete(restorePath(portIdentifier, channelIdentifier)) } ``` @@ -451,16 +451,17 @@ A relayer must then submit an `UpgradeTimeout` message to the initializing chain ```typescript function timeoutChannelUpgrade( - identifier: Identifier, + portIdentifier: Identifier, + channelIdentifier: Identifier, counterpartyChannel: ChannelEnd, proofChannel: CommitmentProof, proofHeight: Height, ) { // current channel must be in UPGRADE_INIT - currentChannel = provableStore.get(channelPath(identifier)) + currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnles(currentChannel.state == UPGRADE_INIT) - upgradeTimeout = provableStore.get(timeoutPath(identifier)) + upgradeTimeout = provableStore.get(timeoutPath(portIdentifier, channelIdentifier)) // proof must be from a height after timeout has elapsed. Either timeoutHeight or timeoutTimestamp must be defined. // if timeoutHeight is defined and proof is from before timeout height From 7f5a03eb24f456d5b18aa80c2a19ef2f19b52f37 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 14 Mar 2022 18:04:27 +0100 Subject: [PATCH 03/12] fix typos --- spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 268bcaf5b..c87fc1c93 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -6,7 +6,7 @@ This standard document specifies the interfaces and state machine logic that IBC ### Motivation -As new features get added to IBC, chains may wish the 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 channels that built on top of the channel. +As new features get added to IBC, chains may wish the 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 @@ -15,7 +15,7 @@ As new features get added to IBC, chains may wish the take advantage of new chan - 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. -- The channel upgrade protocol should have the ability to change all channel-related parameters; however the channel upgrade protocol MUST NOT be able to change the underlying `ClientState`. +- The channel upgrade protocol should have the ability to change all channel-related parameters; however the channel upgrade protocol MUST NOT be able to change the underlying `ConnectionEnd`. The channel upgrade protocol MUST NOT modify the channel identifiers. ## Technical Specification From d6d3a67f74eeb3e6e15d5b67dad16336687d0ba5 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 14 Mar 2022 18:24:53 +0100 Subject: [PATCH 04/12] call app upgrade callbacks --- .../UPGRADES.md | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index c87fc1c93..e4105af0e 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -243,8 +243,26 @@ function chanUpgradeInit( timeoutTimestamp: counterpartyTimeoutTimestamp, } + // call modules onChanUpgradeInit callback + module = lookupModule(portIdentifier) + version, err = module.onChanUpgradeInit( + proposedUpgradeChannel.order, + proposedUpgradeChannel.connectionHops, + proposedUpgradeChannel.portIdentifier, + proposedUpgradeChannel.channelIdentifer, + proposedUpgradeChannel.counterpartyPortIdentifer, + proposedUpgradeChannel.counterpartyChannelIdentifier, + proposedUpgradeChannel.version + ) + // abort transaction if callback returned error + abortTransactionUnless(err != nil) + + // replace channel version with the version returned by application + // in case it was modified + proposedUpgradeChannel.version = version + provableStore.set(timeoutPath(portIdentifier, channelIdentifier), upgradeTimeout) - provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgrade.channel) + provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgradeChannel) privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) } ``` @@ -274,7 +292,7 @@ function chanUpgradeTry( // except for the channel state (upgrade channel will be in UPGRADE_TRY and current channel will be in UPGRADE_INIT) // if the proposed upgrades on either side are incompatible, then we will restore the channel and cancel the upgrade. currentChannel.state = UPGRADE_TRY - restoreChannelUnless(currentChannel.IsEqual(proposedUpgrade.channel)) + restoreChannelUnless(currentChannel.IsEqual(proposedUpgradeChannel) } else { // this is first message in upgrade handshake on this chain so we must store original channel in restore path // in case we need to restore channel later. @@ -318,11 +336,29 @@ function chanUpgradeTry( // on either side are correctly constructed according to the new version selected. restoreChannelUnless(IsCompatible(counterpartyChannel, proposedUpgrade.Channel)) + // call modules onChanUpgradeTry callback + module = lookupModule(portIdentifier) + version, err = module.onChanUpgradeInit( + proposedUpgradeChannel.order, + proposedUpgradeChannel.connectionHops, + proposedUpgradeChannel.portIdentifier, + proposedUpgradeChannel.channelIdentifer, + proposedUpgradeChannel.counterpartyPortIdentifer, + proposedUpgradeChannel.counterpartyChannelIdentifier, + proposedUpgradeChannel.version + ) + // restore channel if callback returned error + restoreChannelUnless(err != nil) + + // replace channel version with the version returned by application + // in case it was modified + proposedUpgradeChannel.version = version + // verify proofs of counterparty state abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) - provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgrade.channel) + provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgradeChannel) } ``` @@ -351,6 +387,17 @@ function chanUpgradeAck( // on either side are correctly constructed according to the new version selected. restoreChannelUnless(IsCompatible(counterpartyChannel, channel)) + // call modules onChanUpgradeAck callback + module = lookupModule(portIdentifier) + err = module.onChanUpgradeAck( + portIdentifier, + channelIdentifier, + counterpartyChannel.channelIdentifier, + counterpartyChannel.version + ) + // restore channel if callback returned error + restoreChannelUnless(err != nil) + // verify proofs of counterparty state abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) @@ -380,6 +427,14 @@ function chanUpgradeConfirm( // verify proofs of counterparty state abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + + // call modules onChanUpgradeConfirm callback + module = lookupModule(portIdentifier) + // confirm callback must not return error since counterparty successfully upgraded + module.onChanUpgradeConfirm( + portIdentifer, + channelIdentifier + ) // upgrade is complete // set channel to OPEN and remove unnecessary state @@ -402,6 +457,16 @@ function restoreChannelUnless(condition: bool) { provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) privateStore.delete(restorePath(portIdentifier, channelIdentifier)) + + // call modules onChanUpgradeRestore callback + module = lookupModule(portIdentifier) + // restore callback must not return error and it must successfully restore + // application to its pre-upgrade state + module.onChanUpgradeRestore( + portIdentifier, + channelIdentifier + ) + // caller should return as well } else { // caller should continue execution From 1f7d1c90333847471576043bc4b4b64310e9a546 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 14 Mar 2022 18:48:19 +0100 Subject: [PATCH 05/12] fix restoreChannelUnless usage --- .../UPGRADES.md | 147 +++++++++++------- 1 file changed, 88 insertions(+), 59 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index e4105af0e..6d2cc1d1e 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -181,6 +181,34 @@ function verifyUpgradeTimeout( The Channel Upgrade process consists of three sub-protocols: `UpgradeChannelHandshake`, `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 successfully. +### Utility Functions + +`restoreConnectionUnless()` is a utility function that allows a chain to abort an upgrade handshake in progress, and return the `channelEnd` to its original pre-upgrade state while also setting the `errorReceipt`. A relayer can then send a `cancelUpgradeMsg` to the counterparty so that it can restore its `channelEnd` to its pre-upgrade state as well. Once both channel ends are back to the pre-upgrade state, packet processing will resume with the original channel and application parameters. + +```typescript +function restoreChannel() { + // cancel upgrade + // write an error receipt into the error path + // and restore original channel + errorReceipt = []byte{1} + provableStore.set(errorPath(portIdentifier, channelIdentifier), errorReceipt) + originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) + provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) + provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) + privateStore.delete(restorePath(portIdentifier, channelIdentifier)) + + // call modules onChanUpgradeRestore callback + module = lookupModule(portIdentifier) + // restore callback must not return error and it must successfully restore + // application to its pre-upgrade state + module.onChanUpgradeRestore( + portIdentifier, + channelIdentifier + ) + // caller should return as well +} +``` + ### Upgrade Handshake The upgrade handshake defines four datagrams: *ChanUpgradeInit*, *ChanUpgradeTry*, *ChanUpgradeAck*, and *ChanUpgradeConfirm* @@ -286,19 +314,6 @@ function chanUpgradeTry( currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(currentChannel.state == OPEN || currentChannel.state == UPGRADE_INIT) - if currentChannel.state == UPGRADE_INIT { - // if there is a crossing hello, ie an UpgradeInit has been called on both channelEnds, - // then we must ensure that the proposedUpgrade by the counterparty is the same as the currentChannel - // except for the channel state (upgrade channel will be in UPGRADE_TRY and current channel will be in UPGRADE_INIT) - // if the proposed upgrades on either side are incompatible, then we will restore the channel and cancel the upgrade. - currentChannel.state = UPGRADE_TRY - restoreChannelUnless(currentChannel.IsEqual(proposedUpgradeChannel) - } else { - // this is first message in upgrade handshake on this chain so we must store original channel in restore path - // in case we need to restore channel later. - privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) - } - // abort transaction if an unmodifiable field is modified // upgraded channel state must be in `UPGRADE_TRY` // NOTE: Any added fields are by default modifiable. @@ -314,27 +329,56 @@ function chanUpgradeTry( abortTransactionUnless( currentChannel.ordering.subsetOf(proposedUpgradeChannel.ordering) ) + + // verify proofs of counterparty state + abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) + + if currentChannel.state == UPGRADE_INIT { + // if there is a crossing hello, ie an UpgradeInit has been called on both channelEnds, + // then we must ensure that the proposedUpgrade by the counterparty is the same as the currentChannel + // except for the channel state (upgrade channel will be in UPGRADE_TRY and current channel will be in UPGRADE_INIT) + // if the proposed upgrades on either side are incompatible, then we will restore the channel and cancel the upgrade. + currentChannel.state = UPGRADE_TRY + if !currentChannel.IsEqual(proposedUpgradeChannel) { + restoreChannel() + return + } + } else { + // this is first message in upgrade handshake on this chain so we must store original channel in restore path + // in case we need to restore channel later. + privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) + } + + // either timeout height or timestamp must be non-zero // if the upgrade feature is implemented on the TRY chain, then a relayer may submit a TRY transaction after the timeout. // this will restore the channel on the executing chain and allow counterparty to use the CancelUpgradeMsg to restore their channel. - restoreChannelUnless(timeoutHeight != 0 || timeoutTimestamp != 0) + if timeoutHeight == 0 && timeoutTimestamp == 0 { + restoreChannel() + return + } upgradeTimeout = UpgradeTimeout{ timeoutHeight: timeoutHeight, timeoutTimestamp: timeoutTimestamp, } // counterparty-specified timeout must not have exceeded - restoreChannelUnless( - timeoutHeight < currentHeight() && - timeoutTimestamp < currentTimestamp() - ) + if (currentHeight() > timeoutHeight && timeoutHeight != 0) || + (currentTimestamp() > timeoutTimestamp && timeoutTimestamp != 0) { + restoreChannel() + return + } // both channel ends must be mutually compatible. // this function has been left unspecified since it will depend on the specific structure of the new channel. // It is the responsibility of implementations to make sure that verification that the proposed new channels // on either side are correctly constructed according to the new version selected. - restoreChannelUnless(IsCompatible(counterpartyChannel, proposedUpgrade.Channel)) + if !IsCompatible(counterpartyChannel, proposedUpgradeChannel) { + restoreChannel() + return + } // call modules onChanUpgradeTry callback module = lookupModule(portIdentifier) @@ -348,15 +392,14 @@ function chanUpgradeTry( proposedUpgradeChannel.version ) // restore channel if callback returned error - restoreChannelUnless(err != nil) + if err != nil { + restoreChannel() + return + } // replace channel version with the version returned by application // in case it was modified proposedUpgradeChannel.version = version - - // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) - abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) provableStore.set(channelPath(portIdentifier, channelIdentifier), proposedUpgradeChannel) } @@ -377,15 +420,24 @@ function chanUpgradeAck( currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(currentChannel.state == UPGRADE_INIT || currentChannel.state == UPGRADE_TRY) + // verify proofs of counterparty state + abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + // counterparty must be in TRY state - restoreChannelUnless(counterpartyChannel.State == UPGRADE_TRY) + if counterpartyChannel.State != UPGRADE_TRY { + restoreChannel() + return + } // verify channels are mutually compatible // this will also check counterparty chosen version is valid // this function has been left unspecified since it will depend on the specific structure of the new channel. // It is the responsibility of implementations to make sure that verification that the proposed new channels // on either side are correctly constructed according to the new version selected. - restoreChannelUnless(IsCompatible(counterpartyChannel, channel)) + if !IsCompatible(counterpartyChannel, channel) { + restoreChannel() + return + } // call modules onChanUpgradeAck callback module = lookupModule(portIdentifier) @@ -396,10 +448,10 @@ function chanUpgradeAck( counterpartyChannel.version ) // restore channel if callback returned error - restoreChannelUnless(err != nil) - - // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + if err != nil { + restoreChannel() + return + } // upgrade is complete // set channel to OPEN and remove unnecessary state @@ -445,34 +497,6 @@ function chanUpgradeConfirm( } ``` -```typescript -function restoreChannelUnless(condition: bool) { - if !condition { - // cancel upgrade - // write an error receipt into the error path - // and restore original channel - errorReceipt = []byte{1} - provableStore.set(errorPath(portIdentifier, channelIdentifier), errorReceipt) - originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) - provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) - provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) - privateStore.delete(restorePath(portIdentifier, channelIdentifier)) - - // call modules onChanUpgradeRestore callback - module = lookupModule(portIdentifier) - // restore callback must not return error and it must successfully restore - // application to its pre-upgrade state - module.onChanUpgradeRestore( - portIdentifier, - channelIdentifier - ) - - // caller should return as well - } else { - // caller should continue execution - } -} -``` ### Cancel Upgrade Process @@ -541,7 +565,12 @@ function timeoutChannelUpgrade( abortTransactionUnless(channel.client.verifyChannelState(proofHeight, proofChannel, counterpartyChannel)) // we must restore the channel since the timeout verification has passed - restoreChannelUnless(false) + originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) + provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) + + // delete auxilliary upgrade state + provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) + privateStore.delete(restorePath(portIdentifier, channelIdentifier)) } ``` From 0d5574aff0df9159c0e7759bfb86ff38e98c9a03 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 14 Mar 2022 19:03:14 +0100 Subject: [PATCH 06/12] fix state conditional checks on TRY --- spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 6d2cc1d1e..8dcd56a31 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -344,14 +344,15 @@ function chanUpgradeTry( restoreChannel() return } - } else { + } else if currentChannel.state == OPEN { // this is first message in upgrade handshake on this chain so we must store original channel in restore path // in case we need to restore channel later. privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) + } else { + // abort transaction if current channel is not in state: INIT or OPEN + abortTransactionUnless(false) } - - // either timeout height or timestamp must be non-zero // if the upgrade feature is implemented on the TRY chain, then a relayer may submit a TRY transaction after the timeout. // this will restore the channel on the executing chain and allow counterparty to use the CancelUpgradeMsg to restore their channel. From 795a0a1d14a3fc663c397fbd0eae89b53b56349d Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 22 Mar 2022 14:27:23 +0100 Subject: [PATCH 07/12] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 8dcd56a31..2009f4c00 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -331,7 +331,7 @@ function chanUpgradeTry( ) // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, proposedUpgradeChannel)) abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) if currentChannel.state == UPGRADE_INIT { @@ -349,7 +349,7 @@ function chanUpgradeTry( // in case we need to restore channel later. privateStore.set(restorePath(portIdentifier, channelIdentifier), currentChannel) } else { - // abort transaction if current channel is not in state: INIT or OPEN + // abort transaction if current channel is not in state: UPGRADE_INIT or OPEN abortTransactionUnless(false) } @@ -383,7 +383,7 @@ function chanUpgradeTry( // call modules onChanUpgradeTry callback module = lookupModule(portIdentifier) - version, err = module.onChanUpgradeInit( + version, err = module.onChanUpgradeTry( proposedUpgradeChannel.order, proposedUpgradeChannel.connectionHops, proposedUpgradeChannel.portIdentifier, From 0a00e31d3d588ae140e9144600ed46c78f6e2fee Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 22 Mar 2022 14:41:56 +0100 Subject: [PATCH 08/12] fix partial review of colin, left out crossing hello remarks --- .../UPGRADES.md | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 2009f4c00..d12c6256d 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -22,7 +22,7 @@ The channel upgrade protocol MUST NOT modify the channel identifiers. ### Data Structures -The `ChannelState` and `ChannelEnd` are defined in [ICS-4](./README.md), they are reproduced here for the reader's convenience. `UPGRADE_INIT`, `UPGRADE_TRY`, `UPGRADE_ERR` are additional states added to enable the upgrade feature. +The `ChannelState` and `ChannelEnd` are defined in [ICS-4](./README.md), they are reproduced here for the reader's convenience. `UPGRADE_INIT`, `UPGRADE_TRY` are additional states added to enable the upgrade feature. ```typescript enum ChannelState { @@ -98,7 +98,7 @@ function errorPath(portIdentifier: Identifier, channelIdentifier: Identifier): P } ``` -The UpgradeError MUST have an associated verification function added to the channel and client interfaces so that a counterparty may verify that chain has stored an error in the UpgradeError path. +The UpgradeError MUST have an associated verification membership and nonmembership function added to the connection interface so that a counterparty may verify that chain has stored an error in the UpgradeError path. ```typescript // Connection VerifyChannelUpgradeError method @@ -111,24 +111,23 @@ function verifyChannelUpgradeError( upgradeErrorReceipt: []byte, ) { client = queryClient(connection.clientIdentifier) - client.verifyChannelUpgradeError(height, connection.counterpartyPrefix, proof, counterpartyPortIdentifer, counterpartyChannelIdentifier, upgradeErrorReceipt) + path = applyPrefix(connection.counterpartyPrefix, channelErrorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) + client.verifyMembership(height, 0, 0, proof, path, upgradeErrorReceipt) } ``` ```typescript -// Client VerifyUpgradeError -function verifyUpgradeError( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - upgradeErrorReceipt []byte, +// Connection VerifyChannelUpgradeErrorAbsence method +function verifyChannelUpgradeErrorAbsence( + connection: ConnectionEnd, + height: Height, + proof: CommitmentProof, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, ) { - path = applyPrefix(prefix, errorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) - abortTransactionUnless(!clientState.frozen) - return clientState.verifiedRoots[height].verifyMembership(path, upgradeErrorReceipt, proof) + client = queryClient(connection.clientIdentifier) + path = applyPrefix(connection.counterpartyPrefix, channelErrorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) + client.verifyNonMembership(height, 0, 0, proof, path) } ``` @@ -142,7 +141,7 @@ function timeoutPath(portIdentifier: Identifier, channelIdentifier: Identifier) } ``` -The timeout path MUST have associated verification methods on the channel and client interfaces in order for a counterparty to prove that a chain stored a particular `UpgradeTimeout`. +The timeout path MUST have associated verification membership method on the connection interface in order for a counterparty to prove that a chain stored a particular `UpgradeTimeout`. ```typescript // Connection VerifyChannelUpgradeTimeout method @@ -155,25 +154,8 @@ function verifyChannelUpgradeTimeout( upgradeTimeout: UpgradeTimeout, ) { client = queryClient(connection.clientIdentifier) - client.verifyChannelUpgradeTimeout(height, connection.counterpartyPrefix, proof, counterpartyPortIdentifier, counterpartyChannelIdentifier, upgradeTimeout) -} -``` - -```typescript -// Client VerifyUpgradeTimeout -function verifyUpgradeTimeout( - clientState: ClientState, - height: Height, - prefix: CommitmentPrefix, - proof: CommitmentProof, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - upgradeTimeout: UpgradeTimeout, -) { - path = applyPrefix(prefix, timeoutPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) - abortTransactionUnless(!clientState.frozen) - timeoutBytes = protobuf.marshal(upgradeTimeout) - return clientState.verifiedRoots[height].verifyMembership(path, timeoutBytes, proof) + path = applyPrefix(connection.counterpartyPrefix, channelTimeoutPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) + client.verifyChannelUpgradeTimeout(height, 0, 0, proof, path, upgradeTimeout) } ``` @@ -528,6 +510,14 @@ function cancelChannelUpgrade( // delete auxilliary upgrade state provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) privateStore.delete(restorePath(portIdentifier, channelIdentifier)) + + // call modules onChanUpgradeRestore callback + module = lookupModule(portIdentifier) + // restore callback must not return error since counterparty successfully upgraded + module.onChanUpgradeRestore( + portIdentifer, + channelIdentifier + ) } ``` @@ -558,8 +548,8 @@ function timeoutChannelUpgrade( // then abort transaction abortTransactionUnless(upgradeTimeout.timeoutHeight.IsZero() || proofHeight >= upgradeTimeout.timeoutHeight) // if timeoutTimestamp is defined then the consensus time from proof height must be greater than timeout timestamp - consensusState = queryConsensusState(currentChannel.clientIdentifer, proofHeight) - abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || consensusState.getTimestamp() >= upgradeTimeout.timestamp) + connection = queryConnection(currentChannel.connectionIdentifier) + abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || getTimestampAtHeight(connection, proofHeight) >= upgradeTimeout.timestamp) // counterparty channel must be proved to still be in OPEN state abortTransactionUnless(counterpartyChannel.State === OPEN) @@ -572,6 +562,14 @@ function timeoutChannelUpgrade( // delete auxilliary upgrade state provableStore.delete(timeoutPath(portIdentifier, channelIdentifier)) privateStore.delete(restorePath(portIdentifier, channelIdentifier)) + + // call modules onChanUpgradeRestore callback + module = lookupModule(portIdentifier) + // restore callback must not return error since counterparty successfully upgraded + module.onChanUpgradeRestore( + portIdentifer, + channelIdentifier + ) } ``` From 55d64a1c3e141c3e238c677064d0a1eaf397053a Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 23 Mar 2022 11:18:36 +0100 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: Marius Poke --- .../UPGRADES.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index d12c6256d..df5e67485 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -54,12 +54,12 @@ The desired property that the channel upgrade protocol MUST NOT modify the under MAY BE MODIFIED: - `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. However, it MUST be the case that the previous ordering is a valid subset of the new ordering. Thus, the only supported change is from stricter ordering rules to less strict ordering. Ex: Switching from ORDERED to UNORDERED is supported, switching from UNORDERED to ORDERED is **unsupported**. +- `ordering`: The ordering MAY be modified by the upgrade protocol. However, it MUST be the case that the previous ordering is a valid subset of the new ordering. Thus, the only supported change is from stricter ordering rules to less strict ordering. For example, switching from ORDERED to UNORDERED is supported, switching from UNORDERED to ORDERED is **unsupported**. - `connectionHops`: The connectionHops MAY be modified by the upgrade protocol. MUST NOT BE MODIFIED: -- `counterpartyChannelIdentifier`: The counterparty channel identifier MAY NOT be modified by the upgrade protocol. -- `counterpartyPortIdentifier`: The counterparty port identifier MAY NOT 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 NOTE: If the upgrade adds any fields to the `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. @@ -155,7 +155,7 @@ function verifyChannelUpgradeTimeout( ) { client = queryClient(connection.clientIdentifier) path = applyPrefix(connection.counterpartyPrefix, channelTimeoutPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)) - client.verifyChannelUpgradeTimeout(height, 0, 0, proof, path, upgradeTimeout) + client.verifyMembership(height, 0, 0, proof, path, upgradeTimeout) } ``` @@ -165,7 +165,7 @@ The Channel Upgrade process consists of three sub-protocols: `UpgradeChannelHand ### Utility Functions -`restoreConnectionUnless()` is a utility function that allows a chain to abort an upgrade handshake in progress, and return the `channelEnd` to its original pre-upgrade state while also setting the `errorReceipt`. A relayer can then send a `cancelUpgradeMsg` to the counterparty so that it can restore its `channelEnd` to its pre-upgrade state as well. Once both channel ends are back to the pre-upgrade state, packet processing will resume with the original channel and application parameters. +`restoreChannel()` is a utility function that allows a chain to abort an upgrade handshake in progress, and return the `channelEnd` to its original pre-upgrade state while also setting the `errorReceipt`. A relayer can then send a `CancelChannelUpgradeMsg` to the counterparty so that it can restore its `channelEnd` to its pre-upgrade state as well. Once both channel ends are back to the pre-upgrade state, packet processing will resume with the original channel and application parameters. ```typescript function restoreChannel() { @@ -204,7 +204,7 @@ A successful protocol execution flows as follows (note that all calls are made t | Relayer | `ChanUpgradeAck` | A | (UPGRADE_INIT, UPGRADE_TRY) | (OPEN, UPGRADE_TRY) | | Relayer | `ChanUpgradeConfirm` | B | (OPEN, UPGRADE_TRY) | (OPEN, OPEN) | -At the end of an opening handshake between two chains implementing the sub-protocol, the following properties hold: +At the end of an 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. @@ -256,10 +256,10 @@ function chanUpgradeInit( // call modules onChanUpgradeInit callback module = lookupModule(portIdentifier) version, err = module.onChanUpgradeInit( - proposedUpgradeChannel.order, + proposedUpgradeChannel.ordering, proposedUpgradeChannel.connectionHops, - proposedUpgradeChannel.portIdentifier, - proposedUpgradeChannel.channelIdentifer, + portIdentifier, + channelIdentifer, proposedUpgradeChannel.counterpartyPortIdentifer, proposedUpgradeChannel.counterpartyChannelIdentifier, proposedUpgradeChannel.version @@ -366,10 +366,10 @@ function chanUpgradeTry( // call modules onChanUpgradeTry callback module = lookupModule(portIdentifier) version, err = module.onChanUpgradeTry( - proposedUpgradeChannel.order, + proposedUpgradeChannel.ordering, proposedUpgradeChannel.connectionHops, - proposedUpgradeChannel.portIdentifier, - proposedUpgradeChannel.channelIdentifer, + portIdentifier, + channelIdentifer, proposedUpgradeChannel.counterpartyPortIdentifer, proposedUpgradeChannel.counterpartyChannelIdentifier, proposedUpgradeChannel.version @@ -551,8 +551,8 @@ function timeoutChannelUpgrade( connection = queryConnection(currentChannel.connectionIdentifier) abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || getTimestampAtHeight(connection, proofHeight) >= upgradeTimeout.timestamp) - // counterparty channel must be proved to still be in OPEN state - abortTransactionUnless(counterpartyChannel.State === OPEN) + // counterparty channel must be proved to still be in OPEN state or UPGRADE_INIT state (crossing hellos) + abortTransactionUnless(counterpartyChannel.State === OPEN || counterpartyChannel.State == UPGRADE_INIT) abortTransactionUnless(channel.client.verifyChannelState(proofHeight, proofChannel, counterpartyChannel)) // we must restore the channel since the timeout verification has passed From b831b2399aa8f08e936dfa007afe6a4d06b3428f Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 23 Mar 2022 14:12:03 +0100 Subject: [PATCH 10/12] fix verify functions --- .../UPGRADES.md | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index d12c6256d..0d731de26 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -133,7 +133,7 @@ function verifyChannelUpgradeErrorAbsence( #### TimeoutPath -The timeout path is a public path set by the upgrade initiator to determine when the TRY step should timeout. It stores the `timeoutHeight` and `timeoutTimestamp` by which point the counterparty must have progressed to the TRY step. This path will be proven on the counterparty chain in case of a successful TRY, to ensure timeout has not passed. Or in the case of a timeout, in which case counterparty proves that the timeout has passed on its chain and restores the channel. +The timeout path is a public path set by the upgrade initiator to determine when the TRY step should timeout. It stores the `timeoutHeight` and `timeoutTimestamp` by which point the counterparty must have progressed to the TRY step. The TRY step will prove the timeout values set by the initiating chain and ensure the timeout has not passed. Or in the case of a timeout, in which case counterparty proves that the timeout has passed on its chain and restores the channel. ```typescript function timeoutPath(portIdentifier: Identifier, channelIdentifier: Identifier) Path { @@ -312,9 +312,18 @@ function chanUpgradeTry( currentChannel.ordering.subsetOf(proposedUpgradeChannel.ordering) ) + // construct upgradeTimeout so it can be verified against counterparty state + upgradeTimeout = UpgradeTimeout{ + timeoutHeight: timeoutHeight, + timeoutTimestamp: timeoutTimestamp, + } + + // get underlying connection for proof verification + connection = getConnection(currentChannel.connectionIdentifier) + // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, proposedUpgradeChannel)) - abortTransactionUnless(verifyUpgradeTimeout(currentChannel, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) + abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, proposedUpgradeChannel)) + abortTransactionUnless(verifyChannelUpgradeTimeout(connection, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) if currentChannel.state == UPGRADE_INIT { // if there is a crossing hello, ie an UpgradeInit has been called on both channelEnds, @@ -342,10 +351,7 @@ function chanUpgradeTry( restoreChannel() return } - upgradeTimeout = UpgradeTimeout{ - timeoutHeight: timeoutHeight, - timeoutTimestamp: timeoutTimestamp, - } + // counterparty-specified timeout must not have exceeded if (currentHeight() > timeoutHeight && timeoutHeight != 0) || @@ -403,8 +409,11 @@ function chanUpgradeAck( currentChannel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(currentChannel.state == UPGRADE_INIT || currentChannel.state == UPGRADE_TRY) + // get underlying connection for proof verification + connection = getConnection(currentChannel.connectionIdentifier) + // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) // counterparty must be in TRY state if counterpartyChannel.State != UPGRADE_TRY { @@ -451,6 +460,7 @@ function chanUpgradeConfirm( channelIdentifier: Identifier, counterpartyChannel: ChannelEnd, proofChannel: CommitmentProof, + proofUpgradeError: CommitmentProof, proofHeight: Height, ) { // current channel is in UPGRADE_TRY @@ -460,8 +470,14 @@ function chanUpgradeConfirm( // counterparty must be in OPEN state abortTransactionUnless(counterpartyChannel.State == OPEN) + // get underlying connection for proof verification + connection = getConnection(currentChannel.connectionIdentifier) + // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(currentChannel, proofHeight, proofChannel, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + // verify counterparty did not abort upgrade handshake by writing upgrade error + // must have absent value at upgradeError path + abortTransactionUnless(verifyUpgradeChannelErrorAbsence(connection, proofHeight, proofUpgradeError, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier)) // call modules onChanUpgradeConfirm callback module = lookupModule(portIdentifier) @@ -499,7 +515,10 @@ function cancelChannelUpgrade( abortTransactionUnless(!isEmpty(errorReceipt)) - abortTransactionUnless(verifyUpgradeError(currentChannel, proofHeight, proofUpgradeError, currentChannel.counterpartyChannelIdentifier, errorReceipt)) + // get underlying connection for proof verification + connection = getConnection(currentChannel.connectionIdentifier) + // verify that a non-empty error receipt is written to the upgradeError path + abortTransactionUnless(verifyChannelUpgradeError(connection, proofHeight, proofUpgradeError, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, errorReceipt)) // cancel upgrade // and restore original conneciton @@ -551,9 +570,12 @@ function timeoutChannelUpgrade( connection = queryConnection(currentChannel.connectionIdentifier) abortTransactionUnless(upgradeTimeout.timeoutTimestamp.IsZero() || getTimestampAtHeight(connection, proofHeight) >= upgradeTimeout.timestamp) + // get underlying connection for proof verification + connection = getConnection(currentChannel.connectionIdentifier) + // counterparty channel must be proved to still be in OPEN state abortTransactionUnless(counterpartyChannel.State === OPEN) - abortTransactionUnless(channel.client.verifyChannelState(proofHeight, proofChannel, counterpartyChannel)) + abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) // we must restore the channel since the timeout verification has passed originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) From d24fd7bca59be0748b4a0f112d52309932cf467e Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 23 Mar 2022 17:05:46 +0100 Subject: [PATCH 11/12] consistent msg names --- .../UPGRADES.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 491e8178e..95f03025f 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -165,7 +165,7 @@ The Channel Upgrade process consists of three sub-protocols: `UpgradeChannelHand ### Utility Functions -`restoreChannel()` is a utility function that allows a chain to abort an upgrade handshake in progress, and return the `channelEnd` to its original pre-upgrade state while also setting the `errorReceipt`. A relayer can then send a `CancelChannelUpgradeMsg` to the counterparty so that it can restore its `channelEnd` to its pre-upgrade state as well. Once both channel ends are back to the pre-upgrade state, packet processing will resume with the original channel and application parameters. +`restoreChannel()` is a utility function that allows a chain to abort an upgrade handshake in progress, and return the `channelEnd` to its original pre-upgrade state while also setting the `errorReceipt`. A relayer can then send a `ChanUpgradeCancelMsg` to the counterparty so that it can restore its `channelEnd` to its pre-upgrade state as well. Once both channel ends are back to the pre-upgrade state, packet processing will resume with the original channel and application parameters. ```typescript function restoreChannel() { @@ -213,9 +213,9 @@ If a chain does not agree to the proposed counterparty `UpgradedChannel`, it may `errorPath(id) => error_receipt` -A relayer may then submit a `CancelChannelUpgradeMsg` to the counterparty. Upon receiving this message a chain must verify that the counterparty wrote a non-empty error receipt into its `UpgradeError` and if successful, it will restore its original channel as well thus cancelling the upgrade. +A relayer may then submit a `ChanUpgradeCancelMsg` to the counterparty. Upon receiving this message a chain must verify that the counterparty wrote a non-empty error receipt into its `UpgradeError` and if successful, it will restore its original channel as well thus cancelling the upgrade. -If an upgrade message arrives after the specified timeout, then the message MUST NOT execute successfully. Again a relayer may submit a proof of this in a `CancelChannelUpgradeTimeoutMsg` so that counterparty cancels the upgrade and restores it original channel as well. +If an upgrade message arrives after the specified timeout, then the message MUST NOT execute successfully. Again a relayer may submit a proof of this in a `ChanUpgradeTimeoutMsg` so that counterparty cancels the upgrade and restores it original channel as well. ```typescript function chanUpgradeInit( @@ -284,10 +284,10 @@ Access control on counterparty should inform choice of timeout values, i.e. time function chanUpgradeTry( portIdentifier: Identifier, channelIdentifier: Identifier, + counterpartyChannel: ChannelEnd, proposedUpgradeChannel: ChannelEnd, timeoutHeight: Height, timeoutTimestamp: uint64, - UpgradeTimeout: UpgradeTimeout, proofChannel: CommitmentProof, proofUpgradeTimeout: CommitmentProof, proofHeight: Height @@ -322,7 +322,7 @@ function chanUpgradeTry( connection = getConnection(currentChannel.connectionIdentifier) // verify proofs of counterparty state - abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, proposedUpgradeChannel)) + abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) abortTransactionUnless(verifyChannelUpgradeTimeout(connection, proofHeight, proofUpgradeTimeout, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, upgradeTimeout)) if currentChannel.state == UPGRADE_INIT { @@ -346,7 +346,7 @@ function chanUpgradeTry( // either timeout height or timestamp must be non-zero // if the upgrade feature is implemented on the TRY chain, then a relayer may submit a TRY transaction after the timeout. - // this will restore the channel on the executing chain and allow counterparty to use the CancelUpgradeMsg to restore their channel. + // this will restore the channel on the executing chain and allow counterparty to use the ChanUpgradeCancelMsg to restore their channel. if timeoutHeight == 0 && timeoutTimestamp == 0 { restoreChannel() return @@ -499,7 +499,7 @@ function chanUpgradeConfirm( ### Cancel Upgrade Process -During the upgrade handshake a chain may cancel the upgrade by writing an error receipt into the error path and restoring the original channel to `OPEN`. The counterparty must then restore its channel to `OPEN` as well. A relayer can facilitate this by calling `CancelChannelUpgrade`: +During the upgrade handshake a chain may cancel the upgrade by writing an error receipt into the error path and restoring the original channel to `OPEN`. The counterparty must then restore its channel to `OPEN` as well. A relayer can facilitate this by sending `ChanUpgradeCancelMsg` to the handler: ```typescript function cancelChannelUpgrade( @@ -546,7 +546,7 @@ It is possible for the channel upgrade process to stall indefinitely on UPGRADE_ In this case, we do not want the initializing chain to be stuck indefinitely in the `UPGRADE_INIT` step. Thus, the `UpgradeInit` message will contain a `TimeoutHeight` and `TimeoutTimestamp`. The counterparty chain is expected to reject `UpgradeTry` message if the specified timeout has already elapsed. -A relayer must then submit an `UpgradeTimeout` message to the initializing chain which proves that the counterparty is still in its original state. If the proof succeeds, then the initializing chain shall also restore its original channel and cancel the upgrade. +A relayer must then submit an `ChanUpgradeTimeoutMsg` message to the initializing chain which proves that the counterparty is still in its original state. If the proof succeeds, then the initializing chain shall also restore its original channel and cancel the upgrade. ```typescript function timeoutChannelUpgrade( @@ -577,6 +577,10 @@ function timeoutChannelUpgrade( abortTransactionUnless(counterpartyChannel.State === OPEN || counterpartyChannel.State == UPGRADE_INIT) abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) + if counterpartyChannel.State == UPGRADE_INIT { + + } + // we must restore the channel since the timeout verification has passed originalChannel = privateStore.get(restorePath(portIdentifier, channelIdentifier)) provableStore.set(channelPath(portIdentifier, channelIdentifier), originalChannel) From 0c8e7534b7073d377a871fced424636d5f2e6d8c Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 23 Mar 2022 17:51:10 +0100 Subject: [PATCH 12/12] add explicit erroring --- spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md index 95f03025f..226f6f772 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md +++ b/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md @@ -578,7 +578,11 @@ function timeoutChannelUpgrade( abortTransactionUnless(verifyChannelState(connection, proofHeight, proofChannel, currentChannel.counterpartyPortIdentifier, currentChannel.counterpartyChannelIdentifier, counterpartyChannel)) if counterpartyChannel.State == UPGRADE_INIT { - + // if the counterparty is in UPGRADE_INIT and we have timed out then we should write and error receipt + // to ensure that counterparty aborts the handshake as well and returns to the original state + // write an error receipt into the error path + errorReceipt = []byte{1} + provableStore.set(errorPath(portIdentifier, channelIdentifier), errorReceipt) } // we must restore the channel since the timeout verification has passed