Skip to content

Commit 5dd0d0d

Browse files
committed
Handle case where current state can never reach target state
According to the IBC spec https://github.com/cosmos/ibc/blob/main/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md#upgrade-handshake, some states may be skipped during the channel upgrade handshake. For example, the stat pair (INIT, FLUSHING) may transition directly to (FLUSHCOMPLETE, FLUSHING) without passing through FLUSHING from INIT. Signed-off-by: Takeshi Arabiki <takeshi.arabiki@datachain.jp>
1 parent f9b6353 commit 5dd0d0d

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

core/channel-upgrade.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ func ExecuteChannelUpgrade(ctx context.Context, pathName string, src, dst *Prova
144144
logger := GetChannelPairLogger(src, dst)
145145
defer logger.TimeTrackContext(ctx, time.Now(), "ExecuteChannelUpgrade")
146146

147+
if (targetSrcState == UPGRADE_STATE_UNINIT && targetDstState == UPGRADE_STATE_INIT) ||
148+
(targetSrcState == UPGRADE_STATE_INIT && targetDstState == UPGRADE_STATE_UNINIT) {
149+
return fmt.Errorf("unreachable target state pair: (%s, %s)", targetSrcState, targetDstState)
150+
}
151+
147152
failures := 0
148153
firstCall := true
149154
err := runUntilComplete(ctx, interval, func() (bool, error) {
@@ -381,12 +386,15 @@ func upgradeChannelStep(ctx context.Context, src, dst *ProvableChain, targetSrcS
381386
),
382387
)}
383388

384-
// check if both chains have reached the target states or UNINIT states
385-
if srcState == targetSrcState && dstState == targetDstState {
386-
if firstCall && srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_UNINIT {
387-
logger.InfoContext(ctx, "both chains have already reached the target states, or the channel upgrade has not been initialized")
389+
if hasReachedOrPassedTargetState(srcState, targetSrcState, dstState) && hasReachedOrPassedTargetState(dstState, targetDstState, srcState) {
390+
if firstCall {
391+
if srcState == UPGRADE_STATE_UNINIT && targetSrcState == UPGRADE_STATE_UNINIT {
392+
logger.InfoContext(ctx, "both chains have already reached or passed the target states, or the channel upgrade has not been initialized")
393+
} else {
394+
logger.InfoContext(ctx, "both chains have already reached or passed the target states")
395+
}
388396
} else {
389-
logger.InfoContext(ctx, "both chains have reached the target states")
397+
logger.InfoContext(ctx, "both chains have reached or passed the target states")
390398
}
391399
out.Last = true
392400
return out, nil
@@ -397,7 +405,8 @@ func upgradeChannelStep(ctx context.Context, src, dst *ProvableChain, targetSrcS
397405
dstAction := UPGRADE_ACTION_NONE
398406
switch {
399407
case srcState == UPGRADE_STATE_UNINIT && dstState == UPGRADE_STATE_UNINIT:
400-
return nil, errors.New("channel upgrade has not been initialized; it will never reach the target states")
408+
// This line should never be reached because the channel upgrade is considered completed
409+
return nil, errors.New("unexpected transition")
401410
case srcState == UPGRADE_STATE_INIT && dstState == UPGRADE_STATE_UNINIT:
402411
if dstChan.Channel.UpgradeSequence >= srcChan.Channel.UpgradeSequence {
403412
srcAction = UPGRADE_ACTION_CANCEL
@@ -793,3 +802,31 @@ func buildActionMsg(
793802
panic(fmt.Errorf("unexpected action: %s", action))
794803
}
795804
}
805+
806+
// hasReachedOrPassedTargetState checks if the current state has reached or passed the target state,
807+
// including cases where the target state is skipped.
808+
func hasReachedOrPassedTargetState(currentState, targetState, counterpartyCurrentState UpgradeState) bool {
809+
return currentState == targetState || hasPassedTargetState(currentState, targetState, counterpartyCurrentState)
810+
}
811+
812+
// hasPassedTargetState checks if the current state has passed the target state,
813+
// including cases where the target state is skipped.
814+
func hasPassedTargetState(currentState, targetState, counterpartyCurrentState UpgradeState) bool {
815+
// Check if the current state has passed the target state.
816+
// For simplicity, we consider that any state that can be reached after the target state has passed the target state.
817+
// NOTE: Each chain can cancel the upgrade and initialize another upgrade at any time. This means that the state
818+
// can transition to UNINIT from any state except the case where the counterparty is in INIT state.
819+
820+
isUninitReachable := counterpartyCurrentState != UPGRADE_STATE_INIT
821+
switch targetState {
822+
case UPGRADE_STATE_INIT:
823+
return currentState == UPGRADE_STATE_FLUSHING || currentState == UPGRADE_STATE_FLUSHCOMPLETE ||
824+
(isUninitReachable && currentState == UPGRADE_STATE_UNINIT)
825+
case UPGRADE_STATE_FLUSHING:
826+
return currentState == UPGRADE_STATE_FLUSHCOMPLETE || (isUninitReachable && currentState == UPGRADE_STATE_UNINIT)
827+
case UPGRADE_STATE_FLUSHCOMPLETE:
828+
return isUninitReachable && currentState == UPGRADE_STATE_UNINIT
829+
default:
830+
return false
831+
}
832+
}

tests/cases/tm2tm/scripts/test-channel-upgrade

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,18 @@ $RLY tx channel-upgrade execute ibc01
147147
checkResult orig
148148

149149
echo '##### case 11 #####'
150-
# The target states will never be reached
151-
if $RLY tx channel-upgrade execute ibc01 --target-src-state INIT --target-dst-state FLUSHING
150+
$RLY tx channel-upgrade init ibc01 ibc0 $altSrcOpts
151+
$RLY tx channel-upgrade execute ibc01 --target-src-state FLUSHING --target-dst-state FLUSHING
152+
# Both chains have already passed the target states
153+
$RLY tx channel-upgrade execute ibc01 --target-src-state INIT --target-dst-state INIT
154+
$RLY tx channel-upgrade execute ibc01
155+
checkResult alt
156+
157+
echo '##### case 12 #####'
158+
# Unreachable target pair (INIT, UNINIT)
159+
if $RLY tx channel-upgrade execute ibc01 --target-src-state INIT
152160
then
153-
echo 'channel-upgrade completed with exit code 0, but non-zero code was expected' >&2
161+
echo 'channel-upgrade finished with exit code 0, but non-zero code was expected' >&2
154162
exit 1
155163
fi
156-
checkResult orig
164+
checkResult alt

0 commit comments

Comments
 (0)