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

Add ChanUpgradeCancel core handler #3846

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5cd004e
wip: adding restoreChannel and writeErrorReceipt functions
chatton Jun 1, 2023
cbc8e32
merge 04-channel-upgrades
chatton Jun 6, 2023
d01355f
emit error receipt events on abort
chatton Jun 6, 2023
6f7940b
remove unused function
chatton Jun 6, 2023
6d50848
added happy path test for abort
chatton Jun 6, 2023
795cd05
fleshed out TestAbortHandshake
chatton Jun 7, 2023
dfbc5d4
corrected docstring and removed unrequired checks in test
chatton Jun 7, 2023
b2a99ca
merge feature branch
chatton Jun 7, 2023
d429514
chore: added implementation of WriteUpgradeCancelChannel
chatton Jun 7, 2023
4330c51
Merge branch '04-channel-upgrades' into cian/issue#3649-implement-res…
chatton Jun 7, 2023
c1e05b8
fix linting
chatton Jun 7, 2023
619381b
Merge branch 'cian/issue#3649-implement-restorechannel' of https://gi…
chatton Jun 7, 2023
91605c1
addressing PR feedback
chatton Jun 7, 2023
d050ee8
merge with restore branch
chatton Jun 7, 2023
eaa2b91
addressing PR feedback
chatton Jun 7, 2023
d6bd3bf
merge 04-channel-upgrades
chatton Jun 7, 2023
cfe59ae
Merge branch 'cian/issue#3649-implement-restorechannel' into cian/iss…
chatton Jun 7, 2023
7d2c3d4
chore: adding implementation of ChanUpgradeCancel
chatton Jun 7, 2023
099b147
Merge branch '04-channel-upgrades' into cian/issue#3649-implement-res…
chatton Jun 7, 2023
759474f
added scaffolding for TestChanUpgradeCancel
chatton Jun 7, 2023
3916332
addressing PR feedback
chatton Jun 12, 2023
700acba
merge 04-channel-upgrades
chatton Jun 12, 2023
6b2ce2e
Merge branch 'cian/issue#3649-implement-restorechannel' into cian/iss…
chatton Jun 12, 2023
811c418
addressing PR feedback
chatton Jun 12, 2023
0f0305f
Merge branch '04-channel-upgrades' into cian/issue#3752-implement-wri…
chatton Jun 12, 2023
d6bd69c
merge 04-channel-upgrades
chatton Jun 14, 2023
51e9070
fix merge conflicts
chatton Jun 14, 2023
3e0bf6e
ran linter
chatton Jun 14, 2023
4e12bc0
merge 04-channel-upgrades
chatton Jun 14, 2023
df123ba
merge 04-channel-upgrades
chatton Jun 14, 2023
098d0a3
happy path test
chatton Jun 14, 2023
853eae2
adding tests for ChanUpgradeCancel
chatton Jun 14, 2023
8ced59b
chore: merge 04-channel-upgrades
chatton Jun 14, 2023
bf10856
addresing PR feedback
chatton Jun 16, 2023
0e34c52
Merge branch '04-channel-upgrades' into cian/issue#3753-implement-cha…
chatton Jun 16, 2023
8405547
Merge branch 'cian/issue#3753-implement-chanupgradecancel-handler-for…
chatton Jun 16, 2023
2f540ce
corrected assertion arguments
chatton Jun 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions modules/core/04-channel/keeper/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,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 {
charleenfei marked this conversation as resolved.
Show resolved Hide resolved
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])
charleenfei marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return errorsmod.Wrap(err, "failed to retrieve connection using the channel connection hops")
chatton marked this conversation as resolved.
Show resolved Hide resolved
}

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 {
chatton marked this conversation as resolved.
Show resolved Hide resolved
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should say:

error receipt sequence must be greater than or equal to current sequence

And maybe we should add the sequence values?

}

channel.UpgradeSequence = errorReceipt.Sequence + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking about this more, should we actually update the upgrade sequences in the Write functions also? this is the one mutation we make in these handler functions. cc @damiannolan @colin-axner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this did feel a little off, when I was first re-reading what I had written, I was confused for a minute as the channel state didn't change. I think I would be in favour of maybe either returning the desired sequence or just doing the receipt + 1 in the write fn.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed today, I think we can remove the +1 in a followup pr since channel upgrade initialization should bump the upgrade sequence

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 @@ -245,8 +288,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())
Comment on lines +291 to +295
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated quick fix to make it consistent with the other write functions.

emitChannelUpgradeCancelEvent(ctx, portID, channelID, channel, upgrade)
}

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 @@ -876,3 +877,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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, should be:

counterparty upgrade

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am not sure this line is necessary bc the UpdateClient function below commits a block to chainB already

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very good catch, this makes total sense to me, but the test fails when this commit block is removed.

I don't understand this behaviour

do you or maybe @colin-axner understand why this would be the case ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context: #3900

When CommitBlock is called, it commits the current header set within the test chain. This is created on a call to NextBlock. The state changes are not being reflected to the AppHash being committed. So when CommitBlock is being called the first time, it doesn't include the provable state changes (which is why calling it once results in an invalid proof). Committing the current block should reflect the current state changes. We should update it. I think the difficulty is getting the working app hash as this is currently private in the sdk

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testing pkg has some cleanup in its future. I'd like to simplify the API a bit (there shouldn't be CommitBlock and NextBlock)

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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed


// cause the upgrade to fail on chain b so an error receipt is written.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slick

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damiannolan gets the credit for this one ❤️

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)
}
})
}
}
Loading