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 protos, msgs, keeper handler to upgrade clients using v1 governance proposals #4436

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
25 changes: 25 additions & 0 deletions modules/core/02-client/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,28 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
bz := k.cdc.MustMarshal(&params)
store.Set([]byte(types.ParamsKey), bz)
}

// ScheduleIBCSoftwareUpgrade schedules an upgrade for the IBC client.
func (k Keeper) ScheduleIBCSoftwareUpgrade(ctx sdk.Context, plan upgradetypes.Plan, upgradedClientState exported.ClientState) error {
// zero out any custom fields before setting
cs := upgradedClientState.ZeroCustomFields()
bz, err := types.MarshalClientState(k.cdc, cs)
if err != nil {
return errorsmod.Wrap(err, "could not marshal UpgradedClientState")
}

if err := k.upgradeKeeper.ScheduleUpgrade(ctx, plan); err != nil {
return err
}

// sets the new upgraded client last height committed on this chain at plan.Height,
// since the chain will panic at plan.Height and new chain will resume at plan.Height
if err = k.upgradeKeeper.SetUpgradedClient(ctx, plan.Height, bz); err != nil {
return err
}

// emitting an event for handling client upgrade proposal
emitUpgradeClientProposalEvent(ctx, plan.Name, plan.Height)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want a separate event for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i would lean towards not for this function, as this function calls the upgradeKeeper to schedule a client upgrade so i think the event should remain the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

potentially could be different for recover client tho

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes we should, opened #4507


return nil
}
112 changes: 112 additions & 0 deletions modules/core/02-client/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

tmbytes "github.com/cometbft/cometbft/libs/bytes"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
Expand Down Expand Up @@ -483,3 +485,113 @@ func (suite *KeeperTestSuite) TestUnsetParams() {
suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.GetParams(ctx)
})
}

// TestIBCSoftwareUpgrade tests that an IBC client upgrade has been properly scheduled
func (suite *KeeperTestSuite) TestIBCSoftwareUpgrade() {
var (
upgradedClientState *ibctm.ClientState
oldPlan, plan upgradetypes.Plan
)

testCases := []struct {
name string
malleate func()
expError error
}{
{
"valid upgrade proposal",
func() {},
nil,
},
{
"valid upgrade proposal with previous IBC state", func() {
oldPlan = upgradetypes.Plan{
Name: "upgrade IBC clients",
Height: 100,
}
},
nil,
},
{
"fail: scheduling upgrade with plan height 0",
func() {
plan.Height = 0
},
sdkerrors.ErrInvalidRequest,
},
}

for _, tc := range testCases {
tc := tc

suite.Run(tc.name, func() {
suite.SetupTest() // reset
oldPlan.Height = 0 // reset

path := ibctesting.NewPath(suite.chainA, suite.chainB)
suite.coordinator.SetupClients(path)
upgradedClientState = suite.chainA.GetClientState(path.EndpointA.ClientID).ZeroCustomFields().(*ibctm.ClientState)

// use height 1000 to distinguish from old plan
plan = upgradetypes.Plan{
Name: "upgrade IBC clients",
Height: 1000,
}

tc.malleate()

// set the old plan if it is not empty
if oldPlan.Height != 0 {
// set upgrade plan in the upgrade store
store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(upgradetypes.StoreKey))
bz := suite.chainA.App.AppCodec().MustMarshal(&oldPlan)
store.Set(upgradetypes.PlanKey(), bz)

bz, err := types.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClientState)
suite.Require().NoError(err)

suite.Require().NoError(suite.chainA.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainA.GetContext(), oldPlan.Height, bz))
}
Comment on lines +543 to +554
Copy link
Contributor

Choose a reason for hiding this comment

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

There's probably some code magic we could do here by adding the logic to setting an old plan and checking the expected result only in the test cases that use it. Maybe we can open as a followup issue (I know this comes from the existing code). I'd expect only the test cases needing an existing plan to execute that logic (maybe by calling ibcSoftwareUpgrade with an older plan height and then having some assertion to check that the old plan exists. Happy as is though


err := suite.chainA.App.GetIBCKeeper().ClientKeeper.ScheduleIBCSoftwareUpgrade(suite.chainA.GetContext(), plan, upgradedClientState)

if tc.expError == nil {
suite.Require().NoError(err)

// check that the correct plan is returned
storedPlan, found := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradePlan(suite.chainA.GetContext())
suite.Require().True(found)
suite.Require().Equal(plan, storedPlan)

// check that old upgraded client state is cleared
cs, found := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), oldPlan.Height)
suite.Require().False(found)
suite.Require().Empty(cs)

// check that client state was set
storedClientState, found := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), plan.Height)
suite.Require().True(found)
clientState, err := types.UnmarshalClientState(suite.chainA.App.AppCodec(), storedClientState)
suite.Require().NoError(err)
suite.Require().Equal(upgradedClientState, clientState)
} else {
// check that the new plan wasn't stored
storedPlan, found := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradePlan(suite.chainA.GetContext())
if oldPlan.Height != 0 {
// NOTE: this is only true if the ScheduleUpgrade function
// returns an error before clearing the old plan
suite.Require().True(found)
suite.Require().Equal(oldPlan, storedPlan)
} else {
suite.Require().False(found)
suite.Require().Empty(storedPlan)
}

// check that client state was not set
cs, found := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), plan.Height)
suite.Require().Empty(cs)
suite.Require().False(found)
}
})
}
}
1 change: 1 addition & 0 deletions modules/core/02-client/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
&MsgUpgradeClient{},
&MsgSubmitMisbehaviour{},
&MsgUpdateParams{},
&MsgIBCSoftwareUpgrade{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
Expand Down
37 changes: 37 additions & 0 deletions modules/core/02-client/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

host "github.com/cosmos/ibc-go/v7/modules/core/24-host"
ibcerrors "github.com/cosmos/ibc-go/v7/modules/core/errors"
Expand Down Expand Up @@ -283,3 +284,39 @@ func (msg *MsgUpdateParams) ValidateBasic() error {
}
return msg.Params.Validate()
}

// NewMsgIBCSoftwareUpgrade creates a new MsgIBCSoftwareUpgrade instance
func NewMsgIBCSoftwareUpgrade(authority string, plan upgradetypes.Plan, upgradedClientState exported.ClientState) (*MsgIBCSoftwareUpgrade, error) {
charleenfei marked this conversation as resolved.
Show resolved Hide resolved
anyClient, err := PackClientState(upgradedClientState)
if err != nil {
return nil, err
}

return &MsgIBCSoftwareUpgrade{
Authority: authority,
Plan: plan,
UpgradedClientState: anyClient,
}, nil
}

// GetSigners returns the expected signers for a MsgIBCSoftwareUpgrade message.
func (msg *MsgIBCSoftwareUpgrade) GetSigners() []sdk.AccAddress {
accAddr, err := sdk.AccAddressFromBech32(msg.Authority)
if err != nil {
panic(err)
}
return []sdk.AccAddress{accAddr}
}

// ValidateBasic performs basic checks on a MsgIBCSoftwareUpgrade.
func (msg *MsgIBCSoftwareUpgrade) ValidateBasic() error {
Copy link
Member

Choose a reason for hiding this comment

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

Considering that for the time being we're implicitly on a tendermint chain when using ibc-go should we add a check on the provided client state that ClientType() returns exported.Tendermint

cc. @colin-axner

Copy link
Contributor

Choose a reason for hiding this comment

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

Fine by me

if _, err := sdk.AccAddressFromBech32(msg.Authority); err != nil {
return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err)
}

if _, err := UnpackClientState(msg.UpgradedClientState); err != nil {
return err
}

return msg.Plan.ValidateBasic()
}
159 changes: 159 additions & 0 deletions modules/core/02-client/types/msgs_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package types_test

import (
"errors"
"testing"
"time"

"github.com/golang/protobuf/proto" //nolint:staticcheck
"github.com/stretchr/testify/require"
testifysuite "github.com/stretchr/testify/suite"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

"github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types"
ibcerrors "github.com/cosmos/ibc-go/v7/modules/core/errors"
"github.com/cosmos/ibc-go/v7/modules/core/exported"
solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine"
ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
Expand Down Expand Up @@ -678,3 +684,156 @@ func TestMsgUpdateParamsGetSigners(t *testing.T) {
}
}
}

// TestMsgIBCSoftwareUpgrade_NewMsgIBCSoftwareUpgrade tests NewMsgIBCSoftwareUpgrade
func (suite *TypesTestSuite) TestMsgIBCSoftwareUpgrade_NewMsgIBCSoftwareUpgrade() {
testCases := []struct {
name string
upgradedClientState exported.ClientState
expPass bool
}{
{
"success",
ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
true,
},
{
"fail: failed to pack ClientState",
nil,
false,
},
}

for _, tc := range testCases {
plan := upgradetypes.Plan{
Name: "upgrade IBC clients",
Height: 1000,
}
msg, err := types.NewMsgIBCSoftwareUpgrade(
ibctesting.TestAccAddress,
plan,
tc.upgradedClientState,
)

if tc.expPass {
suite.Require().NoError(err)
suite.Assert().Equal(ibctesting.TestAccAddress, msg.Authority)
suite.Assert().Equal(plan, msg.Plan)
unpackedClientState, err := types.UnpackClientState(msg.UpgradedClientState)
suite.Require().NoError(err)
suite.Assert().Equal(tc.upgradedClientState, unpackedClientState)
} else {
suite.Require().True(errors.Is(err, ibcerrors.ErrPackAny))
}
}
}

// TestMsgIBCSoftwareUpgrade_GetSigners tests GetSigners for MsgIBCSoftwareUpgrade
func (suite *TypesTestSuite) TestMsgIBCSoftwareUpgrade_GetSigners() {
testCases := []struct {
name string
address sdk.AccAddress
expPass bool
}{
{
"success: valid address",
sdk.AccAddress(ibctesting.TestAccAddress),
true,
},
{
"failure: nil address",
nil,
false,
},
}

for _, tc := range testCases {
clientState := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath)
plan := upgradetypes.Plan{
Name: "upgrade IBC clients",
Height: 1000,
}
msg, err := types.NewMsgIBCSoftwareUpgrade(
tc.address.String(),
plan,
clientState,
)
suite.Require().NoError(err)

if tc.expPass {
suite.Require().Equal([]sdk.AccAddress{tc.address}, msg.GetSigners())
} else {
suite.Require().Panics(func() { msg.GetSigners() })
}
}
}

// TestMsgIBCSoftwareUpgrade_ValidateBasic tests ValidateBasic for MsgIBCSoftwareUpgrade
func (suite *TypesTestSuite) TestMsgIBCSoftwareUpgrade_ValidateBasic() {
var (
authority string
plan upgradetypes.Plan
anyClient *codectypes.Any
)
testCases := []struct {
name string
malleate func()
expError error
}{
{
"success",
func() {},
nil,
},
{
"failure: invalid authority address",
func() {
authority = "invalid"
},
ibcerrors.ErrInvalidAddress,
},
{
"failure: error unpacking client state",
func() {
anyClient = &codectypes.Any{}
},
ibcerrors.ErrUnpackAny,
},
{
"failure: error validating upgrade plan, height is not greater than zero",
func() {
plan.Height = 0
},
sdkerrors.ErrInvalidRequest,
},
}

for _, tc := range testCases {
authority = ibctesting.TestAccAddress
plan = upgradetypes.Plan{
Name: "upgrade IBC clients",
Height: 1000,
}
upgradedClientState := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath)
var err error
anyClient, err = types.PackClientState(upgradedClientState)
suite.Require().NoError(err)

tc.malleate()

msg := types.MsgIBCSoftwareUpgrade{
authority,
plan,
anyClient,
}

err = msg.ValidateBasic()

if tc.expError == nil {
suite.Require().NoError(err)
}
if tc.expError != nil {
suite.Require().True(errors.Is(err, tc.expError))
}
}
}
Loading