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

feat: adding chanUpgradeAck handler to 04-channel #3828

Merged
merged 19 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1293330
adding boilerplate skeleton for chanUpgradeAck handler
damiannolan May 31, 2023
c4dce22
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 5, 2023
a8c6882
updating msg servers args
damiannolan Jun 5, 2023
c17c8ed
adding test scaffolding and syncing latest changes of feat branch
damiannolan Jun 5, 2023
152a873
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 5, 2023
fbacd83
configure both proposed upgrades to use mock.UpgradeVersion
damiannolan Jun 6, 2023
dfaf589
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 7, 2023
2834124
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 12, 2023
f92edcb
updating chanUpgradeAck test cases
damiannolan Jun 12, 2023
0ae120f
updating var naming for consistency, adding additional testcases
damiannolan Jun 12, 2023
73e9352
rm msg server implementation
damiannolan Jun 12, 2023
2b8b3fe
adding invalid flush status err and rm lint ignore comment
damiannolan Jun 13, 2023
e53d1aa
adding test helpers to endpoint for get/set channel upgrade
damiannolan Jun 13, 2023
f9c2167
lint it
damiannolan Jun 13, 2023
18638ac
addressing PR review
damiannolan Jun 13, 2023
a11593d
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 13, 2023
0c45acd
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 13, 2023
1622f1d
Update modules/core/04-channel/keeper/upgrade.go
damiannolan Jun 13, 2023
e949908
Merge branch '04-channel-upgrades' into damian/1620-chan-upgrade-ack
damiannolan Jun 14, 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
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ linters-settings:
allow-leading-space: true
require-explanation: false
require-specific: false
revive:
rules:
- name: if-return
disabled: true
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
65 changes: 62 additions & 3 deletions modules/core/04-channel/keeper/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (k Keeper) WriteUpgradeTryChannel(ctx sdk.Context, portID, channelID string
previousState := channel.State
channel.State = types.TRYUPGRADE
// TODO: determine flush status
// channel.FlushStatus = flushStatus
channel.FlushStatus = types.FLUSHING
damiannolan marked this conversation as resolved.
Show resolved Hide resolved

upgrade.Fields.Version = upgradeVersion

Expand Down Expand Up @@ -230,12 +230,71 @@ func (k Keeper) WriteUpgradeAckChannel(
emitChannelUpgradeAckEvent(ctx, portID, channelID, channel, proposedUpgrade)
}

// ChanUpgradeAck is called by a module to accept the ACKUPGRADE handshake step of the channel upgrade protocol.
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
// This method should only be called by the IBC core msg server.
func (k Keeper) ChanUpgradeAck(
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
ctx sdk.Context,
portID,
channelID string,
counterpartyFlushStatus types.FlushStatus,
counterpartyUpgrade types.Upgrade,
proofChannel,
proofUpgrade []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)
}

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

if !collections.Contains(counterpartyFlushStatus, []types.FlushStatus{types.FLUSHING, types.FLUSHCOMPLETE}) {
return errorsmod.Wrapf(types.ErrInvalidFlushStatus, "expected one of [%s, %s], got %s", types.FLUSHING, types.FLUSHCOMPLETE, counterpartyFlushStatus)
}

connection, err := k.GetConnection(ctx, channel.ConnectionHops[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we do this instead?

Suggested change
connection, err := k.GetConnection(ctx, channel.ConnectionHops[0])
connection, err := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])

I believe this is the way we retrieve the connection from the channel keeper in the rest of the 04-channel codebase. If I'm not mistaken, the GetConnection function in channel keeper is only used in ICA, since it is not possible to access the connection keeper from there.

Copy link
Member Author

Choose a reason for hiding this comment

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

seems like all of the channel upgradability code is currently using k.GetConnection(), maybe we could address all together and choose one for consistency in a follow up PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

opened #3840 as I'd like to get this merged

if err != nil {
return errorsmod.Wrap(err, "failed to retrieve connection using the channel connection hops")
}

counterpartyHops := []string{connection.GetCounterparty().GetConnectionID()}
counterpartyChannel := types.Channel{
State: types.TRYUPGRADE,
Ordering: channel.Ordering,
ConnectionHops: counterpartyHops,
Counterparty: types.NewCounterparty(portID, channelID),
charleenfei marked this conversation as resolved.
Show resolved Hide resolved
Version: channel.Version,
UpgradeSequence: channel.UpgradeSequence,
FlushStatus: counterpartyFlushStatus, // provided by the relayer
}

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

if err := k.startFlushUpgradeHandshake(ctx, portID, channelID, upgrade.Fields, counterpartyChannel, counterpartyUpgrade,
proofChannel, proofUpgrade, proofHeight); err != nil {
return err
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
}

// in the crossing hellos case, the versions returned by both on TRY must be the same
if channel.State == types.TRYUPGRADE {
if upgrade.Fields.Version != counterpartyUpgrade.Fields.Version {
return types.NewUpgradeError(channel.UpgradeSequence, errorsmod.Wrap(types.ErrIncompatibleCounterpartyUpgrade, "both channel ends must agree on the same version"))
}
}
colin-axner marked this conversation as resolved.
Show resolved Hide resolved

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
// aborted upgrade.
//
//lint:ignore U1000 Ignore unused function temporarily for debugging
func (k Keeper) startFlushUpgradeHandshake(
ctx sdk.Context,
portID,
Expand Down
159 changes: 159 additions & 0 deletions modules/core/04-channel/keeper/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,165 @@ func (suite *KeeperTestSuite) TestChanUpgradeTry() {
}
}

func (suite *KeeperTestSuite) TestChanUpgradeAck() {
var (
path *ibctesting.Path
counterpartyFlushStatus types.FlushStatus
counterpartyUpgrade types.Upgrade
)

testCases := []struct {
name string
malleate func()
expPass bool
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
}{
{
"success",
func() {},
true,
},
{
"success with later upgrade sequence",
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
func() {
channel := path.EndpointA.GetChannel()
channel.UpgradeSequence = 10
path.EndpointA.SetChannel(channel)

channel = path.EndpointB.GetChannel()
channel.UpgradeSequence = 10
path.EndpointB.SetChannel(channel)

suite.coordinator.CommitBlock(suite.chainA, suite.chainB)

err := path.EndpointA.UpdateClient()
suite.Require().NoError(err)
},
true,
},
{
"channel not found",
func() {
path.EndpointA.ChannelID = ibctesting.InvalidID
path.EndpointA.ChannelConfig.PortID = ibctesting.InvalidID
},
false,
},
{
"channel state is not in INITUPGRADE or TRYUPGRADE state",
func() {
suite.Require().NoError(path.EndpointA.SetChannelState(types.CLOSED))
},
false,
},
{
"counterparty flush status is not in FLUSHING or FLUSHCOMPLETE",
func() {
counterpartyFlushStatus = types.NOTINFLUSH
},
false,
},
{
"connection not found",
func() {
channel := path.EndpointA.GetChannel()
channel.ConnectionHops = []string{"connection-100"}
path.EndpointA.SetChannel(channel)
},
false,
},
{
"invalid connection state",
func() {
connectionEnd := path.EndpointA.GetConnection()
connectionEnd.State = connectiontypes.UNINITIALIZED
path.EndpointA.SetConnection(connectionEnd)
},
false,
},
{
"upgrade not found",
func() {
store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(exported.ModuleName))
store.Delete(host.ChannelUpgradeKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID))
},
false,
},
{
"channel end version mismatch on crossing hellos",
Copy link
Member Author

Choose a reason for hiding this comment

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

spoke with @AdityaSripal about this yesterday, testing this case as a full integration with the testing lib is tricky as its a timing sensitive state we end up, which would require a historical proof being submitted for the second chain to transition to TRY.

I chose to override the channel state directly here in order to cover this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can open an issue to make the test case more robust? It might be nice to have a test which simulates this scenario as it would potentially occur

Copy link
Member Author

Choose a reason for hiding this comment

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

func() {
channel := path.EndpointA.GetChannel()
channel.State = types.TRYUPGRADE

path.EndpointA.SetChannel(channel)

upgrade := path.EndpointA.GetChannelUpgrade()
upgrade.Fields.Version = "invalid-version"

path.EndpointA.SetChannelUpgrade(upgrade)
},
false,
},
{
"startFlushUpgradeHandshake fails due to proof verification failure, counterparty upgrade connection hops are tampered with",
func() {
counterpartyUpgrade.Fields.ConnectionHops = []string{ibctesting.InvalidID}
},
false,
},
{
"startFlushUpgradeHandshake fails due to mismatch in upgrade sequences",
func() {
channel := path.EndpointA.GetChannel()
channel.UpgradeSequence = 5
path.EndpointA.SetChannel(channel)
},
false,
},
}

for _, tc := range testCases {
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

counterpartyFlushStatus = types.FLUSHING

err := path.EndpointA.ChanUpgradeInit()
suite.Require().NoError(err)

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

// ensure client is up to date to receive valid proofs
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)

counterpartyUpgrade = path.EndpointB.GetChannelUpgrade()

tc.malleate()

proofChannel, proofUpgrade, proofHeight := path.EndpointA.QueryChannelUpgradeProof()

err = suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.ChanUpgradeAck(
suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, counterpartyFlushStatus, counterpartyUpgrade,
proofChannel, proofUpgrade, proofHeight,
)

if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}

// TestStartFlushUpgradeHandshake tests the startFlushUpgradeHandshake.
// UpgradeInit will be run on chainA and startFlushUpgradeHandshake
// will be called on chainB
Expand Down
1 change: 1 addition & 0 deletions modules/core/04-channel/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ var (
ErrUpgradeNotFound = errorsmod.Register(SubModuleName, 30, "upgrade not found")
ErrIncompatibleCounterpartyUpgrade = errorsmod.Register(SubModuleName, 31, "incompatible counterparty upgrade")
ErrInvalidUpgradeError = errorsmod.Register(SubModuleName, 32, "invalid upgrade error")
ErrInvalidFlushStatus = errorsmod.Register(SubModuleName, 33, "invalid flush status")
)
14 changes: 14 additions & 0 deletions testing/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,20 @@ func (endpoint *Endpoint) SetChannel(channel channeltypes.Channel) {
endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel)
}

// GetChannelUpgrade retrieves an IBC Channel Upgrade for the endpoint. The upgrade
// is expected to exist otherwise testing will fail.
func (endpoint *Endpoint) GetChannelUpgrade() channeltypes.Upgrade {
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
upgrade, found := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetUpgrade(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID)
require.True(endpoint.Chain.TB, found)
damiannolan marked this conversation as resolved.
Show resolved Hide resolved

return upgrade
}

// SetChannelUpgrade sets the channel upgrade for this endpoint.
func (endpoint *Endpoint) SetChannelUpgrade(upgrade channeltypes.Upgrade) {
endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetUpgrade(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, upgrade)
}

// QueryClientStateProof performs and abci query for a client stat associated
// with this endpoint and returns the ClientState along with the proof.
func (endpoint *Endpoint) QueryClientStateProof() (exported.ClientState, []byte) {
Expand Down