Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ChanUpgradeTimeout with spec changes #4438

5 changes: 1 addition & 4 deletions modules/core/04-channel/keeper/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,17 +405,14 @@ func emitChannelUpgradeTimeoutEvent(ctx sdk.Context, portID string, channelID st
}

// emitErrorReceiptEvent emits an error receipt event
func emitErrorReceiptEvent(ctx sdk.Context, portID string, channelID string, currentChannel types.Channel, upgradeFields types.UpgradeFields, err error) {
func emitErrorReceiptEvent(ctx sdk.Context, portID string, channelID string, currentChannel types.Channel, err error) {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeChannelUpgradeInit, // TODO(bug): use correct const value
sdk.NewAttribute(types.AttributeKeyPortID, portID),
sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
sdk.NewAttribute(types.AttributeCounterpartyPortID, currentChannel.Counterparty.PortId),
sdk.NewAttribute(types.AttributeCounterpartyChannelID, currentChannel.Counterparty.ChannelId),
sdk.NewAttribute(types.AttributeKeyUpgradeConnectionHops, upgradeFields.ConnectionHops[0]),
sdk.NewAttribute(types.AttributeKeyUpgradeVersion, upgradeFields.Version),
sdk.NewAttribute(types.AttributeKeyUpgradeOrdering, upgradeFields.Ordering.String()),
sdk.NewAttribute(types.AttributeKeyUpgradeSequence, fmt.Sprintf("%d", currentChannel.UpgradeSequence)),
sdk.NewAttribute(types.AttributeKeyUpgradeErrorReceipt, err.Error()),
),
Expand Down
111 changes: 45 additions & 66 deletions modules/core/04-channel/keeper/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,7 @@ func (k Keeper) WriteUpgradeCancelChannel(ctx sdk.Context, portID, channelID str

previousState := channel.State

k.SetUpgradeErrorReceipt(ctx, portID, channelID, errorReceipt)
channel = k.restoreChannel(ctx, portID, channelID, errorReceipt.Sequence, channel)
channel = k.restoreChannel(ctx, portID, channelID, errorReceipt.Sequence, channel, types.NewUpgradeError(errorReceipt.Sequence, types.ErrInvalidUpgrade))

k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", previousState, "new-state", types.OPEN.String())
emitChannelUpgradeCancelEvent(ctx, portID, channelID, channel, upgrade)
Expand All @@ -634,18 +633,16 @@ func (k Keeper) ChanUpgradeTimeout(
ctx sdk.Context,
portID, channelID string,
counterpartyChannel types.Channel,
prevErrorReceipt *types.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
proofCounterpartyChannel []byte,
proofHeight exported.Height,
) error {
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}

if channel.State != types.INITUPGRADE {
return errorsmod.Wrapf(types.ErrInvalidChannelState, "channel state is not INITUPGRADE (got %s)", channel.State)
if !collections.Contains(channel.State, []types.State{types.STATE_FLUSHING, types.STATE_FLUSHCOMPLETE}) {
return errorsmod.Wrapf(types.ErrInvalidChannelState, "expected one of [%s, %s], got %s", types.STATE_FLUSHING, types.STATE_FLUSHCOMPLETE, channel.State)
}

upgrade, found := k.GetUpgrade(ctx, portID, channelID)
Expand All @@ -668,23 +665,44 @@ func (k Keeper) ChanUpgradeTimeout(
)
}

// 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, abort transaction
proofTimestamp, err := k.connectionKeeper.GetTimestampAtHeight(ctx, connection, proofHeight)
if err != nil {
return err
}

timeout := upgrade.Timeout
proofHeightIsInvalid := timeout.Height.IsZero() || proofHeight.LT(timeout.Height)
proofTimestampIsInvalid := timeout.Timestamp == 0 || proofTimestamp < timeout.Timestamp
if proofHeightIsInvalid && proofTimestampIsInvalid {
return errorsmod.Wrap(types.ErrInvalidUpgradeTimeout, "timeout has not yet passed on counterparty chain")
// 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, abort transaction
timeoutHeight := upgrade.Timeout.Height
timeoutTimeStamp := upgrade.Timeout.Timestamp
if (timeoutHeight.IsZero() || proofHeight.LT(timeoutHeight)) &&
(timeoutTimeStamp == 0 || proofTimestamp < timeoutTimeStamp) {
return errorsmod.Wrap(types.ErrInvalidUpgradeTimeout, "upgrade timeout has not been reached for height or timestamp")
}

// counterparty channel must be proved to still be in OPEN state or FLUSHING state.
if !collections.Contains(counterpartyChannel.State, []types.State{types.OPEN, types.STATE_FLUSHING}) {
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
return errorsmod.Wrapf(types.ErrInvalidCounterparty, "expected one of [%s, %s], got %s", types.OPEN, types.STATE_FLUSHING, counterpartyChannel.State)
}

if counterpartyChannel.State == types.OPEN {
upgradeConnection, found := k.connectionKeeper.GetConnection(ctx, upgrade.Fields.ConnectionHops[0])
if !found {
return errorsmod.Wrap(
connectiontypes.ErrConnectionNotFound,
upgrade.Fields.ConnectionHops[0],
)
}
counterpartyHops := []string{upgradeConnection.GetCounterparty().GetConnectionID()}

upgradeAlreadyComplete := upgrade.Fields.Version == counterpartyChannel.Version && upgrade.Fields.Ordering == counterpartyChannel.Ordering && upgrade.Fields.ConnectionHops[0] == counterpartyHops[0]
if upgradeAlreadyComplete {
// counterparty has already successfully upgraded so we cannot timeout
return errorsmod.Wrap(types.ErrUpgradeTimeoutFailed, "counterparty channel is already upgraded")
}
}

// counterparty channel must be proved to still be in OPEN state or INITUPGRADE state (crossing hellos)
if !collections.Contains(counterpartyChannel.State, []types.State{types.OPEN, types.INITUPGRADE}) {
return errorsmod.Wrapf(types.ErrInvalidChannelState, "expected one of [%s, %s], got %s", types.OPEN, types.INITUPGRADE, counterpartyChannel.State)
if counterpartyChannel.UpgradeSequence < channel.UpgradeSequence {
return errorsmod.Wrapf(types.ErrInvalidUpgradeSequence, "counterparty channel upgrade sequence (%d) must be greater than or equal to current upgrade sequence (%d)", counterpartyChannel.UpgradeSequence, channel.UpgradeSequence)
}

// verify the counterparty channel state
Expand All @@ -699,38 +717,6 @@ func (k Keeper) ChanUpgradeTimeout(
return errorsmod.Wrap(err, "failed to verify counterparty channel state")
}

// Error receipt passed in is either nil or it is a stale error receipt from a previous upgrade
if prevErrorReceipt == nil {
if err := k.connectionKeeper.VerifyChannelUpgradeErrorAbsence(
ctx,
channel.Counterparty.PortId, channel.Counterparty.ChannelId,
connection,
proofErrorReceipt,
proofHeight,
); err != nil {
return errorsmod.Wrap(err, "failed to verify absence of counterparty channel upgrade error receipt")
}

return nil
}
// timeout for this sequence can only succeed if the error receipt written into the error path on the counterparty
// was for a previous sequence by the timeout deadline.
upgradeSequence := channel.UpgradeSequence
if upgradeSequence <= prevErrorReceipt.Sequence {
return errorsmod.Wrapf(types.ErrInvalidUpgradeSequence, "previous counterparty error receipt sequence is greater than or equal to our current upgrade sequence: %d > %d", prevErrorReceipt.Sequence, upgradeSequence)
}

if err := k.connectionKeeper.VerifyChannelUpgradeError(
ctx,
channel.Counterparty.PortId, channel.Counterparty.ChannelId,
connection,
*prevErrorReceipt,
proofErrorReceipt,
proofHeight,
); err != nil {
return errorsmod.Wrap(err, "failed to verify counterparty channel upgrade error receipt")
}

return nil
}

Expand All @@ -753,7 +739,7 @@ func (k Keeper) WriteUpgradeTimeoutChannel(
panic(fmt.Sprintf("could not find existing upgrade when cancelling channel upgrade, channelID: %s, portID: %s", channelID, portID))
}

channel = k.restoreChannel(ctx, portID, channelID, channel.UpgradeSequence, channel)
channel = k.restoreChannel(ctx, portID, channelID, channel.UpgradeSequence, channel, types.NewUpgradeError(channel.UpgradeSequence, types.ErrUpgradeTimeout))

k.Logger(ctx).Info("channel state restored", "port-id", portID, "channel-id", channelID)
emitChannelUpgradeTimeoutEvent(ctx, portID, channelID, channel, upgrade)
Expand Down Expand Up @@ -924,54 +910,47 @@ func (k Keeper) abortUpgrade(ctx sdk.Context, portID, channelID string, err erro
return errorsmod.Wrap(types.ErrInvalidUpgradeError, "cannot abort upgrade handshake with nil error")
}

upgrade, found := k.GetUpgrade(ctx, portID, channelID)
if !found {
return errorsmod.Wrapf(types.ErrUpgradeNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}

channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}

// the channel upgrade sequence has already been updated in ChannelUpgradeTry, so we can pass
// its updated value.
k.restoreChannel(ctx, portID, channelID, channel.UpgradeSequence, channel)

// in the case of application callbacks, the error may not be an upgrade error.
// in this case we need to construct one in order to write the error receipt.
upgradeError, ok := err.(*types.UpgradeError)
if !ok {
upgradeError = types.NewUpgradeError(channel.UpgradeSequence, err)
}

if err := k.WriteErrorReceipt(ctx, portID, channelID, upgrade.Fields, upgradeError); err != nil {
return err
}

// the channel upgrade sequence has already been updated in ChannelUpgradeTry, so we can pass
// its updated value.
k.restoreChannel(ctx, portID, channelID, channel.UpgradeSequence, channel, upgradeError)
return nil
}

// restoreChannel will restore the channel state and flush status to their pre-upgrade state so that upgrade is aborted.
func (k Keeper) restoreChannel(ctx sdk.Context, portID, channelID string, upgradeSequence uint64, channel types.Channel) types.Channel {
func (k Keeper) restoreChannel(ctx sdk.Context, portID, channelID string, upgradeSequence uint64, channel types.Channel, err *types.UpgradeError) types.Channel {
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
channel.State = types.OPEN
channel.UpgradeSequence = upgradeSequence

k.SetChannel(ctx, portID, channelID, channel)

// delete state associated with upgrade which is no longer required.
k.deleteUpgradeInfo(ctx, portID, channelID)

_ = k.WriteErrorReceipt(ctx, portID, channelID, err)

return channel
}

// WriteErrorReceipt will write an error receipt from the provided UpgradeError.
func (k Keeper) WriteErrorReceipt(ctx sdk.Context, portID, channelID string, upgradeFields types.UpgradeFields, upgradeError *types.UpgradeError) error {
func (k Keeper) WriteErrorReceipt(ctx sdk.Context, portID, channelID string, upgradeError *types.UpgradeError) error {
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}

k.SetUpgradeErrorReceipt(ctx, portID, channelID, upgradeError.GetErrorReceipt())
emitErrorReceiptEvent(ctx, portID, channelID, channel, upgradeFields, upgradeError)
emitErrorReceiptEvent(ctx, portID, channelID, channel, upgradeError)
return nil
}
Loading