Skip to content

Commit

Permalink
Merge branch '04-channel-upgrades' into charly/#3749-msg-server-timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
charleenfei committed Jun 17, 2023
2 parents 9ee7758 + e201873 commit 55bc80f
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types"
host "github.com/cosmos/ibc-go/v7/modules/core/24-host"
"github.com/cosmos/ibc-go/v7/modules/core/exported"
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
)

Expand Down Expand Up @@ -259,7 +258,8 @@ func (im IBCMiddleware) OnChanUpgradeTimeout(
prevErrorReceipt channeltypes.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height) error {
proofHeight ibcexported.Height,
) error {
return im.app.OnChanUpgradeTimeout(ctx, portID, channelID, counterpartyChannel, prevErrorReceipt, proofCounterpartyChannel, proofErrorReceipt, proofHeight)
}

Expand Down
3 changes: 2 additions & 1 deletion modules/apps/27-interchain-accounts/host/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func (im IBCModule) OnChanUpgradeTimeout(
prevErrorReceipt channeltypes.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height) error {
proofHeight ibcexported.Height,
) error {
return nil
}
3 changes: 2 additions & 1 deletion modules/apps/29-fee/ibc_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ func (im IBCMiddleware) OnChanUpgradeTimeout(
prevErrorReceipt channeltypes.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height) error {
proofHeight exported.Height,
) error {
return im.app.OnChanUpgradeTimeout(ctx, portID, channelID, counterpartyChannel, prevErrorReceipt, proofCounterpartyChannel, proofErrorReceipt, proofHeight)
}

Expand Down
3 changes: 2 additions & 1 deletion modules/apps/transfer/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ func (im IBCModule) OnChanUpgradeTimeout(
prevErrorReceipt channeltypes.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height) error {
proofHeight ibcexported.Height,
) error {
return nil
}
22 changes: 22 additions & 0 deletions modules/core/04-channel/keeper/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,28 @@ func emitChannelUpgradeOpenEvent(ctx sdk.Context, portID string, channelID strin
})
}

// emitChannelUpgradeTimeoutEvent emits an upgrade timeout event.
func emitChannelUpgradeTimeoutEvent(ctx sdk.Context, portID string, channelID string, currentChannel types.Channel, upgrade types.Upgrade) {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeChannelUpgradeTimeout,
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, upgrade.Fields.ConnectionHops[0]),
sdk.NewAttribute(types.AttributeKeyUpgradeVersion, upgrade.Fields.Version),
sdk.NewAttribute(types.AttributeKeyUpgradeOrdering, upgrade.Fields.Ordering.String()),
sdk.NewAttribute(types.AttributeKeyUpgradeTimeout, upgrade.Timeout.String()),
sdk.NewAttribute(types.AttributeKeyUpgradeSequence, fmt.Sprintf("%d", currentChannel.UpgradeSequence)),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
})
}

// emitErrorReceiptEvent emits an error receipt event
func emitErrorReceiptEvent(ctx sdk.Context, portID string, channelID string, currentChannel types.Channel, upgrade types.Upgrade, err error) {
ctx.EventManager().EmitEvents(sdk.Events{
Expand Down
5 changes: 5 additions & 0 deletions modules/core/04-channel/keeper/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ func (k Keeper) ValidateUpgradeFields(ctx sdk.Context, proposedUpgrade types.Upg
func (k Keeper) WriteUpgradeOpenChannel(ctx sdk.Context, portID, channelID string) {
k.writeUpgradeOpenChannel(ctx, portID, channelID)
}

// WriteUpgradeTimeoutChannel is a wrapper around writeUpgradeTimeoutChannel to allow the function to be directly called in tests.
func (k Keeper) WriteUpgradeTimeoutChannel(ctx sdk.Context, portID, channelID string) error {
return k.writeUpgradeTimeoutChannel(ctx, portID, channelID)
}
89 changes: 89 additions & 0 deletions modules/core/04-channel/keeper/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,49 @@ func (k Keeper) WriteUpgradeAckChannel(
emitChannelUpgradeAckEvent(ctx, portID, channelID, channel, proposedUpgrade)
}

// ChanUpgradeCancel is called by a module to cancel a channel upgrade that is in progress.
func (k Keeper) ChanUpgradeCancel(ctx sdk.Context, portID, channelID string, errorReceipt types.ErrorReceipt, errorReceiptProof []byte, proofHeight clienttypes.Height) error {
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}

// the channel state must be in INITUPGRADE or TRYUPGRADE
if !collections.Contains(channel.State, []types.State{types.INITUPGRADE, types.TRYUPGRADE}) {
return errorsmod.Wrapf(types.ErrInvalidChannelState, "expected one of [%s, %s], got %s", types.INITUPGRADE, types.TRYUPGRADE, channel.State)
}

// get underlying connection for proof verification
connection, err := k.GetConnection(ctx, channel.ConnectionHops[0])
if err != nil {
return errorsmod.Wrap(err, "failed to retrieve connection using the channel connection hops")
}

if connection.GetState() != int32(connectiontypes.OPEN) {
return errorsmod.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.State(connection.GetState()).String(),
)
}

if err := k.connectionKeeper.VerifyChannelUpgradeError(ctx, portID, channelID, connection, errorReceipt, errorReceiptProof, proofHeight); err != nil {
return errorsmod.Wrap(err, "failed to verify counterparty error receipt")
}

// If counterparty sequence is less than the current sequence, abort the transaction since this error receipt is from a previous upgrade.
// Otherwise, set our upgrade sequence to the counterparty's error sequence + 1 so that both sides start with a fresh sequence.
currentSequence := channel.UpgradeSequence
counterpartySequence := errorReceipt.Sequence
if counterpartySequence < currentSequence {
return errorsmod.Wrap(types.ErrInvalidUpgradeSequence, "error sequence must be less than current sequence")
}

channel.UpgradeSequence = errorReceipt.Sequence + 1
k.SetChannel(ctx, portID, channelID, channel)

return nil
}

// WriteUpgradeCancelChannel writes a channel which has canceled the upgrade process.Auxiliary upgrade state is
// also deleted.
func (k Keeper) WriteUpgradeCancelChannel(ctx sdk.Context, portID, channelID string) {
Expand All @@ -344,8 +387,11 @@ func (k Keeper) WriteUpgradeCancelChannel(ctx sdk.Context, portID, channelID str
panic(fmt.Sprintf("could not find existing channel when updating channel state, channelID: %s, portID: %s", channelID, portID))
}

previousState := channel.State

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

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 Down Expand Up @@ -412,6 +458,49 @@ func (k Keeper) ChanUpgradeAck(
return nil
}

// ChanUpgradeTimeout times out an outstanding upgrade.
// This should be used by the initialising chain when the counterparty chain has not responded to an upgrade proposal within the specified timeout period.
func (k Keeper) ChanUpgradeTimeout(
ctx sdk.Context,
portID, channelID string,
counterpartyChannel types.Channel,
prevErrorReceipt types.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height,
) error {
return nil
}

// writeUpgradeTimeoutChannel restores the channel state of an initialising chain in the event that the counterparty chain has passed the timeout set in ChanUpgradeInit to the state before the upgrade was proposed.
// Auxiliary upgrade state is also deleted.
// An event is emitted for the handshake step.
func (k Keeper) writeUpgradeTimeoutChannel(
ctx sdk.Context,
portID, channelID string,
) error {
defer telemetry.IncrCounter(1, "ibc", "channel", "upgrade-timeout")

channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
panic(fmt.Sprintf("could not find existing channel when updating channel state in successful ChanUpgradeTimeout step, channelID: %s, portID: %s", channelID, portID))
}

upgrade, found := k.GetUpgrade(ctx, portID, channelID)
if !found {
panic(fmt.Sprintf("could not find existing upgrade when cancelling channel upgrade, channelID: %s, portID: %s", channelID, portID))
}

if err := k.AbortUpgrade(ctx, portID, channelID, types.NewUpgradeError(channel.UpgradeSequence, types.ErrUpgradeTimeout)); err != nil {
return errorsmod.Wrapf(types.ErrUpgradeRestoreFailed, "err: %v", err)
}

k.Logger(ctx).Info("channel state restored", "port-id", portID, "channel-id", channelID)
emitChannelUpgradeTimeoutEvent(ctx, portID, channelID, channel, upgrade)

return nil
}

// startFlushUpgradeHandshake will verify the counterparty proposed upgrade and the current channel state.
// Once the counterparty information has been verified, it will be validated against the self proposed upgrade.
// If any of the proposed upgrade fields are incompatible, an upgrade error will be returned resulting in an
Expand Down
114 changes: 114 additions & 0 deletions modules/core/04-channel/keeper/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types"
Expand Down Expand Up @@ -1044,3 +1045,116 @@ func (suite *KeeperTestSuite) TestAbortHandshake() {
})
}
}

func (suite *KeeperTestSuite) TestChanUpgradeCancel() {
var (
path *ibctesting.Path
errorReceipt types.ErrorReceipt
errorReceiptProof []byte
proofHeight clienttypes.Height
)
tests := []struct {
name string
malleate func()
expError error
}{
{
name: "success",
malleate: func() {},
expError: nil,
},
{
name: "invalid channel state",
malleate: func() {
channel := path.EndpointA.GetChannel()
channel.State = types.INIT
path.EndpointA.SetChannel(channel)
},
expError: types.ErrInvalidChannelState,
},
{
name: "channel not found",
malleate: func() {
path.EndpointA.Chain.DeleteKey(host.ChannelKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID))
},
expError: types.ErrChannelNotFound,
},
{
name: "connection not found",
malleate: func() {
channel := path.EndpointA.GetChannel()
channel.ConnectionHops = []string{"connection-100"}
path.EndpointA.SetChannel(channel)
},
expError: connectiontypes.ErrConnectionNotFound,
},
{
name: "counter partyupgrade sequence less than current sequence",
malleate: func() {
var ok bool
errorReceipt, ok = suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.GetUpgradeErrorReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
suite.Require().True(ok)

// the channel sequence will be 1
errorReceipt.Sequence = 0

suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.SetUpgradeErrorReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, errorReceipt)

suite.coordinator.CommitBlock(suite.chainB)
suite.Require().NoError(path.EndpointA.UpdateClient())

upgradeErrorReceiptKey := host.ChannelUpgradeErrorKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
errorReceiptProof, proofHeight = suite.chainB.QueryProof(upgradeErrorReceiptKey)
},
expError: types.ErrInvalidUpgradeSequence,
},
}

for _, tc := range tests {
tc := tc
suite.Run(tc.name, func() {
suite.SetupTest()

path = ibctesting.NewPath(suite.chainA, suite.chainB)
suite.coordinator.Setup(path)

path.EndpointA.ChannelConfig.ProposedUpgrade.Fields.Version = mock.UpgradeVersion
path.EndpointB.ChannelConfig.ProposedUpgrade.Fields.Version = mock.UpgradeVersion

suite.Require().NoError(path.EndpointA.ChanUpgradeInit())

suite.Require().NoError(path.EndpointB.UpdateClient())

// cause the upgrade to fail on chain b so an error receipt is written.
suite.chainB.GetSimApp().IBCMockModule.IBCApp.OnChanUpgradeTry = func(
ctx sdk.Context, portID, channelID string, order types.Order, connectionHops []string, counterpartyVersion string,
) (string, error) {
return "", fmt.Errorf("mock app callback failed")
}

suite.Require().NoError(path.EndpointB.ChanUpgradeTry())

suite.Require().NoError(path.EndpointA.UpdateClient())

upgradeErrorReceiptKey := host.ChannelUpgradeErrorKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
errorReceiptProof, proofHeight = suite.chainB.QueryProof(upgradeErrorReceiptKey)

var ok bool
errorReceipt, ok = suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.GetUpgradeErrorReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
suite.Require().True(ok)

tc.malleate()

err := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.ChanUpgradeCancel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt, errorReceiptProof, proofHeight)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err)
channel := path.EndpointA.GetChannel()
suite.Require().Equal(errorReceipt.Sequence+1, channel.UpgradeSequence, "upgrade sequence should be incremented")
} else {
suite.Require().ErrorIs(err, tc.expError)
}
})
}
}
1 change: 0 additions & 1 deletion modules/core/04-channel/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,4 @@ var (
ErrInvalidFlushStatus = errorsmod.Register(SubModuleName, 33, "invalid flush status")
ErrUpgradeRestoreFailed = errorsmod.Register(SubModuleName, 34, "restore failed")
ErrUpgradeTimeout = errorsmod.Register(SubModuleName, 35, "upgrade timed-out")
ErrInvalidUpgradeTimeout = errorsmod.Register(SubModuleName, 36, "upgrade timeout is invalid")
)
26 changes: 14 additions & 12 deletions modules/core/04-channel/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,24 @@ const (
AttributeKeyUpgradeOrdering = "upgrade_ordering"
AttributeKeyUpgradeErrorReceipt = "upgrade_error_receipt"
AttributeKeyUpgradeChannelFlushStatus = "channel_flush_status"
AttributeKeyUpgradeTimeout = "upgrade_timeout"
)

// IBC channel events vars
var (
EventTypeChannelOpenInit = "channel_open_init"
EventTypeChannelOpenTry = "channel_open_try"
EventTypeChannelOpenAck = "channel_open_ack"
EventTypeChannelOpenConfirm = "channel_open_confirm"
EventTypeChannelCloseInit = "channel_close_init"
EventTypeChannelCloseConfirm = "channel_close_confirm"
EventTypeChannelClosed = "channel_close"
EventTypeChannelUpgradeInit = "channel_upgrade_init"
EventTypeChannelUpgradeTry = "channel_upgrade_try"
EventTypeChannelUpgradeAck = "channel_upgrade_ack"
EventTypeChannelUpgradeOpen = "channel_upgrade_open"
EventTypeChannelUpgradeCancel = "channel_upgrade_cancelled"
EventTypeChannelOpenInit = "channel_open_init"
EventTypeChannelOpenTry = "channel_open_try"
EventTypeChannelOpenAck = "channel_open_ack"
EventTypeChannelOpenConfirm = "channel_open_confirm"
EventTypeChannelCloseInit = "channel_close_init"
EventTypeChannelCloseConfirm = "channel_close_confirm"
EventTypeChannelClosed = "channel_close"
EventTypeChannelUpgradeInit = "channel_upgrade_init"
EventTypeChannelUpgradeTry = "channel_upgrade_try"
EventTypeChannelUpgradeAck = "channel_upgrade_ack"
EventTypeChannelUpgradeOpen = "channel_upgrade_open"
EventTypeChannelUpgradeTimeout = "channel_upgrade_timeout"
EventTypeChannelUpgradeCancel = "channel_upgrade_cancelled"

AttributeValueCategory = fmt.Sprintf("%s_%s", ibcexported.ModuleName, SubModuleName)
)
4 changes: 2 additions & 2 deletions modules/core/05-port/types/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ type UpgradableModule interface {
OnChanUpgradeTimeout(
ctx sdk.Context,
portID, channelID string,
counterpartyChannel types.Channel,
prevErrorReceipt types.ErrorReceipt,
counterpartyChannel channeltypes.Channel,
prevErrorReceipt channeltypes.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height,
Expand Down
5 changes: 3 additions & 2 deletions testing/mock/ibc_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,12 @@ type IBCApp struct {
portID,
channelID string,
) error

OnChanUpgradeTimeout func(
ctx sdk.Context,
portID, channelID string,
counterpartyChannel types.Channel,
prevErrorReceipt types.ErrorReceipt,
counterpartyChannel channeltypes.Channel,
prevErrorReceipt channeltypes.ErrorReceipt,
proofCounterpartyChannel,
proofErrorReceipt []byte,
proofHeight exported.Height,
Expand Down
Loading

0 comments on commit 55bc80f

Please sign in to comment.