diff --git a/client/grpc/reflection/reflection.pb.gw.go b/client/grpc/reflection/reflection.pb.gw.go index b3e8c46414..ab486750e8 100644 --- a/client/grpc/reflection/reflection.pb.gw.go +++ b/client/grpc/reflection/reflection.pb.gw.go @@ -106,6 +106,7 @@ func local_request_ReflectionService_ListImplementations_0(ctx context.Context, // RegisterReflectionServiceHandlerServer registers the http handlers for service ReflectionService to "mux". // UnaryRPC :call ReflectionServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterReflectionServiceHandlerFromEndpoint instead. func RegisterReflectionServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ReflectionServiceServer) error { mux.Handle("GET", pattern_ReflectionService_ListAllInterfaces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/client/grpc/simulate/simulate.pb.gw.go b/client/grpc/simulate/simulate.pb.gw.go index 733449d037..78cb85437c 100644 --- a/client/grpc/simulate/simulate.pb.gw.go +++ b/client/grpc/simulate/simulate.pb.gw.go @@ -70,6 +70,7 @@ func local_request_SimulateService_Simulate_0(ctx context.Context, marshaler run // RegisterSimulateServiceHandlerServer registers the http handlers for service SimulateService to "mux". // UnaryRPC :call SimulateServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterSimulateServiceHandlerFromEndpoint instead. func RegisterSimulateServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server SimulateServiceServer) error { mux.Handle("POST", pattern_SimulateService_Simulate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/proto/ibc/client/client.proto b/proto/ibc/client/client.proto index d194cce52e..a2c7d9ded1 100644 --- a/proto/ibc/client/client.proto +++ b/proto/ibc/client/client.proto @@ -26,6 +26,21 @@ message ClientConsensusStates { [(gogoproto.moretags) = "yaml:\"consensus_states\""]; } +// ClientUpdateProposal is a governance proposal. If it passes, the client is +// updated with the provided header. The update may fail if the header is not +// valid given certain conditions specified by the client implementation. +message ClientUpdateProposal { + option (gogoproto.goproto_getters) = false; + // the title of the update proposal + string title = 1; + // the description of the proposal + string description = 2; + // the client identifier for the client to be updated if the proposal passes + string client_id = 3 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // the header used to update the client if the proposal passes + google.protobuf.Any header = 4; +} + // MsgCreateClient defines a message to create an IBC client message MsgCreateClient { // client unique identifier diff --git a/proto/ibc/lightclients/solomachine/v1/solomachine.proto b/proto/ibc/lightclients/solomachine/v1/solomachine.proto index 22d53ccd3f..a6463ef757 100644 --- a/proto/ibc/lightclients/solomachine/v1/solomachine.proto +++ b/proto/ibc/lightclients/solomachine/v1/solomachine.proto @@ -15,6 +15,10 @@ message ClientState { [(gogoproto.moretags) = "yaml:\"frozen_sequence\""]; ConsensusState consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // when set to true, will allow governance to update a solo machine client. + // The client will be unfrozen if it is frozen. + bool allow_update_after_proposal = 3 + [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; } // ConsensusState defines a solo machine consensus state diff --git a/proto/ibc/tendermint/tendermint.proto b/proto/ibc/tendermint/tendermint.proto index 007593238a..2fce5b648f 100644 --- a/proto/ibc/tendermint/tendermint.proto +++ b/proto/ibc/tendermint/tendermint.proto @@ -54,6 +54,15 @@ message ClientState { // Proof specifications used in verifying counterparty state repeated ics23.ProofSpec proof_specs = 8 [(gogoproto.moretags) = "yaml:\"proof_specs\""]; + + // This flag, when set to true, will allow governance to recover a client + // which has expired + bool allow_update_after_expiry = 9 + [(gogoproto.moretags) = "yaml:\"allow_update_after_expiry\""]; + // This flag, when set to true, will allow governance to unfreeze a client + // whose chain has experienced a misbehaviour event + bool allow_update_after_misbehaviour = 10 + [(gogoproto.moretags) = "yaml:\"allow_update_after_misbehaviour\""]; } // ConsensusState defines the consensus state from Tendermint. diff --git a/simapp/app.go b/simapp/app.go index feac218720..a26d08bd07 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -52,6 +52,7 @@ import ( transfer "github.com/cosmos/cosmos-sdk/x/ibc-transfer" ibctransferkeeper "github.com/cosmos/cosmos-sdk/x/ibc-transfer/keeper" ibctransfertypes "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" + ibcclient "github.com/cosmos/cosmos-sdk/x/ibc/02-client" porttypes "github.com/cosmos/cosmos-sdk/x/ibc/05-port/types" ibchost "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ibckeeper "github.com/cosmos/cosmos-sdk/x/ibc/keeper" @@ -244,17 +245,6 @@ func NewSimApp( ) app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath) - // register the proposal types - govRouter := govtypes.NewRouter() - govRouter.AddRoute(govtypes.RouterKey, govtypes.ProposalHandler). - AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). - AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)). - AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)) - app.GovKeeper = govkeeper.NewKeeper( - appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, - &stakingKeeper, govRouter, - ) - // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks app.StakingKeeper = *stakingKeeper.SetHooks( @@ -266,6 +256,18 @@ func NewSimApp( appCodec, keys[ibchost.StoreKey], app.StakingKeeper, scopedIBCKeeper, ) + // register the proposal types + govRouter := govtypes.NewRouter() + govRouter.AddRoute(govtypes.RouterKey, govtypes.ProposalHandler). + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). + AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)). + AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)). + AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) + app.GovKeeper = govkeeper.NewKeeper( + appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, + &stakingKeeper, govRouter, + ) + // Create Transfer Keepers app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), diff --git a/x/auth/types/query.pb.gw.go b/x/auth/types/query.pb.gw.go index 7e80fa6fa5..3b156842dd 100644 --- a/x/auth/types/query.pb.gw.go +++ b/x/auth/types/query.pb.gw.go @@ -106,6 +106,7 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Account_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/bank/types/query.pb.gw.go b/x/bank/types/query.pb.gw.go index 85d7767917..ca9c1ddbfa 100644 --- a/x/bank/types/query.pb.gw.go +++ b/x/bank/types/query.pb.gw.go @@ -272,6 +272,7 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Balance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/distribution/types/query.pb.gw.go b/x/distribution/types/query.pb.gw.go index b3a714f349..890e51e40d 100644 --- a/x/distribution/types/query.pb.gw.go +++ b/x/distribution/types/query.pb.gw.go @@ -488,6 +488,7 @@ func local_request_Query_CommunityPool_0(ctx context.Context, marshaler runtime. // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/evidence/types/query.pb.gw.go b/x/evidence/types/query.pb.gw.go index e9346629c2..e15d163524 100644 --- a/x/evidence/types/query.pb.gw.go +++ b/x/evidence/types/query.pb.gw.go @@ -124,6 +124,7 @@ func local_request_Query_AllEvidence_0(ctx context.Context, marshaler runtime.Ma // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Evidence_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/gov/types/query.pb.gw.go b/x/gov/types/query.pb.gw.go index 4e2c61f5f8..e9fc72f302 100644 --- a/x/gov/types/query.pb.gw.go +++ b/x/gov/types/query.pb.gw.go @@ -528,6 +528,7 @@ func local_request_Query_TallyResult_0(ctx context.Context, marshaler runtime.Ma // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Proposal_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/ibc-transfer/types/query.pb.gw.go b/x/ibc-transfer/types/query.pb.gw.go index 9d5093a47f..23b91aafc1 100644 --- a/x/ibc-transfer/types/query.pb.gw.go +++ b/x/ibc-transfer/types/query.pb.gw.go @@ -142,6 +142,7 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_DenomTrace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/ibc/02-client/handler.go b/x/ibc/02-client/handler.go index 466d6d1733..67351e4ae3 100644 --- a/x/ibc/02-client/handler.go +++ b/x/ibc/02-client/handler.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" ) @@ -93,3 +94,16 @@ func HandleMsgSubmitMisbehaviour(ctx sdk.Context, k keeper.Keeper, msg *types.Ms Events: ctx.EventManager().Events().ToABCIEvents(), }, nil } + +// NewClientUpdateProposalHandler defines the client update proposal handler +func NewClientUpdateProposalHandler(k keeper.Keeper) govtypes.Handler { + return func(ctx sdk.Context, content govtypes.Content) error { + switch c := content.(type) { + case *types.ClientUpdateProposal: + return k.ClientUpdateProposal(ctx, c) + + default: + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized ibc proposal content type: %T", c) + } + } +} diff --git a/x/ibc/02-client/handler_test.go b/x/ibc/02-client/handler_test.go new file mode 100644 index 0000000000..0d2955f39c --- /dev/null +++ b/x/ibc/02-client/handler_test.go @@ -0,0 +1,76 @@ +package client_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" +) + +func (suite *ClientTestSuite) TestNewClientUpdateProposalHandler() { + var ( + content govtypes.Content + err error + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "valid update client proposal", func() { + clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + clientState := suite.chainA.GetClientState(clientA) + + tmClientState, ok := clientState.(*ibctmtypes.ClientState) + suite.Require().True(ok) + tmClientState.AllowUpdateAfterMisbehaviour = true + tmClientState.FrozenHeight = tmClientState.LatestHeight + suite.chainA.App.IBCKeeper.ClientKeeper.SetClientState(suite.chainA.GetContext(), clientA, tmClientState) + + // use next header for chainB to update the client on chainA + header, err := suite.chainA.ConstructUpdateTMClientHeader(suite.chainB, clientA) + suite.Require().NoError(err) + + content, err = clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, clientA, header) + suite.Require().NoError(err) + }, true, + }, + { + "nil proposal", func() { + content = nil + }, false, + }, + { + "unsupported proposal type", func() { + content = distributiontypes.NewCommunityPoolSpendProposal(ibctesting.Title, ibctesting.Description, suite.chainA.SenderAccount.GetAddress(), sdk.NewCoins(sdk.NewCoin("communityfunds", sdk.NewInt(10)))) + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + tc.malleate() + + proposalHandler := client.NewClientUpdateProposalHandler(suite.chainA.App.IBCKeeper.ClientKeeper) + + err = proposalHandler(suite.chainA.GetContext(), content) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + +} diff --git a/x/ibc/02-client/keeper/client.go b/x/ibc/02-client/keeper/client.go index b4880b9d79..05db718ea9 100644 --- a/x/ibc/02-client/keeper/client.go +++ b/x/ibc/02-client/keeper/client.go @@ -37,7 +37,7 @@ func (k Keeper) CreateClient( return clientState, nil } -// UpdateClient updates the consensus state and the state root from a provided header +// UpdateClient updates the consensus state and the state root from a provided header. func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) (exported.ClientState, error) { clientType, found := k.GetClientType(ctx, clientID) if !found { diff --git a/x/ibc/02-client/keeper/client_test.go b/x/ibc/02-client/keeper/client_test.go index 3733dc56aa..c3c4ce8374 100644 --- a/x/ibc/02-client/keeper/client_test.go +++ b/x/ibc/02-client/keeper/client_test.go @@ -36,11 +36,11 @@ func (suite *KeeperTestSuite) TestCreateClient() { i := i if tc.expPanic { suite.Require().Panics(func() { - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) suite.keeper.CreateClient(suite.ctx, tc.clientID, clientState, suite.consensusState) }, "Msg %d didn't panic: %s", i, tc.msg) } else { - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) if tc.expPass { suite.Require().NotNil(clientState, "valid test case %d failed: %s", i, tc.msg) } @@ -77,7 +77,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { expPass bool }{ {"valid update", func() error { - clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) // store intermediate consensus state to check that trustedHeight does not need to be highest consensus state before header height @@ -96,7 +96,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { return err }, true}, {"valid past update", func() error { - clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) suite.Require().NoError(err) @@ -143,7 +143,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { return nil }, false}, {"consensus state not found", func() error { - clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) suite.keeper.SetClientType(suite.ctx, testClientID, exported.Tendermint) updateHeader = createFutureUpdateFn(suite) @@ -159,7 +159,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { return nil }, false}, {"valid past update before client was frozen", func() error { - clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) clientState.FrozenHeight = types.NewHeight(0, testClientHeight.EpochHeight-1) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) suite.Require().NoError(err) @@ -180,7 +180,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { return nil }, true}, {"invalid header", func() error { - clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState = ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) suite.Require().NoError(err) updateHeader = createPastUpdateFn(suite) @@ -291,7 +291,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.NextValidatorsHash = bothValsHash - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) return err @@ -308,7 +308,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.NextValidatorsHash = valsHash - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) // store intermediate consensus state to check that trustedHeight does not need to be highest consensus state before header height @@ -336,7 +336,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.NextValidatorsHash = valsHash - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) // store trusted consensus state for Header2 @@ -364,7 +364,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.NextValidatorsHash = valsHash - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) // intermediate consensus state at height + 3 is not created return err @@ -381,7 +381,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.NextValidatorsHash = valsHash - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) // intermediate consensus state at height + 3 is not created return err @@ -404,7 +404,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { }, func() error { suite.consensusState.NextValidatorsHash = bothValsHash - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) _, err := suite.keeper.CreateClient(suite.ctx, testClientID, clientState, suite.consensusState) clientState.FrozenHeight = types.NewHeight(0, 1) @@ -423,7 +423,7 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { ClientId: testClientID, }, func() error { - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) if err != nil { return err } diff --git a/x/ibc/02-client/keeper/grpc_query_test.go b/x/ibc/02-client/keeper/grpc_query_test.go index 6b9fe8d92a..cd15bcd396 100644 --- a/x/ibc/02-client/keeper/grpc_query_test.go +++ b/x/ibc/02-client/keeper/grpc_query_test.go @@ -42,7 +42,7 @@ func (suite *KeeperTestSuite) TestQueryClientState() { { "success", func() { - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) var err error @@ -208,7 +208,7 @@ func (suite *KeeperTestSuite) TestQueryConsensusState() { { "success latest height", func() { - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) cs := ibctmtypes.NewConsensusState( suite.consensusState.Timestamp, commitmenttypes.NewMerkleRoot([]byte("hash1")), suite.consensusState.Height, nil, ) diff --git a/x/ibc/02-client/keeper/keeper_test.go b/x/ibc/02-client/keeper/keeper_test.go index d3e03f0b8d..a5cb4b481e 100644 --- a/x/ibc/02-client/keeper/keeper_test.go +++ b/x/ibc/02-client/keeper/keeper_test.go @@ -111,7 +111,7 @@ func TestKeeperTestSuite(t *testing.T) { } func (suite *KeeperTestSuite) TestSetClientState() { - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) retrievedState, found := suite.keeper.GetClientState(suite.ctx, testClientID) @@ -146,7 +146,7 @@ func (suite *KeeperTestSuite) TestValidateSelfClient() { }{ { "success", - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false), true, }, { @@ -156,37 +156,37 @@ func (suite *KeeperTestSuite) TestValidateSelfClient() { }, { "frozen client", - &ibctmtypes.ClientState{testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, testClientHeight, commitmenttypes.GetSDKSpecs()}, + &ibctmtypes.ClientState{testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false}, false, }, { "incorrect chainID", - ibctmtypes.NewClientState("gaiatestnet", ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState("gaiatestnet", ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false), false, }, { "invalid client height", - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.NewHeight(0, testClientHeight.EpochHeight+10), commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.NewHeight(0, testClientHeight.EpochHeight+10), commitmenttypes.GetSDKSpecs(), false, false), false, }, { "invalid proof specs", - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, nil), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, nil, false, false), false, }, { "invalid trust level", - ibctmtypes.NewClientState(testChainID, ibctmtypes.Fraction{0, 1}, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.Fraction{0, 1}, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false), false, }, { "invalid unbonding period", - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod+10, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod+10, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false), false, }, { "invalid trusting period", - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, ubdPeriod+10, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, ubdPeriod+10, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false), false, }, } @@ -209,9 +209,9 @@ func (suite KeeperTestSuite) TestGetAllClients() { testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false), } for i := range expClients { @@ -233,9 +233,9 @@ func (suite KeeperTestSuite) TestGetAllGenesisClients() { testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()), - ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false), + ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.Height{}, commitmenttypes.GetSDKSpecs(), false, false), } expGenClients := make([]types.IdentifiedClientState, len(expClients)) @@ -283,7 +283,7 @@ func (suite KeeperTestSuite) TestGetConsensusState() { func (suite KeeperTestSuite) TestConsensusStateHelpers() { // initial setup - clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs()) + clientState := ibctmtypes.NewClientState(testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), false, false) suite.keeper.SetClientState(suite.ctx, testClientID, clientState) suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight, suite.consensusState) diff --git a/x/ibc/02-client/keeper/proposal.go b/x/ibc/02-client/keeper/proposal.go new file mode 100644 index 0000000000..c4b844bbca --- /dev/null +++ b/x/ibc/02-client/keeper/proposal.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" +) + +// ClientUpdateProposal will try to update the client with the new header if and only if +// the proposal passes. The localhost client is not allowed to be modified with a proposal. +func (k Keeper) ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdateProposal) error { + clientType, found := k.GetClientType(ctx, p.ClientId) + if !found { + return sdkerrors.Wrapf(types.ErrClientTypeNotFound, "cannot update client with ID %s", p.ClientId) + } + + if clientType == exported.Localhost || p.ClientId == exported.ClientTypeLocalHost { + return sdkerrors.Wrap(types.ErrInvalidUpdateClientProposal, "cannot update localhost client with proposal") + } + + clientState, found := k.GetClientState(ctx, p.ClientId) + if !found { + return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", p.ClientId) + } + + header, err := types.UnpackHeader(p.Header) + if err != nil { + return err + } + + clientState, consensusState, err := clientState.CheckProposedHeaderAndUpdateState(ctx, k.cdc, k.ClientStore(ctx, p.ClientId), header) + if err != nil { + return err + } + + k.SetClientState(ctx, p.ClientId, clientState) + k.SetClientConsensusState(ctx, p.ClientId, header.GetHeight(), consensusState) + + k.Logger(ctx).Info("client updated after governance proposal passed", "client-id", p.ClientId, "height", clientState.GetLatestHeight()) + + // emitting events in the keeper for proposal updates to clients + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeUpdateClientProposal, + sdk.NewAttribute(types.AttributeKeyClientID, p.ClientId), + sdk.NewAttribute(types.AttributeKeyClientType, clientType.String()), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, fmt.Sprintf("%d", header.GetHeight())), + ), + ) + + return nil +} diff --git a/x/ibc/02-client/keeper/proposal_test.go b/x/ibc/02-client/keeper/proposal_test.go new file mode 100644 index 0000000000..11ca735042 --- /dev/null +++ b/x/ibc/02-client/keeper/proposal_test.go @@ -0,0 +1,95 @@ +package keeper_test + +import ( + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" +) + +func (suite *KeeperTestSuite) TestClientUpdateProposal() { + var ( + content *types.ClientUpdateProposal + err error + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "valid update client proposal", func() { + clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + clientState := suite.chainA.GetClientState(clientA) + + tmClientState, ok := clientState.(*ibctmtypes.ClientState) + suite.Require().True(ok) + tmClientState.AllowUpdateAfterMisbehaviour = true + tmClientState.FrozenHeight = tmClientState.LatestHeight + suite.chainA.App.IBCKeeper.ClientKeeper.SetClientState(suite.chainA.GetContext(), clientA, tmClientState) + + // use next header for chainB to update the client on chainA + header, err := suite.chainA.ConstructUpdateTMClientHeader(suite.chainB, clientA) + suite.Require().NoError(err) + + content, err = clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, clientA, header) + suite.Require().NoError(err) + }, true, + }, + { + "client type does not exist", func() { + content, err = clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, ibctesting.InvalidID, &ibctmtypes.Header{}) + suite.Require().NoError(err) + }, false, + }, + { + "cannot update localhost", func() { + content, err = clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, exported.Localhost.String(), &ibctmtypes.Header{}) + suite.Require().NoError(err) + }, false, + }, + { + "client does not exist", func() { + // bypass ClientType check + suite.chainA.App.IBCKeeper.ClientKeeper.SetClientType(suite.chainA.GetContext(), ibctesting.InvalidID, exported.Tendermint) + content, err = clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, ibctesting.InvalidID, &ibctmtypes.Header{}) + suite.Require().NoError(err) + }, false, + }, + { + "cannot unpack header, header is nil", func() { + clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + content = &clienttypes.ClientUpdateProposal{ibctesting.Title, ibctesting.Description, clientA, nil} + }, false, + }, + { + "update fails", func() { + header := &ibctmtypes.Header{} + clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + content, err = clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, clientA, header) + suite.Require().NoError(err) + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + tc.malleate() + + err = suite.chainA.App.IBCKeeper.ClientKeeper.ClientUpdateProposal(suite.chainA.GetContext(), content) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + +} diff --git a/x/ibc/02-client/types/client.pb.go b/x/ibc/02-client/types/client.pb.go index f0aff8873a..e0a194d44b 100644 --- a/x/ibc/02-client/types/client.pb.go +++ b/x/ibc/02-client/types/client.pb.go @@ -137,6 +137,53 @@ func (m *ClientConsensusStates) GetConsensusStates() []*types.Any { return nil } +// ClientUpdateProposal is a governance proposal. If it passes, the client is +// updated with the provided header. The update may fail if the header is not +// valid given certain conditions specified by the client implementation. +type ClientUpdateProposal struct { + // the title of the update proposal + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + // the description of the proposal + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // the client identifier for the client to be updated if the proposal passes + ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" yaml:"client_id"` + // the header used to update the client if the proposal passes + Header *types.Any `protobuf:"bytes,4,opt,name=header,proto3" json:"header,omitempty"` +} + +func (m *ClientUpdateProposal) Reset() { *m = ClientUpdateProposal{} } +func (m *ClientUpdateProposal) String() string { return proto.CompactTextString(m) } +func (*ClientUpdateProposal) ProtoMessage() {} +func (*ClientUpdateProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_226f80e576f20abd, []int{2} +} +func (m *ClientUpdateProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientUpdateProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientUpdateProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientUpdateProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientUpdateProposal.Merge(m, src) +} +func (m *ClientUpdateProposal) XXX_Size() int { + return m.Size() +} +func (m *ClientUpdateProposal) XXX_DiscardUnknown() { + xxx_messageInfo_ClientUpdateProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientUpdateProposal proto.InternalMessageInfo + // MsgCreateClient defines a message to create an IBC client type MsgCreateClient struct { // client unique identifier @@ -154,7 +201,7 @@ func (m *MsgCreateClient) Reset() { *m = MsgCreateClient{} } func (m *MsgCreateClient) String() string { return proto.CompactTextString(m) } func (*MsgCreateClient) ProtoMessage() {} func (*MsgCreateClient) Descriptor() ([]byte, []int) { - return fileDescriptor_226f80e576f20abd, []int{2} + return fileDescriptor_226f80e576f20abd, []int{3} } func (m *MsgCreateClient) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -226,7 +273,7 @@ func (m *MsgUpdateClient) Reset() { *m = MsgUpdateClient{} } func (m *MsgUpdateClient) String() string { return proto.CompactTextString(m) } func (*MsgUpdateClient) ProtoMessage() {} func (*MsgUpdateClient) Descriptor() ([]byte, []int) { - return fileDescriptor_226f80e576f20abd, []int{3} + return fileDescriptor_226f80e576f20abd, []int{4} } func (m *MsgUpdateClient) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -291,7 +338,7 @@ func (m *MsgSubmitMisbehaviour) Reset() { *m = MsgSubmitMisbehaviour{} } func (m *MsgSubmitMisbehaviour) String() string { return proto.CompactTextString(m) } func (*MsgSubmitMisbehaviour) ProtoMessage() {} func (*MsgSubmitMisbehaviour) Descriptor() ([]byte, []int) { - return fileDescriptor_226f80e576f20abd, []int{4} + return fileDescriptor_226f80e576f20abd, []int{5} } func (m *MsgSubmitMisbehaviour) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -360,7 +407,7 @@ type Height struct { func (m *Height) Reset() { *m = Height{} } func (*Height) ProtoMessage() {} func (*Height) Descriptor() ([]byte, []int) { - return fileDescriptor_226f80e576f20abd, []int{5} + return fileDescriptor_226f80e576f20abd, []int{6} } func (m *Height) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -392,6 +439,7 @@ var xxx_messageInfo_Height proto.InternalMessageInfo func init() { proto.RegisterType((*IdentifiedClientState)(nil), "ibc.client.IdentifiedClientState") proto.RegisterType((*ClientConsensusStates)(nil), "ibc.client.ClientConsensusStates") + proto.RegisterType((*ClientUpdateProposal)(nil), "ibc.client.ClientUpdateProposal") proto.RegisterType((*MsgCreateClient)(nil), "ibc.client.MsgCreateClient") proto.RegisterType((*MsgUpdateClient)(nil), "ibc.client.MsgUpdateClient") proto.RegisterType((*MsgSubmitMisbehaviour)(nil), "ibc.client.MsgSubmitMisbehaviour") @@ -401,40 +449,44 @@ func init() { func init() { proto.RegisterFile("ibc/client/client.proto", fileDescriptor_226f80e576f20abd) } var fileDescriptor_226f80e576f20abd = []byte{ - // 525 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0x3f, 0x8f, 0x12, 0x41, - 0x1c, 0x65, 0xe0, 0x42, 0xee, 0x06, 0x22, 0x97, 0x15, 0x04, 0x31, 0xd9, 0x25, 0x53, 0x51, 0xc8, - 0xae, 0x87, 0x8d, 0xa1, 0x03, 0x1a, 0x49, 0xc4, 0x98, 0xbd, 0x58, 0x68, 0x4c, 0x2e, 0xbb, 0xb3, - 0x73, 0xbb, 0x13, 0xd9, 0x1d, 0xb2, 0x33, 0x6b, 0xe4, 0x1b, 0x18, 0x2b, 0x4b, 0x0b, 0x8b, 0x2b, - 0xfd, 0x10, 0xda, 0xdb, 0x79, 0xa5, 0xd5, 0xc6, 0xc0, 0x37, 0xa0, 0xb4, 0x32, 0xcc, 0x2c, 0x1e, - 0x5c, 0x4e, 0x8a, 0x3b, 0x0b, 0xab, 0x99, 0xdf, 0xbf, 0xf7, 0x7b, 0xef, 0x65, 0x32, 0xb0, 0x4e, - 0x5d, 0x6c, 0xe1, 0x09, 0x25, 0x91, 0xc8, 0x0e, 0x73, 0x1a, 0x33, 0xc1, 0x34, 0x48, 0x5d, 0x6c, - 0xaa, 0x4c, 0xb3, 0xea, 0x33, 0x9f, 0xc9, 0xb4, 0xb5, 0xba, 0xa9, 0x8e, 0xe6, 0x5d, 0x9f, 0x31, - 0x7f, 0x42, 0x2c, 0x19, 0xb9, 0xc9, 0xa9, 0xe5, 0x44, 0x33, 0x55, 0x42, 0x9f, 0x00, 0xac, 0x8d, - 0x3c, 0x12, 0x09, 0x7a, 0x4a, 0x89, 0x37, 0x94, 0x28, 0xc7, 0xc2, 0x11, 0x44, 0x3b, 0x82, 0x07, - 0x0a, 0xf4, 0x84, 0x7a, 0x0d, 0xd0, 0x02, 0xed, 0x83, 0x41, 0x75, 0x99, 0x1a, 0x87, 0x33, 0x27, - 0x9c, 0xf4, 0xd0, 0x9f, 0x12, 0xb2, 0xf7, 0xd5, 0x7d, 0xe4, 0x69, 0xcf, 0x60, 0x39, 0xcb, 0xf3, - 0x15, 0x44, 0x23, 0xdf, 0x02, 0xed, 0x52, 0xb7, 0x6a, 0xaa, 0xf5, 0xe6, 0x7a, 0xbd, 0xd9, 0x8f, - 0x66, 0x83, 0xfa, 0x32, 0x35, 0x6e, 0x6f, 0x61, 0xc9, 0x19, 0x64, 0x97, 0xf0, 0x05, 0x09, 0xf4, - 0x19, 0xc0, 0x9a, 0x22, 0x35, 0x64, 0x11, 0x27, 0x11, 0x4f, 0xb8, 0x2c, 0xf0, 0xeb, 0xd0, 0x7b, - 0x05, 0x0f, 0xf1, 0x1a, 0x45, 0x6d, 0xe3, 0x8d, 0x7c, 0xab, 0xf0, 0x57, 0x8a, 0xf7, 0x96, 0xa9, - 0x51, 0xcf, 0xf0, 0x2e, 0xcd, 0x21, 0xbb, 0x82, 0xb7, 0x09, 0xa1, 0x2f, 0x79, 0x58, 0x19, 0x73, - 0x7f, 0x18, 0x13, 0x47, 0x10, 0xc5, 0xf9, 0xbf, 0xf0, 0x50, 0x7b, 0x01, 0x2b, 0x97, 0xe8, 0x37, - 0x0a, 0x3b, 0x40, 0x9b, 0xcb, 0xd4, 0xb8, 0x73, 0xa5, 0x6a, 0x64, 0xdf, 0xda, 0x16, 0xad, 0x8d, - 0x60, 0x91, 0x53, 0x3f, 0x22, 0x71, 0x63, 0xaf, 0x05, 0xda, 0xe5, 0xc1, 0xd1, 0xaf, 0xd4, 0xe8, - 0xf8, 0x54, 0x04, 0x89, 0x6b, 0x62, 0x16, 0x5a, 0x98, 0xf1, 0x90, 0xf1, 0xec, 0xe8, 0x70, 0xef, - 0xb5, 0x25, 0x66, 0x53, 0xc2, 0xcd, 0x3e, 0xc6, 0x7d, 0xcf, 0x8b, 0x09, 0xe7, 0x76, 0x06, 0x80, - 0xbe, 0x02, 0x69, 0xdf, 0xf3, 0xa9, 0x77, 0x23, 0xfb, 0xee, 0xc3, 0x62, 0x40, 0x1c, 0x8f, 0xc4, - 0xbb, 0x8c, 0xb3, 0xb3, 0x9e, 0x0d, 0xfe, 0x85, 0x9b, 0xf2, 0xff, 0x0e, 0x60, 0x6d, 0xcc, 0xfd, - 0xe3, 0xc4, 0x0d, 0xa9, 0x18, 0x53, 0xee, 0x92, 0xc0, 0x79, 0x43, 0x59, 0x12, 0x5f, 0x47, 0xc5, - 0x23, 0x58, 0x0e, 0x37, 0x20, 0x76, 0x6a, 0xd9, 0xea, 0xfc, 0x97, 0x8a, 0xde, 0x03, 0x58, 0x7c, - 0x4c, 0xa8, 0x1f, 0x08, 0xad, 0x07, 0xcb, 0x64, 0xca, 0x70, 0x70, 0x12, 0x25, 0xa1, 0x4b, 0x62, - 0xa9, 0x62, 0x6f, 0xf3, 0xf9, 0x6d, 0x56, 0x91, 0x5d, 0x92, 0xe1, 0x53, 0x19, 0x5d, 0xcc, 0x06, - 0x12, 0x4b, 0x6a, 0xb9, 0x62, 0x56, 0x55, 0xd7, 0xb3, 0x6a, 0x6f, 0x6f, 0xff, 0xdd, 0x99, 0x91, - 0xfb, 0x78, 0x66, 0xe4, 0x06, 0x4f, 0xbe, 0xcd, 0x75, 0x70, 0x3e, 0xd7, 0xc1, 0xcf, 0xb9, 0x0e, - 0x3e, 0x2c, 0xf4, 0xdc, 0xf9, 0x42, 0xcf, 0xfd, 0x58, 0xe8, 0xb9, 0x97, 0xdd, 0x9d, 0xea, 0xde, - 0x5a, 0xab, 0x6f, 0xf3, 0x41, 0xb7, 0x93, 0xfd, 0x9c, 0x52, 0xad, 0x5b, 0x94, 0x0e, 0x3e, 0xfc, - 0x1d, 0x00, 0x00, 0xff, 0xff, 0x0c, 0x85, 0x19, 0xfc, 0x54, 0x05, 0x00, 0x00, + // 588 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0xb1, 0x8f, 0x12, 0x4f, + 0x14, 0x66, 0x38, 0x7e, 0xe4, 0x18, 0xc8, 0x8f, 0xcb, 0x0a, 0x82, 0x98, 0xec, 0x92, 0xad, 0xae, + 0x90, 0x5d, 0x0f, 0x1b, 0x43, 0x07, 0x34, 0x92, 0x88, 0xb9, 0xec, 0xc5, 0x42, 0x63, 0x72, 0xd9, + 0x9d, 0x9d, 0xdb, 0x9d, 0xc8, 0xee, 0x6c, 0x76, 0x66, 0x8d, 0xfc, 0x07, 0xc6, 0xca, 0xd2, 0xc2, + 0xe2, 0x4a, 0xff, 0x01, 0x3b, 0xed, 0xed, 0xbc, 0xd2, 0x8a, 0x18, 0xf8, 0x0f, 0x28, 0xad, 0x0c, + 0x33, 0x8b, 0xc0, 0xe5, 0x38, 0x0d, 0x5a, 0x58, 0xed, 0xbc, 0xf7, 0xe6, 0x7d, 0xef, 0xfb, 0xbe, + 0x07, 0x03, 0x6b, 0xc4, 0x41, 0x26, 0x1a, 0x11, 0x1c, 0xf2, 0xf4, 0x63, 0x44, 0x31, 0xe5, 0x54, + 0x81, 0xc4, 0x41, 0x86, 0xcc, 0x34, 0x2a, 0x1e, 0xf5, 0xa8, 0x48, 0x9b, 0x8b, 0x93, 0xbc, 0xd1, + 0xb8, 0xe5, 0x51, 0xea, 0x8d, 0xb0, 0x29, 0x22, 0x27, 0x39, 0x33, 0xed, 0x70, 0x2c, 0x4b, 0xfa, + 0x3b, 0x00, 0xab, 0x03, 0x17, 0x87, 0x9c, 0x9c, 0x11, 0xec, 0xf6, 0x05, 0xca, 0x09, 0xb7, 0x39, + 0x56, 0x8e, 0x60, 0x41, 0x82, 0x9e, 0x12, 0xb7, 0x0e, 0x9a, 0xe0, 0xb0, 0xd0, 0xab, 0xcc, 0x27, + 0xda, 0xc1, 0xd8, 0x0e, 0x46, 0x1d, 0xfd, 0x67, 0x49, 0xb7, 0xf6, 0xe5, 0x79, 0xe0, 0x2a, 0xc7, + 0xb0, 0x94, 0xe6, 0xd9, 0x02, 0xa2, 0x9e, 0x6d, 0x82, 0xc3, 0x62, 0xbb, 0x62, 0xc8, 0xf1, 0xc6, + 0x72, 0xbc, 0xd1, 0x0d, 0xc7, 0xbd, 0xda, 0x7c, 0xa2, 0xdd, 0xd8, 0xc0, 0x12, 0x3d, 0xba, 0x55, + 0x44, 0x2b, 0x12, 0xfa, 0x7b, 0x00, 0xab, 0x92, 0x54, 0x9f, 0x86, 0x0c, 0x87, 0x2c, 0x61, 0xa2, + 0xc0, 0x76, 0xa1, 0xf7, 0x0c, 0x1e, 0xa0, 0x25, 0x8a, 0x9c, 0xc6, 0xea, 0xd9, 0xe6, 0xde, 0x56, + 0x8a, 0xb7, 0xe7, 0x13, 0xad, 0x96, 0xe2, 0x5d, 0xea, 0xd3, 0xad, 0x32, 0xda, 0x24, 0xa4, 0x7f, + 0x00, 0xb0, 0x22, 0xa9, 0x3e, 0x8e, 0x5c, 0x9b, 0xe3, 0xe3, 0x98, 0x46, 0x94, 0xd9, 0x23, 0xa5, + 0x02, 0xff, 0xe3, 0x84, 0x8f, 0xb0, 0x64, 0x69, 0xc9, 0x40, 0x69, 0xc2, 0xa2, 0x8b, 0x19, 0x8a, + 0x49, 0xc4, 0x09, 0x0d, 0x85, 0x55, 0x05, 0x6b, 0x3d, 0xb5, 0xa9, 0x70, 0xef, 0xb7, 0x14, 0xde, + 0x81, 0x79, 0x1f, 0xdb, 0x2e, 0x8e, 0xeb, 0xb9, 0xed, 0xd6, 0x5b, 0xe9, 0x9d, 0x4e, 0xee, 0xd5, + 0xb9, 0x96, 0xd1, 0x3f, 0x66, 0x61, 0x79, 0xc8, 0xbc, 0x7e, 0x8c, 0x6d, 0x8e, 0xa5, 0x80, 0x7f, + 0x62, 0xf7, 0xca, 0x13, 0x58, 0xbe, 0x64, 0xbb, 0x70, 0x61, 0x1b, 0x68, 0x63, 0x3e, 0xd1, 0x6e, + 0x5e, 0xb9, 0x2d, 0xdd, 0xfa, 0x7f, 0x73, 0x59, 0xca, 0x00, 0xe6, 0x19, 0xf1, 0xc2, 0xd4, 0xa7, + 0x52, 0xef, 0xe8, 0xfb, 0x44, 0x6b, 0x79, 0x84, 0xfb, 0x89, 0x63, 0x20, 0x1a, 0x98, 0x88, 0xb2, + 0x80, 0xb2, 0xf4, 0xd3, 0x62, 0xee, 0x73, 0x93, 0x8f, 0x23, 0xcc, 0x8c, 0x2e, 0x42, 0x5d, 0xd7, + 0x8d, 0x31, 0x63, 0x56, 0x0a, 0xa0, 0x7f, 0x02, 0xc2, 0x3e, 0xb9, 0xf3, 0xdd, 0xed, 0x5b, 0x6d, + 0x2e, 0xfb, 0xeb, 0xcd, 0xad, 0xf1, 0xdf, 0xfb, 0x53, 0xfe, 0x5f, 0x00, 0xac, 0x0e, 0x99, 0x77, + 0x92, 0x38, 0x01, 0xe1, 0x43, 0xc2, 0x1c, 0xec, 0xdb, 0x2f, 0x08, 0x4d, 0xe2, 0x5d, 0x54, 0xdc, + 0x87, 0xa5, 0x60, 0x0d, 0xe2, 0x5a, 0x2d, 0x1b, 0x37, 0xff, 0xa6, 0xa2, 0xd7, 0x00, 0xe6, 0x1f, + 0x60, 0xe2, 0xf9, 0x5c, 0xe9, 0xc0, 0x12, 0x8e, 0x28, 0xf2, 0x4f, 0xc3, 0x24, 0x70, 0x70, 0x2c, + 0x54, 0xe4, 0xd6, 0x7f, 0x7e, 0xeb, 0x55, 0xdd, 0x2a, 0x8a, 0xf0, 0x91, 0x88, 0x56, 0xbd, 0xbe, + 0xc0, 0x12, 0x5a, 0xae, 0xe8, 0x95, 0xd5, 0x65, 0xaf, 0x9c, 0xdb, 0xd9, 0x5f, 0xfc, 0xb3, 0xde, + 0x9e, 0x6b, 0x99, 0xde, 0xc3, 0xcf, 0x53, 0x15, 0x5c, 0x4c, 0x55, 0xf0, 0x6d, 0xaa, 0x82, 0x37, + 0x33, 0x35, 0x73, 0x31, 0x53, 0x33, 0x5f, 0x67, 0x6a, 0xe6, 0x69, 0xfb, 0x5a, 0x75, 0x2f, 0xcd, + 0xc5, 0x73, 0x7f, 0xb7, 0xdd, 0x4a, 0x5f, 0x7c, 0xa1, 0xd6, 0xc9, 0x0b, 0x07, 0xef, 0xfd, 0x08, + 0x00, 0x00, 0xff, 0xff, 0x57, 0xd4, 0xed, 0x68, 0x0c, 0x06, 0x00, 0x00, } func (m *IdentifiedClientState) Marshal() (dAtA []byte, err error) { @@ -523,6 +575,62 @@ func (m *ClientConsensusStates) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ClientUpdateProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientUpdateProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientUpdateProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Header != nil { + { + size, err := m.Header.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintClient(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintClient(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintClient(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintClient(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *MsgCreateClient) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -762,6 +870,31 @@ func (m *ClientConsensusStates) Size() (n int) { return n } +func (m *ClientUpdateProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovClient(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovClient(uint64(l)) + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovClient(uint64(l)) + } + if m.Header != nil { + l = m.Header.Size() + n += 1 + l + sovClient(uint64(l)) + } + return n +} + func (m *MsgCreateClient) Size() (n int) { if m == nil { return 0 @@ -1090,6 +1223,191 @@ func (m *ClientConsensusStates) Unmarshal(dAtA []byte) error { } return nil } +func (m *ClientUpdateProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientUpdateProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientUpdateProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Header == nil { + m.Header = &types.Any{} + } + if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipClient(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgCreateClient) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/ibc/02-client/types/codec_test.go b/x/ibc/02-client/types/codec_test.go index 381b32d2f7..cab2a2b706 100644 --- a/x/ibc/02-client/types/codec_test.go +++ b/x/ibc/02-client/types/codec_test.go @@ -33,7 +33,7 @@ func TestPackClientState(t *testing.T) { }, { "tendermint client", - ibctmtypes.NewClientState(chainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + ibctmtypes.NewClientState(chainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), true, }, { diff --git a/x/ibc/02-client/types/errors.go b/x/ibc/02-client/types/errors.go index 2526f16183..fe9099eaec 100644 --- a/x/ibc/02-client/types/errors.go +++ b/x/ibc/02-client/types/errors.go @@ -26,4 +26,6 @@ var ( ErrFailedPacketAckAbsenceVerification = sdkerrors.Register(SubModuleName, 19, "packet acknowledgement absence verification failed") ErrFailedNextSeqRecvVerification = sdkerrors.Register(SubModuleName, 20, "next sequence receive verification failed") ErrSelfConsensusStateNotFound = sdkerrors.Register(SubModuleName, 21, "self consensus state not found") + ErrUpdateClientFailed = sdkerrors.Register(SubModuleName, 22, "unable to update light client") + ErrInvalidUpdateClientProposal = sdkerrors.Register(SubModuleName, 23, "invalid update client proposal") ) diff --git a/x/ibc/02-client/types/events.go b/x/ibc/02-client/types/events.go index 08e83a4976..839ccb5084 100644 --- a/x/ibc/02-client/types/events.go +++ b/x/ibc/02-client/types/events.go @@ -15,9 +15,10 @@ const ( // IBC client events vars var ( - EventTypeCreateClient = "create_client" - EventTypeUpdateClient = "update_client" - EventTypeSubmitMisbehaviour = "client_misbehaviour" + EventTypeCreateClient = "create_client" + EventTypeUpdateClient = "update_client" + EventTypeSubmitMisbehaviour = "client_misbehaviour" + EventTypeUpdateClientProposal = "update_client_proposal" AttributeValueCategory = fmt.Sprintf("%s_%s", host.ModuleName, SubModuleName) ) diff --git a/x/ibc/02-client/types/genesis_test.go b/x/ibc/02-client/types/genesis_test.go index e3a251d27f..c4d84bd008 100644 --- a/x/ibc/02-client/types/genesis_test.go +++ b/x/ibc/02-client/types/genesis_test.go @@ -51,7 +51,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.IdentifiedClientState{ types.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), types.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chainID", clientHeight), @@ -76,7 +76,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.IdentifiedClientState{ types.NewIdentifiedClientState( - "/~@$*", ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + "/~@$*", ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), types.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chainID", clientHeight), @@ -101,7 +101,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.IdentifiedClientState{ types.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), types.NewIdentifiedClientState(exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", types.Height{})), }, @@ -115,7 +115,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.IdentifiedClientState{ types.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), types.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", clientHeight), @@ -140,7 +140,7 @@ func TestValidateGenesis(t *testing.T) { genState: types.NewGenesisState( []types.IdentifiedClientState{ types.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), types.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", clientHeight), diff --git a/x/ibc/02-client/types/msgs_test.go b/x/ibc/02-client/types/msgs_test.go index b2089ade39..4b91130844 100644 --- a/x/ibc/02-client/types/msgs_test.go +++ b/x/ibc/02-client/types/msgs_test.go @@ -52,7 +52,7 @@ func (suite *TypesTestSuite) TestMarshalMsgCreateClient() { }, { "tendermint client", func() { - tendermintClient := ibctmtypes.NewClientState(suite.chain.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()) + tendermintClient := ibctmtypes.NewClientState(suite.chain.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false) msg, err = types.NewMsgCreateClient("tendermint", tendermintClient, suite.chain.CreateTMClientHeader().ConsensusState(), suite.chain.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -104,7 +104,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { { "valid - tendermint client", func() { - tendermintClient := ibctmtypes.NewClientState(suite.chain.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()) + tendermintClient := ibctmtypes.NewClientState(suite.chain.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false) msg, err = types.NewMsgCreateClient("tendermint", tendermintClient, suite.chain.CreateTMClientHeader().ConsensusState(), suite.chain.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -128,7 +128,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { { "failed to unpack consensus state", func() { - tendermintClient := ibctmtypes.NewClientState(suite.chain.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()) + tendermintClient := ibctmtypes.NewClientState(suite.chain.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false) msg, err = types.NewMsgCreateClient("tendermint", tendermintClient, suite.chain.CreateTMClientHeader().ConsensusState(), suite.chain.SenderAccount.GetAddress()) suite.Require().NoError(err) msg.ConsensusState = nil diff --git a/x/ibc/02-client/types/proposal.go b/x/ibc/02-client/types/proposal.go new file mode 100644 index 0000000000..7f9abb004a --- /dev/null +++ b/x/ibc/02-client/types/proposal.go @@ -0,0 +1,55 @@ +package types + +import ( + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" +) + +const ( + // ProposalTypeClientUpdate defines the type for a ClientUpdateProposal + ProposalTypeClientUpdate = "ClientUpdate" +) + +var _ govtypes.Content = &ClientUpdateProposal{} + +// NewClientUpdateProposal creates a new client update proposal. +func NewClientUpdateProposal(title, description, clientID string, header exported.Header) (*ClientUpdateProposal, error) { + any, err := PackHeader(header) + if err != nil { + return nil, err + } + + return &ClientUpdateProposal{ + Title: title, + Description: description, + ClientId: clientID, + Header: any, + }, nil +} + +// GetTitle returns the title of a client update proposal. +func (cup *ClientUpdateProposal) GetTitle() string { return cup.Title } + +// GetDescription returns the description of a client update proposal. +func (cup *ClientUpdateProposal) GetDescription() string { return cup.Description } + +// GetDescription returns the routing key of a client update proposal. +func (cup *ClientUpdateProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a client update proposal. +func (cup *ClientUpdateProposal) ProposalType() string { return ProposalTypeClientUpdate } + +// ValidateBasic runs basic stateless validity checks +func (cup *ClientUpdateProposal) ValidateBasic() error { + err := govtypes.ValidateAbstract(cup) + if err != nil { + return err + } + + header, err := UnpackHeader(cup.Header) + if err != nil { + return err + } + + return header.ValidateBasic() +} diff --git a/x/ibc/02-client/types/proposal_test.go b/x/ibc/02-client/types/proposal_test.go new file mode 100644 index 0000000000..09ae1adc5f --- /dev/null +++ b/x/ibc/02-client/types/proposal_test.go @@ -0,0 +1,77 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" +) + +func TestNewUpdateClientProposal(t *testing.T) { + p, err := types.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, clientID, &ibctmtypes.Header{}) + require.NoError(t, err) + require.NotNil(t, p) + + p, err = types.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, clientID, nil) + require.Error(t, err) + require.Nil(t, p) +} + +func TestValidateBasic(t *testing.T) { + // use solo machine header for testing + solomachine := ibctesting.NewSolomachine(t, clientID) + smHeader := solomachine.CreateHeader() + header, err := types.PackHeader(smHeader) + require.NoError(t, err) + + // use a different pointer so we don't modify 'header' + smInvalidHeader := solomachine.CreateHeader() + + // a sequence of 0 will fail basic validation + smInvalidHeader.Sequence = 0 + + invalidHeader, err := types.PackHeader(smInvalidHeader) + require.NoError(t, err) + + testCases := []struct { + name string + proposal govtypes.Content + expPass bool + }{ + { + "success", + &types.ClientUpdateProposal{ibctesting.Title, ibctesting.Description, clientID, header}, + true, + }, + { + "fails validate abstract - empty title", + &types.ClientUpdateProposal{"", ibctesting.Description, clientID, header}, + false, + }, + { + "fails to unpack header", + &types.ClientUpdateProposal{ibctesting.Title, ibctesting.Description, clientID, nil}, + false, + }, + { + "fails header validate basic", + &types.ClientUpdateProposal{ibctesting.Title, ibctesting.Description, clientID, invalidHeader}, + false, + }, + } + + for _, tc := range testCases { + + err := tc.proposal.ValidateBasic() + + if tc.expPass { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} diff --git a/x/ibc/02-client/types/query.pb.gw.go b/x/ibc/02-client/types/query.pb.gw.go index 3fd7e0efa1..85d7a07e39 100644 --- a/x/ibc/02-client/types/query.pb.gw.go +++ b/x/ibc/02-client/types/query.pb.gw.go @@ -312,6 +312,7 @@ func local_request_Query_ConsensusStates_0(ctx context.Context, marshaler runtim // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_ClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/ibc/03-connection/types/msgs_test.go b/x/ibc/03-connection/types/msgs_test.go index 3ac72fc3cb..5d0a0d84b7 100644 --- a/x/ibc/03-connection/types/msgs_test.go +++ b/x/ibc/03-connection/types/msgs_test.go @@ -107,7 +107,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { signer, _ := sdk.AccAddressFromBech32("cosmos1ckgw5d7jfj7wwxjzs9fdrdev9vc8dzcw3n2lht") clientState := ibctmtypes.NewClientState( - chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), + chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false, ) // Pack consensus state into any to test unpacking error @@ -119,7 +119,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { // invalidClientState fails validateBasic invalidClient := ibctmtypes.NewClientState( - chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), + chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), false, false, ) testMsgs := []*types.MsgConnectionOpenTry{ @@ -179,7 +179,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { signer, _ := sdk.AccAddressFromBech32("cosmos1ckgw5d7jfj7wwxjzs9fdrdev9vc8dzcw3n2lht") clientState := ibctmtypes.NewClientState( - chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), + chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false, ) // Pack consensus state into any to test unpacking error @@ -190,7 +190,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { // invalidClientState fails validateBasic invalidClient := ibctmtypes.NewClientState( - chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), + chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), false, false, ) testMsgs := []*types.MsgConnectionOpenAck{ diff --git a/x/ibc/03-connection/types/query.pb.gw.go b/x/ibc/03-connection/types/query.pb.gw.go index 6e2f3323cb..54c52c6353 100644 --- a/x/ibc/03-connection/types/query.pb.gw.go +++ b/x/ibc/03-connection/types/query.pb.gw.go @@ -330,6 +330,7 @@ func local_request_Query_ConnectionConsensusState_0(ctx context.Context, marshal // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Connection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/ibc/04-channel/types/query.pb.gw.go b/x/ibc/04-channel/types/query.pb.gw.go index 1181eca5aa..933cba2d78 100644 --- a/x/ibc/04-channel/types/query.pb.gw.go +++ b/x/ibc/04-channel/types/query.pb.gw.go @@ -900,6 +900,7 @@ func local_request_Query_NextSequenceReceive_0(ctx context.Context, marshaler ru // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Channel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/ibc/07-tendermint/client/cli/tx.go b/x/ibc/07-tendermint/client/cli/tx.go index f2ba79a9cc..81d074c904 100644 --- a/x/ibc/07-tendermint/client/cli/tx.go +++ b/x/ibc/07-tendermint/client/cli/tx.go @@ -23,8 +23,10 @@ import ( ) const ( - flagTrustLevel = "trust-level" - flagProofSpecs = "proof-specs" + flagTrustLevel = "trust-level" + flagProofSpecs = "proof-specs" + flagAllowUpdateAfterExpiry = "allow_update_after_expiry" + flagAllowUpdateAfterMisbehaviour = "allow_update_after_misbehaviour" ) // NewCreateClientCmd defines the command to create a new IBC Client as defined @@ -111,6 +113,9 @@ func NewCreateClientCmd() *cobra.Command { } } + allowUpdateAfterExpiry, _ := cmd.Flags().GetBool(flagAllowUpdateAfterExpiry) + allowUpdateAfterMisbehaviour, _ := cmd.Flags().GetBool(flagAllowUpdateAfterMisbehaviour) + // validate header if err := header.ValidateBasic(); err != nil { return err @@ -118,7 +123,8 @@ func NewCreateClientCmd() *cobra.Command { height := header.GetHeight().(clienttypes.Height) clientState := types.NewClientState( - header.GetHeader().GetChainID(), trustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, specs, + header.GetHeader().GetChainID(), trustLevel, trustingPeriod, ubdPeriod, maxClockDrift, + height, specs, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, ) consensusState := header.ConsensusState() @@ -140,6 +146,8 @@ func NewCreateClientCmd() *cobra.Command { cmd.Flags().String(flagTrustLevel, "default", "light client trust level fraction for header updates") cmd.Flags().String(flagProofSpecs, "default", "proof specs format to be used for verification") + cmd.Flags().Bool(flagAllowUpdateAfterExpiry, false, "allow governance proposal to update client after expiry") + cmd.Flags().Bool(flagAllowUpdateAfterMisbehaviour, false, "allow governance proposal to update client after misbehaviour") flags.AddTxFlagsToCmd(cmd) return cmd diff --git a/x/ibc/07-tendermint/types/client_state.go b/x/ibc/07-tendermint/types/client_state.go index 258ec0cd73..0b919f77b1 100644 --- a/x/ibc/07-tendermint/types/client_state.go +++ b/x/ibc/07-tendermint/types/client_state.go @@ -25,16 +25,19 @@ func NewClientState( chainID string, trustLevel Fraction, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, latestHeight clienttypes.Height, specs []*ics23.ProofSpec, + allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool, ) *ClientState { return &ClientState{ - ChainId: chainID, - TrustLevel: trustLevel, - TrustingPeriod: trustingPeriod, - UnbondingPeriod: ubdPeriod, - MaxClockDrift: maxClockDrift, - LatestHeight: latestHeight, - FrozenHeight: clienttypes.Height{}, - ProofSpecs: specs, + ChainId: chainID, + TrustLevel: trustLevel, + TrustingPeriod: trustingPeriod, + UnbondingPeriod: ubdPeriod, + MaxClockDrift: maxClockDrift, + LatestHeight: latestHeight, + FrozenHeight: clienttypes.Height{}, + ProofSpecs: specs, + AllowUpdateAfterExpiry: allowUpdateAfterExpiry, + AllowUpdateAfterMisbehaviour: allowUpdateAfterMisbehaviour, } } @@ -64,6 +67,13 @@ func (cs ClientState) GetFrozenHeight() exported.Height { return cs.FrozenHeight } +// IsExpired returns whether or not the client has passed the trusting period since the last +// update (in which case no headers are considered valid). +func (cs ClientState) IsExpired(latestTimestamp, now time.Time) bool { + expirationTime := latestTimestamp.Add(cs.TrustingPeriod) + return !expirationTime.After(now) +} + // Validate performs a basic validation of the client state fields. func (cs ClientState) Validate() error { if strings.TrimSpace(cs.ChainId) == "" { @@ -90,7 +100,6 @@ func (cs ClientState) Validate() error { "trusting period (%s) should be < unbonding period (%s)", cs.TrustingPeriod, cs.UnbondingPeriod, ) } - // Validate ProofSpecs if cs.ProofSpecs == nil { return sdkerrors.Wrap(ErrInvalidProofSpecs, "proof specs cannot be nil for tm client") } diff --git a/x/ibc/07-tendermint/types/client_state_test.go b/x/ibc/07-tendermint/types/client_state_test.go index b275a50c3f..bfd6ee2c43 100644 --- a/x/ibc/07-tendermint/types/client_state_test.go +++ b/x/ibc/07-tendermint/types/client_state_test.go @@ -32,52 +32,52 @@ func (suite *TendermintTestSuite) TestValidate() { }{ { name: "valid client", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: true, }, { name: "invalid chainID", - clientState: types.NewClientState(" ", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(" ", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "invalid trust level", - clientState: types.NewClientState(chainID, types.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "invalid trusting period", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "invalid unbonding period", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "invalid max clock drift", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "invalid height", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.Height{}, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "trusting period not less than unbonding period", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), expPass: false, }, { name: "proof specs is nil", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, nil), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, nil, false, false), expPass: false, }, { name: "proof specs contains nil", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, false, false), expPass: false, }, } @@ -113,7 +113,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { // }, { name: "ApplyPrefix failed", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), consensusState: types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), }, @@ -122,7 +122,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { }, { name: "latest client height < height", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), consensusState: types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), }, @@ -140,7 +140,7 @@ func (suite *TendermintTestSuite) TestVerifyClientConsensusState() { }, { name: "proof verification failed", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), consensusState: types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), NextValidatorsHash: suite.valsHash, diff --git a/x/ibc/07-tendermint/types/codec.go b/x/ibc/07-tendermint/types/codec.go index 9a2c953d98..f82b74efc4 100644 --- a/x/ibc/07-tendermint/types/codec.go +++ b/x/ibc/07-tendermint/types/codec.go @@ -6,7 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/exported" ) -// RegisterInterfaces registers the tendermint concrete evidence and client-related +// RegisterInterfaces registers the tendermint concrete client-related // implementations and interfaces. func RegisterInterfaces(registry codectypes.InterfaceRegistry) { registry.RegisterImplementations( diff --git a/x/ibc/07-tendermint/types/misbehaviour_handle_test.go b/x/ibc/07-tendermint/types/misbehaviour_handle_test.go index 6d62948b3e..ca891dcfbf 100644 --- a/x/ibc/07-tendermint/types/misbehaviour_handle_test.go +++ b/x/ibc/07-tendermint/types/misbehaviour_handle_test.go @@ -49,7 +49,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }{ { "valid misbehavior misbehaviour", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -63,7 +63,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehavior at height greater than last consensusState", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus1, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus1, bothValsHash), &types.Misbehaviour{ @@ -77,7 +77,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour from different chain", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -91,7 +91,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehavior misbehaviour with different trusted heights", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus1, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus3, suite.valsHash), &types.Misbehaviour{ @@ -105,7 +105,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "consensus state's valset hash different from misbehaviour should still pass", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, suite.valsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, suite.valsHash), &types.Misbehaviour{ @@ -119,7 +119,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour with trusted height different from trusted consensus state", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus1, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus3, suite.valsHash), &types.Misbehaviour{ @@ -133,7 +133,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour with trusted validators different from trusted consensus state", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus1, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus3, suite.valsHash), &types.Misbehaviour{ @@ -147,7 +147,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "already frozen client state", - types.ClientState{FrozenHeight: clienttypes.NewHeight(0, 1)}, + &types.ClientState{FrozenHeight: clienttypes.NewHeight(0, 1)}, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -161,7 +161,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "trusted consensus state does not exist", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), nil, // consensus state for trusted height - 1 does not exist in store types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -175,7 +175,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid tendermint misbehaviour", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), nil, @@ -184,7 +184,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "rejected misbehaviour due to expired age duration", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -198,7 +198,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "rejected misbehaviour due to expired block duration", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(0, uint64(epochHeight+simapp.DefaultConsensusParams.Evidence.MaxAgeNumBlocks+1)), commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(0, uint64(epochHeight+simapp.DefaultConsensusParams.Evidence.MaxAgeNumBlocks+1)), commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -212,7 +212,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "provided height > header height", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -226,7 +226,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "unbonding period expired", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(time.Time{}, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), heightMinus1, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -240,7 +240,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "trusted validators is incorrect for given consensus state", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -254,7 +254,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "first valset has too much change", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -268,7 +268,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "second valset has too much change", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ @@ -282,7 +282,7 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "both valsets have too much change", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), height, bothValsHash), &types.Misbehaviour{ diff --git a/x/ibc/07-tendermint/types/proposal_handle.go b/x/ibc/07-tendermint/types/proposal_handle.go new file mode 100644 index 0000000000..fbcf9a0d12 --- /dev/null +++ b/x/ibc/07-tendermint/types/proposal_handle.go @@ -0,0 +1,127 @@ +package types + +import ( + "time" + + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" +) + +// CheckProposedHeaderAndUpdateState will try to update the client with the new header if and +// only if the proposal passes and one of the following two conditions is satisfied: +// 1) AllowUpdateAfterExpiry=true and Expire(ctx.BlockTime) = true +// 2) AllowUpdateAfterMisbehaviour and IsFrozen() = true +// In case 2) before trying to update the client, the client will be unfrozen by resetting +// the FrozenHeight to the zero Height. If AllowUpdateAfterMisbehaviour is set to true, +// expired clients will also be updated even if AllowUpdateAfterExpiry is set to false. +// Note, that even if the update happens, it may not be successful. The header may fail +// validation checks and an error will be returned in that case. +func (cs ClientState) CheckProposedHeaderAndUpdateState( + ctx sdk.Context, cdc codec.BinaryMarshaler, clientStore sdk.KVStore, + header exported.Header, +) (exported.ClientState, exported.ConsensusState, error) { + tmHeader, ok := header.(*Header) + if !ok { + return nil, nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, "expected type %T, got %T", &Header{}, header, + ) + } + + // get consensus state corresponding to client state to check if the client is expired + consensusState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) + if err != nil { + return nil, nil, sdkerrors.Wrapf( + err, "could not get consensus state from clientstore at height: %d", cs.GetLatestHeight(), + ) + } + + switch { + + case cs.IsFrozen(): + if !cs.AllowUpdateAfterMisbehaviour { + return nil, nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unfrozen") + } + + // unfreeze the client + cs.FrozenHeight = clienttypes.Height{} + + // if the client is expired we unexpire the client using softer validation, otherwise + // full validation on the header is performed. + if cs.IsExpired(consensusState.Timestamp, ctx.BlockTime()) { + return cs.unexpireClient(consensusState, tmHeader, ctx.BlockTime()) + } + + // NOTE: the client may be frozen again since the misbehaviour evidence may + // not be expired yet + return cs.CheckHeaderAndUpdateState(ctx, cdc, clientStore, header) + + case cs.AllowUpdateAfterExpiry && cs.IsExpired(consensusState.Timestamp, ctx.BlockTime()): + return cs.unexpireClient(consensusState, tmHeader, ctx.BlockTime()) + + default: + return nil, nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client cannot be updated with proposal") + } + +} + +// unexpireClient checks if the proposed header is sufficient to update an expired client. +// The client is updated if no error occurs. +func (cs ClientState) unexpireClient( + consensusState *ConsensusState, header *Header, currentTimestamp time.Time, +) (exported.ClientState, exported.ConsensusState, error) { + + // the client is expired and either AllowUpdateAfterMisbehaviour or AllowUpdateAfterExpiry + // is set to true so light validation of the header is executed + if err := cs.checkProposedHeader(consensusState, header, currentTimestamp); err != nil { + return nil, nil, err + } + + newClientState, consensusState := update(&cs, header) + return newClientState, consensusState, nil +} + +// checkProposedHeader checks if the Tendermint header is valid for updating a client after +// a passed proposal. +// It returns an error if: +// - the header provided is not parseable to tendermint types +// - header height is less than or equal to the latest client state height +// - signed tendermint header is invalid +// - header timestamp is less than or equal to the latest consensus state timestamp +// - header timestamp is expired +// NOTE: header.ValidateBasic is called in the 02-client proposal handler. Additional checks +// on the validator set and the validator set hash are done in header.ValidateBasic. +func (cs ClientState) checkProposedHeader(consensusState *ConsensusState, header *Header, currentTimestamp time.Time) error { + tmSignedHeader, err := tmtypes.SignedHeaderFromProto(header.SignedHeader) + if err != nil { + return sdkerrors.Wrap(err, "signed header in not tendermint signed header type") + } + + if !header.GetTime().After(consensusState.Timestamp) { + return sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, + "header timestamp is less than or equal to latest consensus state timestamp (%s ≤ %s)", header.GetTime(), consensusState.Timestamp) + } + + // assert header height is newer than latest client state + if header.GetHeight().LTE(cs.GetLatestHeight()) { + return sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, + "header height ≤ consensus state height (%s ≤ %s)", header.GetHeight(), cs.GetLatestHeight(), + ) + } + + if err := tmSignedHeader.ValidateBasic(cs.GetChainID()); err != nil { + return sdkerrors.Wrap(err, "signed header failed basic validation") + } + + if cs.IsExpired(header.GetTime(), currentTimestamp) { + return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "header timestamp is already expired") + } + + return nil +} diff --git a/x/ibc/07-tendermint/types/proposal_handle_test.go b/x/ibc/07-tendermint/types/proposal_handle_test.go new file mode 100644 index 0000000000..cc343c6d79 --- /dev/null +++ b/x/ibc/07-tendermint/types/proposal_handle_test.go @@ -0,0 +1,356 @@ +package types_test + +import ( + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" +) + +var ( + frozenHeight = clienttypes.NewHeight(0, 1) +) + +// sanity checks +func (suite *TendermintTestSuite) TestCheckProposedHeaderAndUpdateStateBasic() { + clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + clientState := suite.chainA.GetClientState(clientA).(*types.ClientState) + clientStore := suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientA) + + // use nil header + cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, nil) + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + + clientState.LatestHeight = clientState.LatestHeight.Increment() + + // consensus state for latest height does not exist + cs, consState, err = clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, suite.chainA.LastHeader) + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) +} + +// to expire clients, time needs to be fast forwarded on both chainA and chainB. +// this is to prevent headers from failing when attempting to update later. +func (suite *TendermintTestSuite) TestCheckProposedHeaderAndUpdateState() { + testCases := []struct { + name string + AllowUpdateAfterExpiry bool + AllowUpdateAfterMisbehaviour bool + FreezeClient bool + ExpireClient bool + expPassUnfreeze bool // expected result using a header that passes stronger validation + expPassUnexpire bool // expected result using a header that passes weaker validation + }{ + { + name: "not allowed to be updated, not frozen or expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: false, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "not allowed to be updated, client is frozen", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: false, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "not allowed to be updated, client is expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: true, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "not allowed to be updated, client is frozen and expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: true, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "allowed to be updated only after misbehaviour, not frozen or expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: false, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "PASS: allowed to be updated only after misbehaviour, client is frozen", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: false, + expPassUnfreeze: true, + expPassUnexpire: false, + }, + { + name: "allowed to be updated only after misbehaviour, client is expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: true, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "allowed to be updated only after misbehaviour, client is frozen and expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: true, + expPassUnfreeze: true, + expPassUnexpire: true, + }, + { + name: "allowed to be updated only after expiry, not frozen or expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: false, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "allowed to be updated only after expiry, client is frozen", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: false, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "PASS: allowed to be updated only after expiry, client is expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: true, + expPassUnfreeze: true, + expPassUnexpire: true, + }, + { + name: "allowed to be updated only after expiry, client is frozen and expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: true, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "allowed to be updated after expiry and misbehaviour, not frozen or expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: false, + expPassUnfreeze: false, + expPassUnexpire: false, + }, + { + name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: false, + expPassUnfreeze: true, + expPassUnexpire: false, + }, + { + name: "PASS: allowed to be updated after expiry and misbehaviour, client is expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: true, + expPassUnfreeze: true, + expPassUnexpire: true, + }, + { + name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen and expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: true, + expPassUnfreeze: true, + expPassUnexpire: true, + }, + } + + for _, tc := range testCases { + tc := tc + + // for each test case a header used for unexpiring clients and unfreezing + // a client are each tested to ensure that unexpiry headers cannot update + // a client when a unfreezing header is required. + suite.Run(tc.name, func() { + + // start by testing unexpiring the client + suite.SetupTest() // reset + + // construct client state based on test case parameters + clientA, _ := suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + clientState := suite.chainA.GetClientState(clientA).(*types.ClientState) + clientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + clientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + if tc.FreezeClient { + clientState.FrozenHeight = frozenHeight + } + if tc.ExpireClient { + suite.chainA.ExpireClient(clientState.TrustingPeriod) + suite.chainB.ExpireClient(clientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + } + + // use next header for chainB to unfreeze client on chainA + unfreezeClientHeader, err := suite.chainA.ConstructUpdateTMClientHeader(suite.chainB, clientA) + suite.Require().NoError(err) + + clientStore := suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientA) + cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, unfreezeClientHeader) + + if tc.expPassUnfreeze { + suite.Require().NoError(err) + suite.Require().Equal(clienttypes.Height{}, cs.GetFrozenHeight()) + suite.Require().NotNil(consState) + } else { + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + } + + // use next header for chainB to unexpire clients but with empty trusted heights + // and validators. Update chainB time so header won't be expired. + unexpireClientHeader, err := suite.chainA.ConstructUpdateTMClientHeader(suite.chainB, clientA) + suite.Require().NoError(err) + unexpireClientHeader.TrustedHeight = clienttypes.Height{} + unexpireClientHeader.TrustedValidators = nil + + clientStore = suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientA) + cs, consState, err = clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, unexpireClientHeader) + + if tc.expPassUnexpire { + suite.Require().NoError(err) + suite.Require().NotNil(cs) + suite.Require().NotNil(consState) + } else { + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + } + }) + } +} + +// test softer validation on headers used for unexpiring clients +func (suite *TendermintTestSuite) TestCheckProposedHeader() { + var ( + header *types.Header + clientState *types.ClientState + clientA string + err error + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", func() {}, true, + }, + { + "invalid signed header", func() { + header.SignedHeader = nil + }, false, + }, + { + "header time is less than or equal to consensus state timestamp", func() { + consensusState, found := suite.chainA.GetConsensusState(clientA, clientState.GetLatestHeight()) + suite.Require().True(found) + consensusState.(*types.ConsensusState).Timestamp = header.GetTime() + suite.chainA.App.IBCKeeper.ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientA, clientState.GetLatestHeight(), consensusState) + + // update block time so client is expired + suite.chainA.ExpireClient(clientState.TrustingPeriod) + suite.chainB.ExpireClient(clientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + }, false, + }, + { + "header height is not newer than client state", func() { + consensusState, found := suite.chainA.GetConsensusState(clientA, clientState.GetLatestHeight()) + suite.Require().True(found) + clientState.LatestHeight = header.GetHeight().(clienttypes.Height) + suite.chainA.App.IBCKeeper.ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientA, clientState.GetLatestHeight(), consensusState) + + }, false, + }, + { + "signed header failed validate basic - wrong chain ID", func() { + clientState.ChainId = ibctesting.InvalidID + }, false, + }, + { + "header is already expired", func() { + // expire client + suite.chainA.ExpireClient(clientState.TrustingPeriod) + suite.chainB.ExpireClient(clientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + clientA, _ = suite.coordinator.SetupClients(suite.chainA, suite.chainB, exported.Tendermint) + clientState = suite.chainA.GetClientState(clientA).(*types.ClientState) + clientState.AllowUpdateAfterExpiry = true + clientState.AllowUpdateAfterMisbehaviour = false + + // expire client + suite.chainA.ExpireClient(clientState.TrustingPeriod) + suite.chainB.ExpireClient(clientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + + // use next header for chainB to unexpire clients but with empty trusted heights + // and validators. + header, err = suite.chainA.ConstructUpdateTMClientHeader(suite.chainB, clientA) + suite.Require().NoError(err) + header.TrustedHeight = clienttypes.Height{} + header.TrustedValidators = nil + + tc.malleate() + + clientStore := suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientA) + cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(cs) + suite.Require().NotNil(consState) + } else { + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + } + }) + } +} diff --git a/x/ibc/07-tendermint/types/tendermint.pb.go b/x/ibc/07-tendermint/types/tendermint.pb.go index 686d96cadf..c7e462974e 100644 --- a/x/ibc/07-tendermint/types/tendermint.pb.go +++ b/x/ibc/07-tendermint/types/tendermint.pb.go @@ -51,6 +51,12 @@ type ClientState struct { LatestHeight types.Height `protobuf:"bytes,7,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height" yaml:"latest_height"` // Proof specifications used in verifying counterparty state ProofSpecs []*_go.ProofSpec `protobuf:"bytes,8,rep,name=proof_specs,json=proofSpecs,proto3" json:"proof_specs,omitempty" yaml:"proof_specs"` + // This flag, when set to true, will allow governance to recover a client + // which has expired + AllowUpdateAfterExpiry bool `protobuf:"varint,9,opt,name=allow_update_after_expiry,json=allowUpdateAfterExpiry,proto3" json:"allow_update_after_expiry,omitempty" yaml:"allow_update_after_expiry"` + // This flag, when set to true, will allow governance to unfreeze a client + // whose chain has experienced a misbehaviour event + AllowUpdateAfterMisbehaviour bool `protobuf:"varint,10,opt,name=allow_update_after_misbehaviour,json=allowUpdateAfterMisbehaviour,proto3" json:"allow_update_after_misbehaviour,omitempty" yaml:"allow_update_after_misbehaviour"` } func (m *ClientState) Reset() { *m = ClientState{} } @@ -309,68 +315,72 @@ func init() { func init() { proto.RegisterFile("ibc/tendermint/tendermint.proto", fileDescriptor_76a953d5a747dd66) } var fileDescriptor_76a953d5a747dd66 = []byte{ - // 963 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xcd, 0x4e, 0x23, 0x47, - 0x10, 0xf6, 0x0f, 0x0b, 0xa6, 0x6d, 0x03, 0xe9, 0x25, 0xac, 0x71, 0x58, 0x8f, 0xd5, 0xb9, 0x70, - 0xd9, 0x99, 0xc5, 0xbb, 0x4a, 0x24, 0x8e, 0xc3, 0x2a, 0x82, 0x28, 0x2b, 0x91, 0x21, 0x9b, 0x44, - 0x91, 0xa2, 0xd1, 0x78, 0xa6, 0x6d, 0xb7, 0x98, 0x99, 0xb6, 0xa6, 0xdb, 0xc8, 0xe4, 0x09, 0x92, - 0xdb, 0x2a, 0xa7, 0x3d, 0x26, 0x6f, 0x91, 0x47, 0xd8, 0x53, 0xc4, 0x31, 0x27, 0x27, 0x82, 0x37, - 0xf0, 0x31, 0xa7, 0xa8, 0x7f, 0xe6, 0xc7, 0x86, 0x08, 0xf6, 0x82, 0xbb, 0xab, 0xbe, 0xef, 0xab, - 0xee, 0xea, 0xaa, 0x1a, 0x80, 0x41, 0xfa, 0xbe, 0xc5, 0x71, 0x1c, 0xe0, 0x24, 0x22, 0x31, 0x2f, - 0x2c, 0xcd, 0x71, 0x42, 0x39, 0x85, 0x1b, 0xa4, 0xef, 0x9b, 0xb9, 0xb5, 0xdd, 0x2d, 0x82, 0x2f, - 0xc7, 0x98, 0x59, 0x17, 0x5e, 0x48, 0x02, 0x8f, 0xd3, 0x44, 0x31, 0xda, 0x7b, 0xb7, 0x10, 0xf2, - 0xaf, 0xf6, 0x3e, 0xf6, 0x69, 0x3c, 0x20, 0xd4, 0x1a, 0x27, 0x94, 0x0e, 0x52, 0x63, 0x67, 0x48, - 0xe9, 0x30, 0xc4, 0x96, 0xdc, 0xf5, 0x27, 0x03, 0x2b, 0x98, 0x24, 0x1e, 0x27, 0x34, 0xd6, 0x7e, - 0x63, 0xd9, 0xcf, 0x49, 0x84, 0x19, 0xf7, 0xa2, 0xb1, 0x06, 0x3c, 0x11, 0xd7, 0xf0, 0x43, 0x82, - 0x63, 0xae, 0x7f, 0x52, 0xa6, 0x74, 0xd0, 0x28, 0x22, 0x3c, 0x92, 0xce, 0x6c, 0xa9, 0x01, 0xdb, - 0x43, 0x3a, 0xa4, 0x72, 0x69, 0x89, 0x95, 0xb2, 0xa2, 0x3f, 0x1e, 0x81, 0xfa, 0x91, 0xd4, 0x39, - 0xe3, 0x1e, 0xc7, 0x70, 0x17, 0xd4, 0xfc, 0x91, 0x47, 0x62, 0x97, 0x04, 0xad, 0x72, 0xb7, 0xbc, - 0xbf, 0xee, 0xac, 0xc9, 0xfd, 0x49, 0x00, 0xdf, 0x80, 0x3a, 0x4f, 0x26, 0x8c, 0xbb, 0x21, 0xbe, - 0xc0, 0x61, 0xab, 0xd2, 0x2d, 0xef, 0xd7, 0x7b, 0x2d, 0x73, 0x31, 0x6d, 0xe6, 0x17, 0x89, 0xe7, - 0x8b, 0x0b, 0xd9, 0xed, 0xf7, 0x33, 0xa3, 0x34, 0x9f, 0x19, 0xf0, 0xd2, 0x8b, 0xc2, 0x43, 0x54, - 0xa0, 0x22, 0x07, 0xc8, 0xdd, 0x57, 0x62, 0x03, 0x07, 0x60, 0x53, 0xee, 0x48, 0x3c, 0x74, 0xc7, - 0x38, 0x21, 0x34, 0x68, 0x55, 0xa5, 0xf4, 0xae, 0xa9, 0x92, 0x61, 0xa6, 0xc9, 0x30, 0x5f, 0xe9, - 0x64, 0xd9, 0x48, 0x6b, 0xef, 0x14, 0xb4, 0x73, 0x3e, 0x7a, 0xf7, 0xb7, 0x51, 0x76, 0x36, 0x52, - 0xeb, 0xa9, 0x34, 0x42, 0x02, 0xb6, 0x26, 0x71, 0x9f, 0xc6, 0x41, 0x21, 0xd0, 0xca, 0x7d, 0x81, - 0x3e, 0xd5, 0x81, 0x9e, 0xa8, 0x40, 0xcb, 0x02, 0x2a, 0xd2, 0x66, 0x66, 0xd6, 0xa1, 0x30, 0xd8, - 0x8c, 0xbc, 0xa9, 0xeb, 0x87, 0xd4, 0x3f, 0x77, 0x83, 0x84, 0x0c, 0x78, 0xeb, 0xd1, 0x07, 0x5e, - 0x69, 0x89, 0xaf, 0x02, 0x35, 0x23, 0x6f, 0x7a, 0x24, 0x8c, 0xaf, 0x84, 0x0d, 0xbe, 0x01, 0xcd, - 0x41, 0x42, 0x7f, 0xc2, 0xb1, 0x3b, 0xc2, 0x64, 0x38, 0xe2, 0xad, 0x55, 0x19, 0x04, 0xca, 0x27, - 0xd1, 0xc5, 0x71, 0x2c, 0x3d, 0xf6, 0x9e, 0x56, 0xdf, 0x56, 0xea, 0x0b, 0x34, 0xe4, 0x34, 0xd4, - 0x5e, 0x61, 0x85, 0x6c, 0xe8, 0x71, 0xcc, 0x78, 0x2a, 0xbb, 0xf6, 0x50, 0xd9, 0x05, 0x1a, 0x72, - 0x1a, 0x6a, 0xaf, 0x65, 0x4f, 0x40, 0x5d, 0xb6, 0x82, 0xcb, 0xc6, 0xd8, 0x67, 0xad, 0x5a, 0xb7, - 0xba, 0x5f, 0xef, 0x6d, 0x99, 0xc4, 0x67, 0xbd, 0x17, 0xe6, 0xa9, 0xf0, 0x9c, 0x8d, 0xb1, 0x6f, - 0xef, 0xe4, 0x25, 0x53, 0x80, 0x23, 0x07, 0x8c, 0x53, 0x08, 0x3b, 0x5c, 0xf9, 0xf9, 0x37, 0xa3, - 0x84, 0xfe, 0xac, 0x80, 0x8d, 0x23, 0x1a, 0x33, 0x1c, 0xb3, 0x09, 0x53, 0xd5, 0x6b, 0x83, 0xf5, - 0xac, 0x61, 0x64, 0xf9, 0xd6, 0x7b, 0xed, 0x5b, 0x29, 0xff, 0x26, 0x45, 0xd8, 0x35, 0x71, 0xfc, - 0xb7, 0x22, 0xb3, 0x39, 0x0d, 0xbe, 0x04, 0x2b, 0x09, 0xa5, 0x5c, 0xd7, 0x77, 0x5b, 0xdd, 0x3a, - 0x6f, 0xa6, 0xd7, 0x38, 0x39, 0x0f, 0xb1, 0x43, 0x29, 0xb7, 0x57, 0x04, 0xdd, 0x91, 0x68, 0xf8, - 0x1c, 0xac, 0xea, 0x6c, 0x55, 0xff, 0x37, 0x5b, 0x0a, 0xaf, 0x71, 0xf0, 0x97, 0x32, 0xd8, 0x8e, - 0xf1, 0x94, 0xbb, 0xd9, 0x58, 0x61, 0xee, 0xc8, 0x63, 0x23, 0x59, 0x94, 0x0d, 0xfb, 0xbb, 0xf9, - 0xcc, 0xf8, 0x44, 0xe5, 0xe1, 0x2e, 0x14, 0xfa, 0x77, 0x66, 0xbc, 0x1c, 0x12, 0x3e, 0x9a, 0xf4, - 0xc5, 0xe9, 0xee, 0x9e, 0x6c, 0x56, 0x48, 0xfa, 0xcc, 0xea, 0x5f, 0x72, 0xcc, 0xcc, 0x63, 0x3c, - 0xb5, 0xc5, 0xc2, 0x81, 0x42, 0xee, 0xdb, 0x4c, 0xed, 0xd8, 0x63, 0x23, 0x9d, 0xd0, 0xdf, 0x2b, - 0xa0, 0xf1, 0x9a, 0xb0, 0x3e, 0x1e, 0x79, 0x17, 0x84, 0x4e, 0x12, 0x78, 0x00, 0xd6, 0xd5, 0x0d, - 0xb2, 0x69, 0x60, 0x6f, 0xcf, 0x67, 0xc6, 0x96, 0x3a, 0x56, 0xe6, 0x42, 0x4e, 0x4d, 0xad, 0x4f, - 0x02, 0x68, 0x16, 0xe6, 0x47, 0x45, 0x32, 0x1e, 0xcf, 0x67, 0xc6, 0xa6, 0x66, 0x68, 0x0f, 0xca, - 0x87, 0xca, 0xd7, 0xa0, 0x36, 0xc2, 0x5e, 0x80, 0x13, 0xf7, 0x40, 0x67, 0x6e, 0x67, 0x79, 0xa2, - 0x1c, 0x4b, 0xbf, 0xdd, 0xb9, 0x9e, 0x19, 0x6b, 0x6a, 0x7d, 0x90, 0x4b, 0xa6, 0x64, 0xe4, 0xac, - 0xa9, 0xe5, 0x41, 0x41, 0xb2, 0xa7, 0x1b, 0xfc, 0x01, 0x92, 0xbd, 0x5b, 0x92, 0xbd, 0x4c, 0xb2, - 0x77, 0x58, 0x13, 0xf9, 0x79, 0x27, 0x72, 0xf4, 0x6b, 0x15, 0xac, 0x2a, 0x06, 0xf4, 0x40, 0x93, - 0x91, 0x61, 0x8c, 0x03, 0x57, 0xc1, 0x74, 0xc1, 0x75, 0x8a, 0x81, 0xd4, 0x07, 0xe1, 0x4c, 0xc2, - 0x74, 0xd0, 0xbd, 0xab, 0x99, 0x51, 0xce, 0x7b, 0x66, 0x41, 0x02, 0x39, 0x0d, 0x56, 0xc0, 0xc2, - 0x1f, 0x41, 0x33, 0x7b, 0x77, 0x97, 0xe1, 0xb4, 0x28, 0xef, 0x08, 0x91, 0x3d, 0xe8, 0x19, 0xe6, - 0x76, 0x2b, 0x97, 0x5f, 0xa0, 0x23, 0xa7, 0x71, 0x51, 0xc0, 0xc1, 0xef, 0x81, 0x1a, 0x92, 0x32, - 0xfe, 0x3d, 0xc5, 0xfb, 0x54, 0xb7, 0xfa, 0xc7, 0x85, 0x91, 0x9b, 0xf1, 0x90, 0xd3, 0xd4, 0x06, - 0xdd, 0xec, 0x21, 0x80, 0x29, 0x22, 0x2f, 0x5c, 0xfd, 0x1a, 0xf7, 0x9d, 0xfe, 0xe9, 0x7c, 0x66, - 0xec, 0x2e, 0x46, 0xc9, 0x35, 0x90, 0xf3, 0x91, 0x36, 0xe6, 0x25, 0x8c, 0xbe, 0x04, 0xb5, 0xf4, - 0xb3, 0x03, 0xf7, 0xc0, 0x7a, 0x3c, 0x89, 0x70, 0x22, 0x3c, 0xf2, 0x45, 0xaa, 0x4e, 0x6e, 0x80, - 0x5d, 0x50, 0x0f, 0x70, 0x4c, 0x23, 0x12, 0x4b, 0x7f, 0x45, 0xfa, 0x8b, 0x26, 0xfb, 0xf4, 0xfd, - 0x75, 0xa7, 0x7c, 0x75, 0xdd, 0x29, 0xff, 0x73, 0xdd, 0x29, 0xbf, 0xbd, 0xe9, 0x94, 0xae, 0x6e, - 0x3a, 0xa5, 0xbf, 0x6e, 0x3a, 0xa5, 0x1f, 0x3e, 0x2b, 0xb4, 0x9b, 0x4f, 0x59, 0x44, 0x99, 0xfe, - 0x79, 0xc6, 0x82, 0x73, 0x6b, 0x6a, 0x89, 0x0f, 0xf0, 0xf3, 0xcf, 0x9f, 0x2d, 0xff, 0x53, 0xd0, - 0x5f, 0x95, 0x93, 0xe7, 0xc5, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x86, 0x4d, 0xba, 0x76, 0x82, - 0x08, 0x00, 0x00, + // 1039 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0x6f, 0xda, 0xd2, 0xa6, 0x93, 0xf4, 0x0f, 0xb3, 0xa5, 0xeb, 0x96, 0x6c, 0x1c, 0x0d, 0x08, + 0x55, 0x48, 0x6b, 0x6f, 0xb3, 0x2b, 0x90, 0x7a, 0xc3, 0x5d, 0x50, 0x8b, 0x58, 0xa9, 0xb8, 0x14, + 0x10, 0x12, 0xb2, 0x1c, 0x7b, 0x92, 0x8c, 0x6a, 0x7b, 0x8c, 0x67, 0x52, 0x52, 0x3e, 0x01, 0x48, + 0x1c, 0x56, 0x9c, 0xf6, 0x08, 0xdf, 0x66, 0x4f, 0xa8, 0x47, 0x4e, 0x06, 0xb5, 0xdf, 0x20, 0x47, + 0x4e, 0xc8, 0x33, 0xe3, 0x3f, 0x49, 0x5b, 0x75, 0xb9, 0xb4, 0x33, 0xef, 0xf7, 0xe7, 0xd9, 0x6f, + 0xde, 0x3c, 0x07, 0xe8, 0xa4, 0xe7, 0x99, 0x1c, 0x47, 0x3e, 0x4e, 0x42, 0x12, 0xf1, 0xca, 0xd2, + 0x88, 0x13, 0xca, 0x29, 0x5c, 0x23, 0x3d, 0xcf, 0x28, 0xa3, 0x3b, 0x9d, 0x2a, 0xf9, 0x22, 0xc6, + 0xcc, 0x3c, 0x77, 0x03, 0xe2, 0xbb, 0x9c, 0x26, 0x52, 0xb1, 0xd3, 0xba, 0xc1, 0x10, 0x7f, 0x15, + 0xfa, 0xc0, 0xa3, 0x51, 0x9f, 0x50, 0x33, 0x4e, 0x28, 0xed, 0xe7, 0xc1, 0xf6, 0x80, 0xd2, 0x41, + 0x80, 0x4d, 0xb1, 0xeb, 0x8d, 0xfa, 0xa6, 0x3f, 0x4a, 0x5c, 0x4e, 0x68, 0xa4, 0x70, 0x7d, 0x16, + 0xe7, 0x24, 0xc4, 0x8c, 0xbb, 0x61, 0xac, 0x08, 0x0f, 0xb3, 0xd7, 0xf0, 0x02, 0x82, 0x23, 0xae, + 0xfe, 0xe5, 0x4a, 0x01, 0xd0, 0x30, 0x24, 0x3c, 0x14, 0x60, 0xb1, 0x54, 0x84, 0xcd, 0x01, 0x1d, + 0x50, 0xb1, 0x34, 0xb3, 0x95, 0x8c, 0xa2, 0x5f, 0x97, 0x41, 0xe3, 0x40, 0xf8, 0x9c, 0x70, 0x97, + 0x63, 0xb8, 0x0d, 0xea, 0xde, 0xd0, 0x25, 0x91, 0x43, 0x7c, 0xad, 0xd6, 0xa9, 0xed, 0xae, 0xd8, + 0xcb, 0x62, 0x7f, 0xe4, 0xc3, 0x53, 0xd0, 0xe0, 0xc9, 0x88, 0x71, 0x27, 0xc0, 0xe7, 0x38, 0xd0, + 0xe6, 0x3b, 0xb5, 0xdd, 0x46, 0x57, 0x33, 0xa6, 0xcb, 0x66, 0x7c, 0x96, 0xb8, 0x5e, 0xf6, 0x42, + 0xd6, 0xce, 0xeb, 0x54, 0x9f, 0x9b, 0xa4, 0x3a, 0xbc, 0x70, 0xc3, 0x60, 0x1f, 0x55, 0xa4, 0xc8, + 0x06, 0x62, 0xf7, 0x45, 0xb6, 0x81, 0x7d, 0xb0, 0x2e, 0x76, 0x24, 0x1a, 0x38, 0x31, 0x4e, 0x08, + 0xf5, 0xb5, 0x05, 0x61, 0xbd, 0x6d, 0xc8, 0x62, 0x18, 0x79, 0x31, 0x8c, 0xe7, 0xaa, 0x58, 0x16, + 0x52, 0xde, 0x5b, 0x15, 0xef, 0x52, 0x8f, 0x5e, 0xfd, 0xad, 0xd7, 0xec, 0xb5, 0x3c, 0x7a, 0x2c, + 0x82, 0x90, 0x80, 0x8d, 0x51, 0xd4, 0xa3, 0x91, 0x5f, 0x49, 0xb4, 0x78, 0x5f, 0xa2, 0xf7, 0x54, + 0xa2, 0x87, 0x32, 0xd1, 0xac, 0x81, 0xcc, 0xb4, 0x5e, 0x84, 0x55, 0x2a, 0x0c, 0xd6, 0x43, 0x77, + 0xec, 0x78, 0x01, 0xf5, 0xce, 0x1c, 0x3f, 0x21, 0x7d, 0xae, 0xbd, 0xf5, 0x3f, 0x5f, 0x69, 0x46, + 0x2f, 0x13, 0xad, 0x86, 0xee, 0xf8, 0x20, 0x0b, 0x3e, 0xcf, 0x62, 0xf0, 0x14, 0xac, 0xf6, 0x13, + 0xfa, 0x13, 0x8e, 0x9c, 0x21, 0x26, 0x83, 0x21, 0xd7, 0x96, 0x44, 0x12, 0x28, 0x8e, 0x44, 0x35, + 0xc7, 0xa1, 0x40, 0xac, 0x96, 0x72, 0xdf, 0x94, 0xee, 0x53, 0x32, 0x64, 0x37, 0xe5, 0x5e, 0x72, + 0x33, 0xdb, 0xc0, 0xe5, 0x98, 0xf1, 0xdc, 0x76, 0xf9, 0x4d, 0x6d, 0xa7, 0x64, 0xc8, 0x6e, 0xca, + 0xbd, 0xb2, 0x3d, 0x02, 0x0d, 0x71, 0x15, 0x1c, 0x16, 0x63, 0x8f, 0x69, 0xf5, 0xce, 0xc2, 0x6e, + 0xa3, 0xbb, 0x61, 0x10, 0x8f, 0x75, 0x9f, 0x1a, 0xc7, 0x19, 0x72, 0x12, 0x63, 0xcf, 0xda, 0x2a, + 0x5b, 0xa6, 0x42, 0x47, 0x36, 0x88, 0x73, 0x0a, 0x83, 0x0e, 0xd8, 0x76, 0x83, 0x80, 0xfe, 0xe8, + 0x8c, 0x62, 0xdf, 0xe5, 0xd8, 0x71, 0xfb, 0x1c, 0x27, 0x0e, 0x1e, 0xc7, 0x24, 0xb9, 0xd0, 0x56, + 0x3a, 0xb5, 0xdd, 0xba, 0xf5, 0xfe, 0x24, 0xd5, 0x3b, 0xd2, 0xe6, 0x4e, 0x2a, 0xb2, 0xb7, 0x04, + 0x76, 0x2a, 0xa0, 0x4f, 0x32, 0xe4, 0x53, 0x01, 0xc0, 0x1f, 0x80, 0x7e, 0x8b, 0x2a, 0x24, 0xac, + 0x87, 0x87, 0xee, 0x39, 0xa1, 0xa3, 0x44, 0x03, 0x22, 0xcd, 0x87, 0x93, 0x54, 0xff, 0xe0, 0xce, + 0x34, 0x55, 0x01, 0xb2, 0x5b, 0xb3, 0xc9, 0x5e, 0x54, 0xe0, 0xfd, 0xc5, 0x9f, 0x7f, 0xd7, 0xe7, + 0xd0, 0x9f, 0xf3, 0x60, 0xed, 0x80, 0x46, 0x0c, 0x47, 0x6c, 0xc4, 0xe4, 0x8d, 0xb4, 0xc0, 0x4a, + 0x31, 0x04, 0xc4, 0x95, 0x6c, 0x74, 0x77, 0x6e, 0xb4, 0xd1, 0x57, 0x39, 0xc3, 0xaa, 0x67, 0x47, + 0xf2, 0x32, 0xeb, 0x96, 0x52, 0x06, 0x9f, 0x81, 0xc5, 0x84, 0x52, 0xae, 0xee, 0xec, 0x8e, 0x3c, + 0xc9, 0x72, 0x40, 0xbc, 0xc0, 0xc9, 0x59, 0x80, 0x6d, 0x4a, 0xb9, 0xb5, 0x98, 0xc9, 0x6d, 0xc1, + 0x86, 0x4f, 0xc0, 0x92, 0xea, 0x80, 0x85, 0x3b, 0x3b, 0x40, 0xf2, 0x15, 0x0f, 0xfe, 0x52, 0x03, + 0x9b, 0x11, 0x1e, 0x73, 0xa7, 0x18, 0x95, 0xcc, 0x19, 0xba, 0x6c, 0x28, 0x2e, 0x5a, 0xd3, 0xfa, + 0x66, 0x92, 0xea, 0xef, 0xca, 0x6a, 0xdd, 0xc6, 0x42, 0xff, 0xa6, 0xfa, 0xb3, 0x01, 0xe1, 0xc3, + 0x51, 0x2f, 0x7b, 0xba, 0xdb, 0xa7, 0xb5, 0x19, 0x90, 0x1e, 0x33, 0x7b, 0x17, 0x1c, 0x33, 0xe3, + 0x10, 0x8f, 0xad, 0x6c, 0x61, 0xc3, 0xcc, 0xee, 0xeb, 0xc2, 0xed, 0xd0, 0x65, 0x43, 0x55, 0xd0, + 0x3f, 0xe6, 0x41, 0xb3, 0x5a, 0x67, 0xb8, 0x07, 0x56, 0xe4, 0x1b, 0x14, 0x13, 0xce, 0xda, 0x9c, + 0xa4, 0xfa, 0x86, 0x7c, 0xac, 0x02, 0x42, 0x76, 0x5d, 0xae, 0x8f, 0x7c, 0x68, 0x54, 0x66, 0xe2, + 0xbc, 0x50, 0x3c, 0x98, 0xa4, 0xfa, 0xba, 0x52, 0x28, 0x04, 0x95, 0x83, 0xf2, 0x4b, 0x50, 0x1f, + 0x62, 0xd7, 0xc7, 0x89, 0xb3, 0xa7, 0x2a, 0xb7, 0x35, 0x3b, 0x25, 0x0f, 0x05, 0x6e, 0xb5, 0xaf, + 0x52, 0x7d, 0x59, 0xae, 0xf7, 0x4a, 0xcb, 0x5c, 0x8c, 0xec, 0x65, 0xb9, 0xdc, 0xab, 0x58, 0x76, + 0xd5, 0xd0, 0x7a, 0x03, 0xcb, 0xee, 0x0d, 0xcb, 0x6e, 0x61, 0xd9, 0xdd, 0xaf, 0x67, 0xf5, 0x79, + 0x95, 0xd5, 0xe8, 0xb7, 0x05, 0xb0, 0x24, 0x15, 0xd0, 0x05, 0xab, 0x8c, 0x0c, 0x22, 0xec, 0x3b, + 0x92, 0xa6, 0x1a, 0xae, 0x5d, 0x4d, 0x24, 0x3f, 0x72, 0x27, 0x82, 0xa6, 0x92, 0xb6, 0x2e, 0x53, + 0xbd, 0x56, 0xce, 0x81, 0x29, 0x0b, 0x64, 0x37, 0x59, 0x85, 0x0b, 0xbf, 0x07, 0xab, 0xc5, 0xb9, + 0x3b, 0x0c, 0xe7, 0x4d, 0x79, 0x4b, 0x8a, 0xe2, 0x40, 0x4f, 0x30, 0xb7, 0xb4, 0xd2, 0x7e, 0x4a, + 0x8e, 0xec, 0xe6, 0x79, 0x85, 0x07, 0xbf, 0x05, 0x72, 0xf0, 0x8b, 0xfc, 0xf7, 0x34, 0xef, 0x23, + 0x35, 0xbe, 0xde, 0xa9, 0x7c, 0x46, 0x0a, 0x1d, 0xb2, 0x57, 0x55, 0x40, 0x0d, 0xb0, 0x00, 0xc0, + 0x9c, 0x51, 0x36, 0xae, 0x3a, 0x8d, 0xfb, 0x9e, 0xfe, 0xd1, 0x24, 0xd5, 0xb7, 0xa7, 0xb3, 0x94, + 0x1e, 0xc8, 0x7e, 0x5b, 0x05, 0xcb, 0x16, 0x46, 0x9f, 0x83, 0x7a, 0xfe, 0x29, 0x85, 0x2d, 0xb0, + 0x12, 0x8d, 0x42, 0x9c, 0x64, 0x88, 0x38, 0x91, 0x05, 0xbb, 0x0c, 0xc0, 0x0e, 0x68, 0xf8, 0x38, + 0xa2, 0x21, 0x89, 0x04, 0x3e, 0x2f, 0xf0, 0x6a, 0xc8, 0x3a, 0x7e, 0x7d, 0xd5, 0xae, 0x5d, 0x5e, + 0xb5, 0x6b, 0xff, 0x5c, 0xb5, 0x6b, 0x2f, 0xaf, 0xdb, 0x73, 0x97, 0xd7, 0xed, 0xb9, 0xbf, 0xae, + 0xdb, 0x73, 0xdf, 0x7d, 0x54, 0xb9, 0x6e, 0x1e, 0x65, 0x21, 0x65, 0xea, 0xdf, 0x63, 0xe6, 0x9f, + 0x99, 0x63, 0x33, 0xfb, 0x51, 0xf1, 0xe4, 0xe3, 0xc7, 0xb3, 0x3f, 0x74, 0x7a, 0x4b, 0x62, 0xf2, + 0x3c, 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x00, 0xae, 0xae, 0x92, 0x56, 0x09, 0x00, 0x00, } func (m *ClientState) Marshal() (dAtA []byte, err error) { @@ -393,6 +403,26 @@ func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.AllowUpdateAfterMisbehaviour { + i-- + if m.AllowUpdateAfterMisbehaviour { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x50 + } + if m.AllowUpdateAfterExpiry { + i-- + if m.AllowUpdateAfterExpiry { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x48 + } if len(m.ProofSpecs) > 0 { for iNdEx := len(m.ProofSpecs) - 1; iNdEx >= 0; iNdEx-- { { @@ -731,6 +761,12 @@ func (m *ClientState) Size() (n int) { n += 1 + l + sovTendermint(uint64(l)) } } + if m.AllowUpdateAfterExpiry { + n += 2 + } + if m.AllowUpdateAfterMisbehaviour { + n += 2 + } return n } @@ -1115,6 +1151,46 @@ func (m *ClientState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowUpdateAfterExpiry", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowUpdateAfterExpiry = bool(v != 0) + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowUpdateAfterMisbehaviour", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowUpdateAfterMisbehaviour = bool(v != 0) default: iNdEx = preIndex skippy, err := skipTendermint(dAtA[iNdEx:]) diff --git a/x/ibc/07-tendermint/types/update.go b/x/ibc/07-tendermint/types/update.go index 284d9dba75..30a216a83d 100644 --- a/x/ibc/07-tendermint/types/update.go +++ b/x/ibc/07-tendermint/types/update.go @@ -45,7 +45,7 @@ func (cs ClientState) CheckHeaderAndUpdateState( ) } - // Get consensus bytes from clientStore + // get consensus state from clientStore tmConsState, err := GetConsensusState(clientStore, cdc, tmHeader.TrustedHeight) if err != nil { return nil, nil, sdkerrors.Wrapf( diff --git a/x/ibc/07-tendermint/types/update_test.go b/x/ibc/07-tendermint/types/update_test.go index 31033eb033..a30dea0e9f 100644 --- a/x/ibc/07-tendermint/types/update_test.go +++ b/x/ibc/07-tendermint/types/update_test.go @@ -50,7 +50,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with next height and same validator set", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -60,7 +60,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with future height and different validator set", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+5, epochHeight, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now @@ -70,7 +70,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with next height and different validator set", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, bothValSet.Hash()) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now @@ -80,7 +80,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update for a previous height", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), heightMinus3, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight-1, epochHeight-3, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now @@ -90,7 +90,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update with incorrect header chain-id", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader("ethermint", int64(height.EpochHeight+1), int64(height.EpochHeight), suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -100,7 +100,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update with next height: update header mismatches nextValSetHash", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now @@ -110,7 +110,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update with next height: update header mismatches different nextValSetHash", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, bothValSet.Hash()) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, bothValSet, signers) currentTime = suite.now @@ -120,7 +120,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update with future height: too much change in validator set", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+5, epochHeight, suite.headerTime, altValSet, suite.valSet, altSigners) currentTime = suite.now @@ -130,7 +130,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+5, epochHeight, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now @@ -140,7 +140,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: trusting period has passed since last client timestamp", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) // make current time pass trusting period from last timestamp on clientstate @@ -151,7 +151,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header timestamp is past current timestamp", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -161,7 +161,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header timestamp is not past last client timestamp", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.clientTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -171,7 +171,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "header basic validation failed", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) newHeader = types.CreateTestHeader(chainID, epochHeight+1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) // cause new header to fail validatebasic by changing commit height to mismatch header height @@ -183,7 +183,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { { name: "header height < consensus height", setup: func() { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus5, commitmenttypes.GetSDKSpecs()) + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus5, commitmenttypes.GetSDKSpecs(), false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), height, suite.valsHash) // Make new header at height less than latest client state newHeader = types.CreateTestHeader(chainID, epochHeight-1, epochHeight, suite.headerTime, suite.valSet, suite.valSet, signers) diff --git a/x/ibc/09-localhost/types/client_state.go b/x/ibc/09-localhost/types/client_state.go index bd3a3728f5..d1350783a5 100644 --- a/x/ibc/09-localhost/types/client_state.go +++ b/x/ibc/09-localhost/types/client_state.go @@ -90,6 +90,14 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "cannot submit misbehaviour to localhost client") } +// CheckProposedHeaderAndUpdateState returns an error. The localhost cannot be modified by +// proposals. +func (cs ClientState) CheckProposedHeaderAndUpdateState( + ctx sdk.Context, _ codec.BinaryMarshaler, _ sdk.KVStore, _ exported.Header, +) (exported.ClientState, exported.ConsensusState, error) { + return nil, nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "cannot update localhost client with a proposal") +} + // VerifyClientState verifies that the localhost client state is stored locally func (cs ClientState) VerifyClientState( store sdk.KVStore, cdc codec.BinaryMarshaler, _ exported.Root, diff --git a/x/ibc/09-localhost/types/client_state_test.go b/x/ibc/09-localhost/types/client_state_test.go index a219454f9b..6e8c0ba923 100644 --- a/x/ibc/09-localhost/types/client_state_test.go +++ b/x/ibc/09-localhost/types/client_state_test.go @@ -128,6 +128,21 @@ func (suite *LocalhostTestSuite) TestCheckHeaderAndUpdateState() { suite.Require().Equal(suite.ctx.BlockHeader().ChainID, clientState.ChainId) } +func (suite *LocalhostTestSuite) TestMisbehaviourAndUpdateState() { + clientState := types.NewClientState("chainID", clientHeight) + cs, err := clientState.CheckMisbehaviourAndUpdateState(suite.ctx, nil, nil, nil) + suite.Require().Error(err) + suite.Require().Nil(cs) +} + +func (suite *LocalhostTestSuite) TestProposedHeaderAndUpdateState() { + clientState := types.NewClientState("chainID", clientHeight) + cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.ctx, nil, nil, nil) + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) +} + func (suite *LocalhostTestSuite) TestVerifyConnectionState() { counterparty := connectiontypes.NewCounterparty("clientB", testConnectionID, commitmenttypes.NewMerklePrefix([]byte("ibc"))) conn1 := connectiontypes.NewConnectionEnd(connectiontypes.OPEN, "clientA", counterparty, []string{"1.0.0"}) diff --git a/x/ibc/exported/client.go b/x/ibc/exported/client.go index 2845d1a74a..544dfd24fb 100644 --- a/x/ibc/exported/client.go +++ b/x/ibc/exported/client.go @@ -24,6 +24,7 @@ type ClientState interface { CheckHeaderAndUpdateState(sdk.Context, codec.BinaryMarshaler, sdk.KVStore, Header) (ClientState, ConsensusState, error) CheckMisbehaviourAndUpdateState(sdk.Context, codec.BinaryMarshaler, sdk.KVStore, Misbehaviour) (ClientState, error) + CheckProposedHeaderAndUpdateState(sdk.Context, codec.BinaryMarshaler, sdk.KVStore, Header) (ClientState, ConsensusState, error) // State verification functions diff --git a/x/ibc/genesis_test.go b/x/ibc/genesis_test.go index 042d820cce..ecb824b0fe 100644 --- a/x/ibc/genesis_test.go +++ b/x/ibc/genesis_test.go @@ -37,7 +37,7 @@ func (suite *IBCTestSuite) TestValidateGenesis() { ClientGenesis: clienttypes.NewGenesisState( []clienttypes.IdentifiedClientState{ clienttypes.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), clienttypes.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", clientHeight), @@ -97,7 +97,7 @@ func (suite *IBCTestSuite) TestValidateGenesis() { ClientGenesis: clienttypes.NewGenesisState( []clienttypes.IdentifiedClientState{ clienttypes.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), clienttypes.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("(chaindID)", clienttypes.Height{}), @@ -166,7 +166,7 @@ func (suite *IBCTestSuite) TestInitGenesis() { ClientGenesis: clienttypes.NewGenesisState( []clienttypes.IdentifiedClientState{ clienttypes.NewIdentifiedClientState( - clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs()), + clientID, ibctmtypes.NewClientState(chainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), false, false), ), clienttypes.NewIdentifiedClientState( exported.ClientTypeLocalHost, localhosttypes.NewClientState("chaindID", clientHeight), diff --git a/x/ibc/light-clients/solomachine/client/cli/tx.go b/x/ibc/light-clients/solomachine/client/cli/tx.go index 0d8350424d..853ef81182 100644 --- a/x/ibc/light-clients/solomachine/client/cli/tx.go +++ b/x/ibc/light-clients/solomachine/client/cli/tx.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/version" @@ -16,9 +17,13 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" ) +const ( + flagAllowUpdateAfterProposal = "allow_update_after_proposal" +) + // NewCreateClientCmd defines the command to create a new solo machine client. func NewCreateClientCmd() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "create [client-id] [path/to/consensus_state.json]", Short: "create new solo machine client", Long: "create a new solo machine client with the specified identifier and consensus state", @@ -47,7 +52,9 @@ func NewCreateClientCmd() *cobra.Command { } } - clientState := types.NewClientState(consensusState) + allowUpdateAfterProposal, _ := cmd.Flags().GetBool(flagAllowUpdateAfterProposal) + + clientState := types.NewClientState(consensusState, allowUpdateAfterProposal) msg, err := clienttypes.NewMsgCreateClient(clientID, clientState, consensusState, clientCtx.GetFromAddress()) if err != nil { return err @@ -60,6 +67,11 @@ func NewCreateClientCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } + + cmd.Flags().Bool(flagAllowUpdateAfterProposal, false, "allow governance proposal to update client") + flags.AddTxFlagsToCmd(cmd) + + return cmd } // NewUpdateClientCmd defines the command to update a solo machine client. diff --git a/x/ibc/light-clients/solomachine/types/client_state.go b/x/ibc/light-clients/solomachine/types/client_state.go index 45d1ff86b9..aa2c03f224 100644 --- a/x/ibc/light-clients/solomachine/types/client_state.go +++ b/x/ibc/light-clients/solomachine/types/client_state.go @@ -15,10 +15,11 @@ import ( var _ exported.ClientState = (*ClientState)(nil) // NewClientState creates a new ClientState instance. -func NewClientState(consensusState *ConsensusState) *ClientState { +func NewClientState(consensusState *ConsensusState, allowUpdateAfterProposal bool) *ClientState { return &ClientState{ - FrozenSequence: 0, - ConsensusState: consensusState, + FrozenSequence: 0, + ConsensusState: consensusState, + AllowUpdateAfterProposal: allowUpdateAfterProposal, } } diff --git a/x/ibc/light-clients/solomachine/types/client_state_test.go b/x/ibc/light-clients/solomachine/types/client_state_test.go index 944cb8de91..16b5ffe543 100644 --- a/x/ibc/light-clients/solomachine/types/client_state_test.go +++ b/x/ibc/light-clients/solomachine/types/client_state_test.go @@ -40,17 +40,17 @@ func (suite *SoloMachineTestSuite) TestClientStateValidateBasic() { }, { "sequence is zero", - types.NewClientState(&types.ConsensusState{0, suite.solomachine.ConsensusState().PublicKey, suite.solomachine.Time}), + types.NewClientState(&types.ConsensusState{0, suite.solomachine.ConsensusState().PublicKey, suite.solomachine.Time}, false), false, }, { "timstamp is zero", - types.NewClientState(&types.ConsensusState{1, suite.solomachine.ConsensusState().PublicKey, 0}), + types.NewClientState(&types.ConsensusState{1, suite.solomachine.ConsensusState().PublicKey, 0}, false), false, }, { "pubkey is empty", - types.NewClientState(&types.ConsensusState{suite.solomachine.Sequence, nil, suite.solomachine.Time}), + types.NewClientState(&types.ConsensusState{suite.solomachine.Sequence, nil, suite.solomachine.Time}, false), false, }, } @@ -117,14 +117,14 @@ func (suite *SoloMachineTestSuite) TestVerifyClientState() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, }, { "consensus state in client state is nil", - types.NewClientState(nil), + types.NewClientState(nil, false), prefix, proof, false, @@ -136,7 +136,7 @@ func (suite *SoloMachineTestSuite) TestVerifyClientState() { Sequence: suite.solomachine.Sequence - 1, Timestamp: suite.solomachine.Time, PublicKey: suite.solomachine.ConsensusState().PublicKey, - }), + }, false), prefix, proof, false, @@ -148,7 +148,7 @@ func (suite *SoloMachineTestSuite) TestVerifyClientState() { Sequence: suite.solomachine.Sequence, Timestamp: suite.solomachine.Time + 1, PublicKey: suite.solomachine.ConsensusState().PublicKey, - }), + }, false), prefix, proof, false, @@ -242,14 +242,14 @@ func (suite *SoloMachineTestSuite) TestVerifyClientConsensusState() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, }, { "consensus state in client state is nil", - types.NewClientState(nil), + types.NewClientState(nil, false), prefix, proof, false, @@ -261,7 +261,7 @@ func (suite *SoloMachineTestSuite) TestVerifyClientConsensusState() { Sequence: suite.solomachine.Sequence - 1, Timestamp: suite.solomachine.Time, PublicKey: suite.solomachine.ConsensusState().PublicKey, - }), + }, false), prefix, proof, false, @@ -273,7 +273,7 @@ func (suite *SoloMachineTestSuite) TestVerifyClientConsensusState() { Sequence: suite.solomachine.Sequence, Timestamp: suite.solomachine.Time + 1, PublicKey: suite.solomachine.ConsensusState().PublicKey, - }), + }, false), prefix, proof, false, @@ -363,7 +363,7 @@ func (suite *SoloMachineTestSuite) TestVerifyConnectionState() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, @@ -446,7 +446,7 @@ func (suite *SoloMachineTestSuite) TestVerifyChannelState() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, @@ -526,7 +526,7 @@ func (suite *SoloMachineTestSuite) TestVerifyPacketCommitment() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, @@ -606,7 +606,7 @@ func (suite *SoloMachineTestSuite) TestVerifyPacketAcknowledgement() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, @@ -685,7 +685,7 @@ func (suite *SoloMachineTestSuite) TestVerifyPacketAcknowledgementAbsence() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, @@ -765,7 +765,7 @@ func (suite *SoloMachineTestSuite) TestVerifyNextSeqRecv() { }, { "client is frozen", - &types.ClientState{1, suite.solomachine.ConsensusState()}, + &types.ClientState{1, suite.solomachine.ConsensusState(), false}, prefix, proof, false, diff --git a/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go b/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go index 906d98a6d7..aec4677ba5 100644 --- a/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go +++ b/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go @@ -38,7 +38,7 @@ func (suite *SoloMachineTestSuite) TestCheckMisbehaviourAndUpdateState() { { "wrong client state type", func() { - clientState = ibctmtypes.ClientState{} + clientState = &ibctmtypes.ClientState{} misbehaviour = suite.solomachine.CreateMisbehaviour() }, false, diff --git a/x/ibc/light-clients/solomachine/types/proposal_handle.go b/x/ibc/light-clients/solomachine/types/proposal_handle.go new file mode 100644 index 0000000000..4c5f2325b3 --- /dev/null +++ b/x/ibc/light-clients/solomachine/types/proposal_handle.go @@ -0,0 +1,53 @@ +package types + +import ( + "reflect" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" +) + +// CheckProposedHeaderAndUpdateState updates the consensus state to the header's sequence and +// public key. An error is returned if the client has been disallowed to be updated by a +// governance proposal, the header cannot be casted to a solo machine header, or the current +// public key equals the new public key. +func (cs ClientState) CheckProposedHeaderAndUpdateState( + ctx sdk.Context, cdc codec.BinaryMarshaler, clientStore sdk.KVStore, + header exported.Header, +) (exported.ClientState, exported.ConsensusState, error) { + + if !cs.AllowUpdateAfterProposal { + return nil, nil, sdkerrors.Wrapf( + clienttypes.ErrUpdateClientFailed, + "solo machine client is not allowed to updated with a proposal", + ) + } + + smHeader, ok := header.(*Header) + if !ok { + return nil, nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, "header type %T, expected %T", header, &Header{}, + ) + } + + if reflect.DeepEqual(cs.ConsensusState.GetPubKey(), smHeader.GetPubKey()) { + return nil, nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, "new public key in header equals current public key", + ) + } + + clientState := &cs + + consensusState := &ConsensusState{ + Sequence: smHeader.Sequence, + PublicKey: smHeader.NewPublicKey, + } + + clientState.ConsensusState = consensusState + clientState.FrozenSequence = 0 + + return clientState, consensusState, nil +} diff --git a/x/ibc/light-clients/solomachine/types/proposal_handle_test.go b/x/ibc/light-clients/solomachine/types/proposal_handle_test.go new file mode 100644 index 0000000000..a5ff9180fa --- /dev/null +++ b/x/ibc/light-clients/solomachine/types/proposal_handle_test.go @@ -0,0 +1,72 @@ +package types_test + +import ( + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + "github.com/cosmos/cosmos-sdk/x/ibc/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" +) + +func (suite *SoloMachineTestSuite) TestCheckProposedHeaderAndUpdateState() { + var header exported.Header + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "valid header", func() { + header = suite.solomachine.CreateHeader() + }, true, + }, + { + "nil header", func() { + header = &ibctmtypes.Header{} + }, false, + }, + { + "header does not update public key", func() { + header = &types.Header{ + Sequence: 1, + NewPublicKey: suite.solomachine.ConsensusState().PublicKey, + } + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + clientState := suite.solomachine.ClientState() + + tc.malleate() + + clientStore := suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), suite.solomachine.ClientID) + + // all cases should always fail if the client has 'AllowUpdateAfterProposal' set to false + clientState.AllowUpdateAfterProposal = false + cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + + clientState.AllowUpdateAfterProposal = true + cs, consState, err = clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(header.(*types.Header).GetPubKey(), consState.(*types.ConsensusState).GetPubKey()) + suite.Require().Equal(cs.(*types.ClientState).ConsensusState, consState) + suite.Require().Equal(header.GetHeight().GetEpochHeight(), consState.(*types.ConsensusState).Sequence) + } else { + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + } + }) + } + +} diff --git a/x/ibc/light-clients/solomachine/types/solomachine.pb.go b/x/ibc/light-clients/solomachine/types/solomachine.pb.go index 0329a3ace3..907689ba71 100644 --- a/x/ibc/light-clients/solomachine/types/solomachine.pb.go +++ b/x/ibc/light-clients/solomachine/types/solomachine.pb.go @@ -30,6 +30,9 @@ type ClientState struct { // frozen sequence of the solo machine FrozenSequence uint64 `protobuf:"varint,1,opt,name=frozen_sequence,json=frozenSequence,proto3" json:"frozen_sequence,omitempty" yaml:"frozen_sequence"` ConsensusState *ConsensusState `protobuf:"bytes,2,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty" yaml:"consensus_state"` + // when set to true, will allow governance to update a solo machine client. + // The client will be unfrozen if it is frozen. + AllowUpdateAfterProposal bool `protobuf:"varint,3,opt,name=allow_update_after_proposal,json=allowUpdateAfterProposal,proto3" json:"allow_update_after_proposal,omitempty" yaml:"allow_update_after_proposal"` } func (m *ClientState) Reset() { *m = ClientState{} } @@ -283,45 +286,48 @@ func init() { } var fileDescriptor_6cc2ee18f7f86d4e = []byte{ - // 597 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcf, 0x8a, 0xd3, 0x5e, - 0x14, 0x6e, 0x3a, 0x65, 0x68, 0xef, 0xf4, 0xd7, 0x99, 0x5f, 0xe8, 0x48, 0x2d, 0x43, 0x32, 0x04, - 0xc4, 0xd9, 0x4c, 0x42, 0x74, 0xd7, 0x9d, 0xa9, 0x0b, 0xff, 0x20, 0x4a, 0x3a, 0x0b, 0x51, 0x21, - 0xdc, 0x24, 0x77, 0xda, 0xcb, 0x34, 0xf7, 0xc6, 0xdc, 0x9b, 0xd6, 0xfa, 0x04, 0x2e, 0x5d, 0xba, - 0xf4, 0x05, 0x04, 0x1f, 0x43, 0x10, 0x64, 0x96, 0xae, 0x8a, 0xb4, 0x6f, 0xd0, 0x27, 0x90, 0x26, - 0xb7, 0x69, 0x12, 0x06, 0x8b, 0xe0, 0x2a, 0xe7, 0x9c, 0x9c, 0xfb, 0x9d, 0xef, 0x3b, 0xe7, 0x70, - 0x80, 0x89, 0x5d, 0xcf, 0x18, 0xe3, 0xe1, 0x88, 0x7b, 0x63, 0x8c, 0x08, 0x67, 0x06, 0xa3, 0x63, - 0x1a, 0x40, 0x6f, 0x84, 0x09, 0x32, 0x26, 0x66, 0xde, 0xd5, 0xc3, 0x88, 0x72, 0x2a, 0xab, 0xd8, - 0xf5, 0xf4, 0xfc, 0x13, 0x3d, 0x9f, 0x33, 0x31, 0xbb, 0x77, 0x3d, 0xca, 0x02, 0xca, 0x0c, 0x17, - 0x32, 0x64, 0x78, 0xd1, 0x2c, 0xe4, 0xd4, 0x98, 0x98, 0x2e, 0xe2, 0xd0, 0x14, 0x6e, 0x8a, 0xd4, - 0x6d, 0x0f, 0xe9, 0x90, 0x26, 0xa6, 0xb1, 0xb6, 0xd2, 0xa8, 0xf6, 0x43, 0x02, 0x07, 0xfd, 0x04, - 0x79, 0xc0, 0x21, 0x47, 0x72, 0x1f, 0x1c, 0x5e, 0x46, 0xf4, 0x3d, 0x22, 0x0e, 0x43, 0x6f, 0x63, - 0x44, 0x3c, 0xd4, 0x91, 0x4e, 0xa5, 0xb3, 0x9a, 0xd5, 0x5d, 0xcd, 0xd5, 0x5b, 0x33, 0x18, 0x8c, - 0x7b, 0x5a, 0x29, 0x41, 0xb3, 0x5b, 0x69, 0x64, 0x20, 0x02, 0x32, 0x07, 0x87, 0x1e, 0x25, 0x0c, - 0x11, 0x16, 0x33, 0x87, 0xad, 0x71, 0x3b, 0xd5, 0x53, 0xe9, 0xec, 0xe0, 0x9e, 0xa1, 0xef, 0x90, - 0xa3, 0xf7, 0x37, 0xef, 0x12, 0x3a, 0xf9, 0xaa, 0x25, 0x44, 0xcd, 0x6e, 0x79, 0x85, 0xdc, 0x5e, - 0xed, 0xc3, 0x67, 0xb5, 0xa2, 0x7d, 0x91, 0x40, 0xab, 0x08, 0x22, 0x77, 0x41, 0xbd, 0x28, 0xc6, - 0xce, 0x7c, 0xf9, 0x35, 0x00, 0x61, 0xec, 0x8e, 0xb1, 0xe7, 0x5c, 0xa1, 0x99, 0x60, 0x79, 0x47, - 0x4f, 0x7b, 0xaa, 0xaf, 0x7b, 0xaa, 0x8b, 0x26, 0x8a, 0x9e, 0xea, 0x2f, 0x92, 0xec, 0xa7, 0x68, - 0x66, 0x1d, 0xaf, 0xe6, 0xea, 0xff, 0x29, 0xb7, 0x2d, 0x84, 0x66, 0x37, 0xc2, 0x4d, 0x86, 0x7c, - 0x02, 0x1a, 0x1c, 0x07, 0x88, 0x71, 0x18, 0x84, 0x9d, 0xbd, 0xa4, 0xf2, 0x36, 0x20, 0xf8, 0x7e, - 0x95, 0xc0, 0xfe, 0x23, 0x04, 0x7d, 0x14, 0xfd, 0x91, 0xe7, 0x09, 0x68, 0x30, 0x3c, 0x24, 0x90, - 0xc7, 0x51, 0xda, 0xcc, 0xa6, 0xbd, 0x0d, 0xc8, 0x97, 0xa0, 0x45, 0xd0, 0xd4, 0xc9, 0x29, 0xd9, - 0xfb, 0x1b, 0x25, 0xb7, 0x57, 0x73, 0xf5, 0x38, 0x55, 0x52, 0x84, 0xd1, 0xec, 0x26, 0x41, 0xd3, - 0x2c, 0x51, 0x50, 0xfe, 0x5e, 0x05, 0xcd, 0x67, 0x98, 0xb9, 0x68, 0x04, 0x27, 0x98, 0xc6, 0x91, - 0x6c, 0x82, 0x46, 0x3a, 0x4e, 0x07, 0xfb, 0x09, 0xf3, 0x86, 0xd5, 0x5e, 0xcd, 0xd5, 0x23, 0x31, - 0xb8, 0xcd, 0x2f, 0xcd, 0xae, 0xa7, 0xf6, 0x63, 0xbf, 0xa0, 0xb5, 0x5a, 0xd2, 0x1a, 0x82, 0xff, - 0x32, 0x69, 0x0e, 0x25, 0x48, 0x88, 0x31, 0x77, 0x2e, 0xcf, 0x60, 0xf3, 0xea, 0x01, 0xf1, 0x1f, - 0x42, 0x0e, 0xad, 0xce, 0x6a, 0xae, 0xb6, 0x53, 0x16, 0x05, 0x44, 0xcd, 0x6e, 0x66, 0xfe, 0x73, - 0x52, 0xaa, 0xc8, 0xa7, 0xb4, 0x53, 0xfb, 0xa7, 0x15, 0xf9, 0x94, 0xe6, 0x2b, 0x5e, 0x4c, 0x69, - 0xaf, 0xbe, 0xee, 0xe4, 0xa7, 0x75, 0x37, 0x9f, 0x80, 0xa3, 0x32, 0x4a, 0x71, 0xda, 0x52, 0x79, - 0xda, 0x32, 0xa8, 0xf9, 0x90, 0x43, 0xb1, 0x06, 0x89, 0x2d, 0x26, 0xf3, 0x12, 0xb4, 0x2f, 0x36, - 0xfb, 0x85, 0xfc, 0x0c, 0x76, 0x07, 0x5e, 0x61, 0x4d, 0xab, 0x37, 0xae, 0xa9, 0xf5, 0xe6, 0xdb, - 0x42, 0x91, 0xae, 0x17, 0x8a, 0xf4, 0x6b, 0xa1, 0x48, 0x1f, 0x97, 0x4a, 0xe5, 0x7a, 0xa9, 0x54, - 0x7e, 0x2e, 0x95, 0xca, 0x2b, 0x6b, 0x88, 0xf9, 0x28, 0x76, 0x75, 0x8f, 0x06, 0x86, 0xb8, 0x45, - 0xe9, 0xe7, 0x9c, 0xf9, 0x57, 0xc6, 0x3b, 0x23, 0xbb, 0x79, 0xe7, 0x37, 0x1d, 0x3d, 0x3e, 0x0b, - 0x11, 0x73, 0xf7, 0x93, 0x63, 0x74, 0xff, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x3a, 0xae, - 0x05, 0x21, 0x05, 0x00, 0x00, + // 649 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcf, 0x6e, 0xd3, 0x4e, + 0x10, 0x8e, 0xd3, 0xa8, 0x4a, 0xb6, 0xf9, 0xa5, 0xfd, 0x59, 0x29, 0x0a, 0xa1, 0x8a, 0x2b, 0x4b, + 0x40, 0x2f, 0xb5, 0x65, 0xb8, 0xf5, 0x56, 0x97, 0x03, 0x7f, 0x84, 0xa8, 0xdc, 0x22, 0x21, 0x40, + 0xb2, 0xd6, 0xf6, 0x34, 0x59, 0xd5, 0xd9, 0x35, 0xde, 0x75, 0x42, 0x78, 0x02, 0x8e, 0x1c, 0x39, + 0xf2, 0x02, 0x48, 0x5c, 0x78, 0x07, 0x24, 0x2e, 0x3d, 0x72, 0x8a, 0x50, 0xfb, 0x06, 0x79, 0x02, + 0x14, 0xef, 0x26, 0x8d, 0xa3, 0xaa, 0x15, 0x12, 0x27, 0xef, 0xce, 0x8c, 0xbf, 0xfd, 0xbe, 0xf9, + 0x46, 0x83, 0x1c, 0x12, 0x84, 0x76, 0x4c, 0xba, 0x3d, 0x11, 0xc6, 0x04, 0xa8, 0xe0, 0x36, 0x67, + 0x31, 0xeb, 0xe3, 0xb0, 0x47, 0x28, 0xd8, 0x03, 0x67, 0xf1, 0x6a, 0x25, 0x29, 0x13, 0x4c, 0x37, + 0x48, 0x10, 0x5a, 0x8b, 0xbf, 0x58, 0x8b, 0x35, 0x03, 0xa7, 0x7d, 0x3f, 0x64, 0xbc, 0xcf, 0xb8, + 0x1d, 0x60, 0x0e, 0x76, 0x98, 0x8e, 0x12, 0xc1, 0xec, 0x81, 0x13, 0x80, 0xc0, 0x8e, 0xba, 0x4a, + 0xa4, 0x76, 0xb3, 0xcb, 0xba, 0x2c, 0x3f, 0xda, 0xd3, 0x93, 0x8c, 0x9a, 0xdf, 0xcb, 0x68, 0xed, + 0x20, 0x47, 0x3e, 0x12, 0x58, 0x80, 0x7e, 0x80, 0xd6, 0x4f, 0x52, 0xf6, 0x01, 0xa8, 0xcf, 0xe1, + 0x5d, 0x06, 0x34, 0x84, 0x96, 0xb6, 0xad, 0xed, 0x54, 0xdc, 0xf6, 0x64, 0x6c, 0xdc, 0x1a, 0xe1, + 0x7e, 0xbc, 0x67, 0x2e, 0x15, 0x98, 0x5e, 0x43, 0x46, 0x8e, 0x54, 0x40, 0x17, 0x68, 0x3d, 0x64, + 0x94, 0x03, 0xe5, 0x19, 0xf7, 0xf9, 0x14, 0xb7, 0x55, 0xde, 0xd6, 0x76, 0xd6, 0x1e, 0xd8, 0xd6, + 0x0d, 0x72, 0xac, 0x83, 0xd9, 0x7f, 0x39, 0x9d, 0xc5, 0x57, 0x97, 0x10, 0x4d, 0xaf, 0x11, 0x16, + 0x6a, 0x75, 0x40, 0x77, 0x70, 0x1c, 0xb3, 0xa1, 0x9f, 0x25, 0x11, 0x16, 0xe0, 0xe3, 0x13, 0x01, + 0xa9, 0x9f, 0xa4, 0x2c, 0x61, 0x1c, 0xc7, 0xad, 0x95, 0x6d, 0x6d, 0xa7, 0xea, 0xde, 0x9b, 0x8c, + 0x0d, 0x53, 0x02, 0x5e, 0x53, 0x6c, 0x7a, 0xad, 0x3c, 0xfb, 0x32, 0x4f, 0xee, 0x4f, 0x73, 0x87, + 0x2a, 0xb5, 0x57, 0xf9, 0xf8, 0xc5, 0x28, 0x99, 0x5f, 0x35, 0xd4, 0x28, 0x72, 0xd5, 0xdb, 0xa8, + 0x5a, 0xec, 0x99, 0x37, 0xbf, 0xeb, 0x6f, 0x10, 0x4a, 0xb2, 0x20, 0x26, 0xa1, 0x7f, 0x0a, 0x23, + 0xd5, 0x8c, 0xbb, 0x96, 0xb4, 0xce, 0x9a, 0x5a, 0x67, 0x29, 0xaf, 0x94, 0x75, 0xd6, 0x61, 0x5e, + 0xfd, 0x0c, 0x46, 0xee, 0xe6, 0x64, 0x6c, 0xfc, 0x2f, 0x19, 0x5f, 0x42, 0x98, 0x5e, 0x2d, 0x99, + 0x55, 0xe8, 0x5b, 0xa8, 0x26, 0x48, 0x1f, 0xb8, 0xc0, 0xfd, 0x24, 0x97, 0x59, 0xf1, 0x2e, 0x03, + 0x8a, 0xef, 0x37, 0x0d, 0xad, 0x3e, 0x06, 0x1c, 0x41, 0x7a, 0x2d, 0xcf, 0x2d, 0x54, 0xe3, 0xa4, + 0x4b, 0xb1, 0xc8, 0x52, 0xe9, 0x59, 0xdd, 0xbb, 0x0c, 0xe8, 0x27, 0xa8, 0x41, 0x61, 0xe8, 0x2f, + 0x28, 0x59, 0xf9, 0x1b, 0x25, 0xb7, 0x27, 0x63, 0x63, 0x53, 0x2a, 0x29, 0xc2, 0x98, 0x5e, 0x9d, + 0xc2, 0x70, 0x5e, 0xa8, 0x28, 0xff, 0x2c, 0xa3, 0xfa, 0x73, 0xc2, 0x03, 0xe8, 0xe1, 0x01, 0x61, + 0x59, 0xaa, 0x3b, 0xa8, 0x26, 0xa7, 0xc6, 0x27, 0x51, 0xce, 0xbc, 0xe6, 0x36, 0x27, 0x63, 0x63, + 0x43, 0xcd, 0xc7, 0x2c, 0x65, 0x7a, 0x55, 0x79, 0x7e, 0x12, 0x15, 0xb4, 0x96, 0x97, 0xb4, 0x26, + 0xe8, 0xbf, 0xb9, 0x34, 0x9f, 0x51, 0x50, 0x62, 0x9c, 0x1b, 0x67, 0xf4, 0x68, 0xf6, 0xd7, 0x3e, + 0x8d, 0x1e, 0x61, 0x81, 0xdd, 0xd6, 0x64, 0x6c, 0x34, 0x25, 0x8b, 0x02, 0xa2, 0xe9, 0xd5, 0xe7, + 0xf7, 0x17, 0x74, 0xe9, 0x45, 0x31, 0x64, 0xad, 0xca, 0x3f, 0x7d, 0x51, 0x0c, 0xd9, 0xe2, 0x8b, + 0xc7, 0x43, 0xb6, 0x57, 0x9d, 0x76, 0xf2, 0xf3, 0xb4, 0x9b, 0x4f, 0xd1, 0xc6, 0x32, 0x4a, 0xd1, + 0x6d, 0x6d, 0xd9, 0x6d, 0x1d, 0x55, 0x22, 0x2c, 0xb0, 0x1a, 0x83, 0xfc, 0xac, 0x9c, 0x79, 0x85, + 0x9a, 0xc7, 0xb3, 0xf9, 0x82, 0x68, 0x0e, 0x7b, 0x03, 0x5e, 0x61, 0x4c, 0xcb, 0x57, 0x8e, 0xa9, + 0xfb, 0xf6, 0xc7, 0x79, 0x47, 0x3b, 0x3b, 0xef, 0x68, 0xbf, 0xcf, 0x3b, 0xda, 0xa7, 0x8b, 0x4e, + 0xe9, 0xec, 0xa2, 0x53, 0xfa, 0x75, 0xd1, 0x29, 0xbd, 0x76, 0xbb, 0x44, 0xf4, 0xb2, 0xc0, 0x0a, + 0x59, 0xdf, 0x56, 0x2b, 0x4f, 0x7e, 0x76, 0x79, 0x74, 0x6a, 0xbf, 0xb7, 0xe7, 0xab, 0x75, 0xf7, + 0xaa, 0xdd, 0x2a, 0x46, 0x09, 0xf0, 0x60, 0x35, 0xdf, 0x79, 0x0f, 0xff, 0x04, 0x00, 0x00, 0xff, + 0xff, 0x86, 0xeb, 0x8b, 0x5f, 0x88, 0x05, 0x00, 0x00, } func (m *ClientState) Marshal() (dAtA []byte, err error) { @@ -344,6 +350,16 @@ func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.AllowUpdateAfterProposal { + i-- + if m.AllowUpdateAfterProposal { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } if m.ConsensusState != nil { { size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) @@ -611,6 +627,9 @@ func (m *ClientState) Size() (n int) { l = m.ConsensusState.Size() n += 1 + l + sovSolomachine(uint64(l)) } + if m.AllowUpdateAfterProposal { + n += 2 + } return n } @@ -800,6 +819,26 @@ func (m *ClientState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowUpdateAfterProposal", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowUpdateAfterProposal = bool(v != 0) default: iNdEx = preIndex skippy, err := skipSolomachine(dAtA[iNdEx:]) diff --git a/x/ibc/light-clients/solomachine/types/update_test.go b/x/ibc/light-clients/solomachine/types/update_test.go index 5ab45e7971..6baadf986f 100644 --- a/x/ibc/light-clients/solomachine/types/update_test.go +++ b/x/ibc/light-clients/solomachine/types/update_test.go @@ -29,7 +29,7 @@ func (suite *SoloMachineTestSuite) TestCheckHeaderAndUpdateState() { { "wrong client state type", func() { - clientState = ibctmtypes.ClientState{} + clientState = &ibctmtypes.ClientState{} header = suite.solomachine.CreateHeader() }, false, diff --git a/x/ibc/spec/01_concepts.md b/x/ibc/spec/01_concepts.md index e948f39834..d5b4002307 100644 --- a/x/ibc/spec/01_concepts.md +++ b/x/ibc/spec/01_concepts.md @@ -144,3 +144,25 @@ in the handshake callbacks. Implementations which do not feel they would benefit from versioning can do basic string matching using a single compatible version. + +## ClientUpdateProposal + +A governance proposal may be passed to update a specified client with a provided +header. This is useful in unfreezing clients or updating expired clients. Each +client is expected to implement this functionality. A client may choose to disallow +an update by a governance proposal by returning an error in the client state function +'CheckProposedHeaderAndUpdateState'. + +The localhost client cannot be updated by a governance proposal. + +The solo machine client requires the boolean flag 'AllowUpdateAfterProposal' to be set +to true in order to be updated by a proposal. This is set upon client creation and cannot +be updated later. + +The tendermint client has two flags update flags, 'AllowUpdateAfterExpiry' and +'AllowUpdateAfterMisbehaviour'. The former flag can only be used to unexpire clients. The +latter flag can be used to unfreeze a client and if necessary it will also unexpire the client. +It is advised to let a client expire if it has become frozen before proposing a new header. +This is to avoid the client from becoming refrozen if the misbehaviour evidence has not +expired. These boolean flags are set upon client creation and cannot be updated later. + diff --git a/x/ibc/spec/06_events.md b/x/ibc/spec/06_events.md index 466a8bd2c7..9f96b089b3 100644 --- a/x/ibc/spec/06_events.md +++ b/x/ibc/spec/06_events.md @@ -47,6 +47,16 @@ callbacks to IBC applications. | message | sender | {senderAddress} | | submit_evidence | evidence_hash | {evidenceHash} | +### UpdateClientProposal + +| Type | Attribute Key | Attribute Value | +|------------------------|------------------|-------------------| +| update_client_proposal | client_id | {clientId} | +| update_client_proposal | client_type | {clientType} | +| update_client_proposal | consensus_height | {consensusHeight} | + + + ## ICS 03 - Connection ### MsgConnectionOpenInit diff --git a/x/ibc/spec/README.md b/x/ibc/spec/README.md index 78e6b489cd..8dd5bc0ede 100644 --- a/x/ibc/spec/README.md +++ b/x/ibc/spec/README.md @@ -61,7 +61,8 @@ denomination trace path in order to support special characters and limit the max consensus states in order to verify their membership in the counterparty clients. * [ADR 19 - Protobuf State Encoding](../../../docs/architecture/adr-019-protobuf-state-encoding.md): Migration from Amino to Protobuf for state encoding. * [ADR 020 - Protocol Buffer Transaction Encoding](./../../docs/architecture/adr-020-protobuf-transaction-encoding.md): Client side migration to Protobuf. -* [ADR 021 - Protocol Buffer Query Encoding](./../../docs/architecture/adr-020-protobuf-query-encoding.md): Queries migration to Protobuf. +* [ADR 021 - Protocol Buffer Query Encoding](../../../docs/architecture/adr-020-protobuf-query-encoding.md): Queries migration to Protobuf. +* [ADR 026 - IBC Client Recovery Mechanisms](../../../docs/architecture/adr-026-ibc-client-recovery-mechanisms.md): Allows IBC Clients to be recovered after freezing or expiry. ### SDK Modules diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go index 459ddec4b1..a3982b10ee 100644 --- a/x/ibc/testing/chain.go +++ b/x/ibc/testing/chain.go @@ -36,8 +36,8 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// Default params constants used to create a TM client const ( + // Default params constants used to create a TM client TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 UnbondingPeriod time.Duration = time.Hour * 24 * 7 * 3 MaxClockDrift time.Duration = time.Second * 10 @@ -50,6 +50,10 @@ const ( TransferPort = ibctransfertypes.ModuleName MockPort = mock.ModuleName + + // used for testing UpdateClientProposal + Title = "title" + Description = "description" ) // Default params variables used to create a TM client @@ -380,6 +384,7 @@ func (chain *TestChain) ConstructMsgCreateClient(counterparty *TestChain, client clientState = ibctmtypes.NewClientState( counterparty.ChainID, DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift, height, commitmenttypes.GetSDKSpecs(), + false, false, ) consensusState = counterparty.LastHeader.ConsensusState() case exported.ClientTypeSoloMachine: @@ -406,9 +411,24 @@ func (chain *TestChain) CreateTMClient(counterparty *TestChain, clientID string) } // UpdateTMClient will construct and execute a 07-tendermint MsgUpdateClient. The counterparty -// client will be updated on the (target) chain. -// UpdateTMClient mocks the relayer flow necessary for updating a Tendermint client +// client will be updated on the (target) chain. UpdateTMClient mocks the relayer flow +// necessary for updating a Tendermint client. func (chain *TestChain) UpdateTMClient(counterparty *TestChain, clientID string) error { + header, err := chain.ConstructUpdateTMClientHeader(counterparty, clientID) + require.NoError(chain.t, err) + + msg, err := clienttypes.NewMsgUpdateClient( + clientID, header, + chain.SenderAccount.GetAddress(), + ) + require.NoError(chain.t, err) + + return chain.sendMsgs(msg) +} + +// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChain) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { header := counterparty.LastHeader // Relayer must query for LatestHeight on client to get TrustedHeight trustedHeight := chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) @@ -428,7 +448,7 @@ func (chain *TestChain) UpdateTMClient(counterparty *TestChain, clientID string) // NextValidatorsHash tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.EpochHeight + 1)) if !ok { - return sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) + return nil, sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) } } // inject trusted fields into last header @@ -437,17 +457,18 @@ func (chain *TestChain) UpdateTMClient(counterparty *TestChain, clientID string) trustedVals, err := tmTrustedVals.ToProto() if err != nil { - return err + return nil, err } header.TrustedValidators = trustedVals - msg, err := clienttypes.NewMsgUpdateClient( - clientID, header, - chain.SenderAccount.GetAddress(), - ) - require.NoError(chain.t, err) + return header, nil - return chain.sendMsgs(msg) +} + +// ExpireClient fast forwards the chain's block time by the provided amount of time which will +// expire any clients with a trusting period less than or equal to this amount of time. +func (chain *TestChain) ExpireClient(amount time.Duration) { + chain.CurrentHeader.Time = chain.CurrentHeader.Time.Add(amount) } // CreateTMClientHeader creates a TM header to update the TM client. diff --git a/x/ibc/testing/solomachine.go b/x/ibc/testing/solomachine.go index ecb57f15b2..aba04b3f8d 100644 --- a/x/ibc/testing/solomachine.go +++ b/x/ibc/testing/solomachine.go @@ -41,8 +41,9 @@ func NewSolomachine(t *testing.T, clientID string) *Solomachine { } } +// default usage does not allow update after governance proposal func (solo *Solomachine) ClientState() *solomachinetypes.ClientState { - return solomachinetypes.NewClientState(solo.ConsensusState()) + return solomachinetypes.NewClientState(solo.ConsensusState(), false) } func (solo *Solomachine) ConsensusState() *solomachinetypes.ConsensusState { diff --git a/x/mint/types/query.pb.gw.go b/x/mint/types/query.pb.gw.go index 644c2782e2..c70c3e60b0 100644 --- a/x/mint/types/query.pb.gw.go +++ b/x/mint/types/query.pb.gw.go @@ -88,6 +88,7 @@ func local_request_Query_AnnualProvisions_0(ctx context.Context, marshaler runti // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/params/types/proposal/query.pb.gw.go b/x/params/types/proposal/query.pb.gw.go index 05f80279a9..c7fab13fca 100644 --- a/x/params/types/proposal/query.pb.gw.go +++ b/x/params/types/proposal/query.pb.gw.go @@ -70,6 +70,7 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/slashing/types/query.pb.gw.go b/x/slashing/types/query.pb.gw.go index 77c4688d9e..5ac3296025 100644 --- a/x/slashing/types/query.pb.gw.go +++ b/x/slashing/types/query.pb.gw.go @@ -142,6 +142,7 @@ func local_request_Query_SigningInfos_0(ctx context.Context, marshaler runtime.M // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/staking/types/query.pb.gw.go b/x/staking/types/query.pb.gw.go index 8995f76454..0c43333020 100644 --- a/x/staking/types/query.pb.gw.go +++ b/x/staking/types/query.pb.gw.go @@ -874,6 +874,7 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_Validators_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/x/upgrade/types/query.pb.gw.go b/x/upgrade/types/query.pb.gw.go index 004a27419c..6d311b5778 100644 --- a/x/upgrade/types/query.pb.gw.go +++ b/x/upgrade/types/query.pb.gw.go @@ -106,6 +106,7 @@ func local_request_Query_AppliedPlan_0(ctx context.Context, marshaler runtime.Ma // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { mux.Handle("GET", pattern_Query_CurrentPlan_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {