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

fix: set upgrade sequence correctly in channel recovery #5374

16 changes: 6 additions & 10 deletions modules/core/04-channel/keeper/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,10 @@ func (k Keeper) WriteUpgradeOpenChannel(ctx sdk.Context, portID, channelID strin
return channel
}

// 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, isAuthority bool) error {
// ChanUpgradeCancel is called by the msg server to prove that an error receipt was written on the counterparty
// which constitutes a valid situation where the upgrade should be cancelled. An error is returned if sufficient evidence
// for cancelling the upgrade has not been provided.
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)
Expand All @@ -567,12 +569,6 @@ func (k Keeper) ChanUpgradeCancel(ctx sdk.Context, portID, channelID string, err
return errorsmod.Wrapf(types.ErrUpgradeNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}

// if the msgSender is authorized to make and cancel upgrades AND the current channel has not already reached FLUSHCOMPLETE
// then we can restore immediately without any additional checks
if isAuthority && channel.State != types.FLUSHCOMPLETE {
return nil
}
// otherwise, we can only cancel if the counterparty wrote an error receipt during the upgrade handshake
// an error receipt proof must be provided.
if len(errorReceiptProof) == 0 {
return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty error receipt proof unless the sender is authorized to cancel upgrades AND channel is not in FLUSHCOMPLETE")
Expand Down Expand Up @@ -613,7 +609,7 @@ func (k Keeper) ChanUpgradeCancel(ctx sdk.Context, portID, channelID string, err

// 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, errorReceipt types.ErrorReceipt) {
func (k Keeper) WriteUpgradeCancelChannel(ctx sdk.Context, portID, channelID string, sequence uint64) {
defer telemetry.IncrCounter(1, "ibc", "channel", "upgrade-cancel")

channel, found := k.GetChannel(ctx, portID, channelID)
Expand All @@ -628,7 +624,7 @@ func (k Keeper) WriteUpgradeCancelChannel(ctx sdk.Context, portID, channelID str

previousState := channel.State

channel = k.restoreChannel(ctx, portID, channelID, errorReceipt.Sequence, channel, types.NewUpgradeError(errorReceipt.Sequence, types.ErrInvalidUpgrade))
channel = k.restoreChannel(ctx, portID, channelID, sequence, channel, types.NewUpgradeError(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 Down
67 changes: 11 additions & 56 deletions modules/core/04-channel/keeper/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1197,7 +1197,6 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {
errorReceipt types.ErrorReceipt
errorReceiptProof []byte
proofHeight clienttypes.Height
isAuthority bool
)

tests := []struct {
Expand All @@ -1220,44 +1219,15 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {
expError: nil,
},
{
name: "sender is authority, upgrade can be cancelled in FLUSHING state even with invalid error receipt upgrade sequence",
name: "upgrade cannot be cancelled in FLUSHCOMPLETE with invalid error receipt",
malleate: func() {
isAuthority = true

channel := path.EndpointA.GetChannel()
channel.State = types.FLUSHING
path.EndpointA.SetChannel(channel)

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)

errorReceipt.Sequence = path.EndpointA.GetChannel().UpgradeSequence - 1

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: nil,
},
{
name: "sender is authority, upgrade cannot be cancelled in FLUSHCOMPLETE with invalid error receipt",
malleate: func() {
isAuthority = true
errorReceiptProof = nil
},
expError: commitmenttypes.ErrInvalidProof,
},
{
name: "sender is authority, upgrade cannot be cancalled in FLUSHCOMPLETE with error receipt sequence less than channel upgrade sequence",
name: "upgrade cannot be cancelled in FLUSHCOMPLETE with error receipt sequence less than channel upgrade sequence",
malleate: func() {
isAuthority = true

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)
Expand Down Expand Up @@ -1290,7 +1260,7 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {
expError: types.ErrUpgradeNotFound,
},
{
name: "sender is not authority, error receipt sequence less than channel upgrade sequence",
name: "error receipt sequence less than channel upgrade sequence",
malleate: func() {
var ok bool
errorReceipt, ok = suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.GetUpgradeErrorReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
Expand Down Expand Up @@ -1319,7 +1289,7 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {
expError: connectiontypes.ErrConnectionNotFound,
},
{
name: "sender is authority, channel is in flush complete, error verification failed",
name: "channel is in flush complete, error verification failed",
malleate: func() {
var ok bool
errorReceipt, ok = suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.GetUpgradeErrorReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
Expand All @@ -1329,13 +1299,11 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {

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

isAuthority = true
},
expError: commitmenttypes.ErrInvalidProof,
},
{
name: "sender is not authority, error verification failed",
name: "error verification failed",
malleate: func() {
var ok bool
errorReceipt, ok = suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.GetUpgradeErrorReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
Expand All @@ -1348,31 +1316,18 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {
expError: commitmenttypes.ErrInvalidProof,
},
{
name: "sender is not authority, error verification failed with empty proof",
name: "error verification failed with empty proof",
malleate: func() {
errorReceiptProof = nil
},
expError: commitmenttypes.ErrInvalidProof,
},
{
name: "sender is authority, channel is flushing, cancel succeeds with empty proof",
malleate: func() {
isAuthority = true
errorReceiptProof = nil
channel := path.EndpointA.GetChannel()
channel.State = types.FLUSHING
path.EndpointA.SetChannel(channel)
},
expError: nil,
},
}

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

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

Expand Down Expand Up @@ -1413,7 +1368,7 @@ func (suite *KeeperTestSuite) TestChanUpgradeCancel() {

tc.malleate()

err := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.ChanUpgradeCancel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt, errorReceiptProof, proofHeight, isAuthority)
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 {
Expand Down Expand Up @@ -1466,11 +1421,11 @@ func (suite *KeeperTestSuite) TestChanUpgrade_UpgradeSucceeds_AfterCancel() {
upgradeErrorReceiptKey := host.ChannelUpgradeErrorKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
errorReceiptProof, proofHeight := suite.chainB.QueryProof(upgradeErrorReceiptKey)

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

// need to explicitly call WriteUpgradeOpenChannel as this usually would happen in the msg server layer.
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.WriteUpgradeCancelChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt)
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.WriteUpgradeCancelChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt.Sequence)

channel := path.EndpointA.GetChannel()
suite.Require().Equal(types.OPEN, channel.State)
Expand Down Expand Up @@ -1565,10 +1520,10 @@ func (suite *KeeperTestSuite) TestWriteUpgradeCancelChannel() {

if tc.expPanic {
suite.Require().Panics(func() {
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.WriteUpgradeCancelChannel(ctx, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt)
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.WriteUpgradeCancelChannel(ctx, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt.Sequence)
})
} else {
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.WriteUpgradeCancelChannel(ctx, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt)
suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.WriteUpgradeCancelChannel(ctx, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, errorReceipt.Sequence)

channel = path.EndpointA.GetChannel()

Expand Down
22 changes: 19 additions & 3 deletions modules/core/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,6 @@ func (k Keeper) ChannelUpgradeTimeout(goCtx context.Context, msg *channeltypes.M
// ChannelUpgradeCancel defines a rpc handler method for MsgChannelUpgradeCancel.
func (k Keeper) ChannelUpgradeCancel(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeCancel) (*channeltypes.MsgChannelUpgradeCancelResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId)
if err != nil {
ctx.Logger().Error("channel upgrade cancel failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id"))
Expand All @@ -1063,13 +1062,30 @@ func (k Keeper) ChannelUpgradeCancel(goCtx context.Context, msg *channeltypes.Ms
return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module)
}

channel, found := k.ChannelKeeper.GetChannel(ctx, msg.PortId, msg.ChannelId)
if !found {
return nil, errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId)
}

// if the msgSender is authorized to make and cancel upgrades AND the current channel has not already reached FLUSHCOMPLETE
// then we can restore immediately without any additional checks
isAuthority := k.GetAuthority() == msg.Signer
if err := k.ChannelKeeper.ChanUpgradeCancel(ctx, msg.PortId, msg.ChannelId, msg.ErrorReceipt, msg.ProofErrorReceipt, msg.ProofHeight, isAuthority); err != nil {
if isAuthority && channel.State != channeltypes.FLUSHCOMPLETE {
k.ChannelKeeper.WriteUpgradeCancelChannel(ctx, msg.PortId, msg.ChannelId, channel.UpgradeSequence)

cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId)

ctx.Logger().Info("channel upgrade cancel succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId)

return &channeltypes.MsgChannelUpgradeCancelResponse{}, nil
}

if err := k.ChannelKeeper.ChanUpgradeCancel(ctx, msg.PortId, msg.ChannelId, msg.ErrorReceipt, msg.ProofErrorReceipt, msg.ProofHeight); err != nil {
ctx.Logger().Error("channel upgrade cancel failed", "port-id", msg.PortId, "error", err.Error())
return nil, errorsmod.Wrap(err, "channel upgrade cancel failed")
}

k.ChannelKeeper.WriteUpgradeCancelChannel(ctx, msg.PortId, msg.ChannelId, msg.ErrorReceipt)
k.ChannelKeeper.WriteUpgradeCancelChannel(ctx, msg.PortId, msg.ChannelId, msg.ErrorReceipt.Sequence)

cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId)

Expand Down
88 changes: 87 additions & 1 deletion modules/core/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1518,7 +1518,7 @@ func (suite *KeeperTestSuite) TestChannelUpgradeCancel() {
expResult func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error)
}{
{
"success",
"success: keeper is not authority, valid error receipt so channnel changed to match error receipt seq",
func() {},
func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error) {
suite.Require().NoError(err)
Expand All @@ -1531,6 +1531,92 @@ func (suite *KeeperTestSuite) TestChannelUpgradeCancel() {
suite.Require().Equal(uint64(2), channel.UpgradeSequence)
},
},
{
"success: keeper is authority & channel state in FLUSHING, so error receipt is ignored and channel is restored to initial upgrade sequence",
charleenfei marked this conversation as resolved.
Show resolved Hide resolved
func() {
msg.Signer = suite.chainA.App.GetIBCKeeper().GetAuthority()

channel := path.EndpointA.GetChannel()
channel.State = channeltypes.FLUSHING
channel.UpgradeSequence = uint64(3)
path.EndpointA.SetChannel(channel)
},
func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error) {
suite.Require().NoError(err)
suite.Require().NotNil(res)

channel := path.EndpointA.GetChannel()
// Channel state should be reverted back to open.
suite.Require().Equal(channeltypes.OPEN, channel.State)
// Upgrade sequence should be changed to match initial upgrade sequence.
suite.Require().Equal(uint64(3), channel.UpgradeSequence)
},
},
{
"success: keeper is authority & channel state in FLUSHING, can be cancelled even with invalid error receipt",
func() {
msg.Signer = suite.chainA.App.GetIBCKeeper().GetAuthority()
msg.ProofErrorReceipt = []byte("invalid proof")

channel := path.EndpointA.GetChannel()
channel.State = channeltypes.FLUSHING
channel.UpgradeSequence = uint64(1)
path.EndpointA.SetChannel(channel)
},
func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error) {
suite.Require().NoError(err)
suite.Require().NotNil(res)

channel := path.EndpointA.GetChannel()
// Channel state should be reverted back to open.
suite.Require().Equal(channeltypes.OPEN, channel.State)
// Upgrade sequence should be changed to match initial upgrade sequence.
suite.Require().Equal(uint64(1), channel.UpgradeSequence)
},
},
{
"success: keeper is authority & channel state in FLUSHING, can be cancelled even with empty error receipt",
func() {
msg.Signer = suite.chainA.App.GetIBCKeeper().GetAuthority()
msg.ProofErrorReceipt = nil

channel := path.EndpointA.GetChannel()
channel.State = channeltypes.FLUSHING
channel.UpgradeSequence = uint64(1)
path.EndpointA.SetChannel(channel)
},
func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error) {
suite.Require().NoError(err)
suite.Require().NotNil(res)

channel := path.EndpointA.GetChannel()
// Channel state should be reverted back to open.
suite.Require().Equal(channeltypes.OPEN, channel.State)
// Upgrade sequence should be changed to match initial upgrade sequence.
suite.Require().Equal(uint64(1), channel.UpgradeSequence)
},
},
{
"success: keeper is authority but channel state in FLUSHCOMPLETE, requires valid error receipt",
func() {
msg.Signer = suite.chainA.App.GetIBCKeeper().GetAuthority()

channel := path.EndpointA.GetChannel()
channel.State = channeltypes.FLUSHCOMPLETE
channel.UpgradeSequence = uint64(1)
path.EndpointA.SetChannel(channel)
},
func(res *channeltypes.MsgChannelUpgradeCancelResponse, err error) {
suite.Require().NoError(err)
suite.Require().NotNil(res)

channel := path.EndpointA.GetChannel()
// Channel state should be reverted back to open.
suite.Require().Equal(channeltypes.OPEN, channel.State)
// Upgrade sequence should be changed to match error receipt sequence.
suite.Require().Equal(uint64(2), channel.UpgradeSequence)
},
},
{
"capability not found",
func() {
Expand Down
Loading