From b52ffa08e1394a37f83baabc226d841b4914b4ab Mon Sep 17 00:00:00 2001 From: dauTT Date: Fri, 4 Sep 2020 22:59:22 +0200 Subject: [PATCH] Implement ADR 026 (#7029) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add allow_governance_override_after_expiry flag to tendermint NewCreateClientCmd * 1) Add LatestTimestamp to ClientState struct by adding a new attribute latest_timestamp to the message ClientState in proto/ibc/tendermint/tendermint.proto 2) Autogenerate x/ibc/07-tendermint/types/tendermint.pb.go by running 'make proto-gen'. Strangely, as a side effect x/distribution/types/genesis.pb.go, x/evidence/types/genesis.pb.go were also modified by the command 'make proto-gen' 3) Add Expired() function * Fix tests * 1) Add allow_governance_override_after_expiry flag to tendemint clientStatus 2) Add allow_governance_override_after_misbehaviour flag to tendermint ClientStatus * Cosmetic changes * Fix tests * Add Unfreeze function * Add new ClientUpdateProposal type * Add minor fixes * Add NewClientUpdateProposalHandler unit tests * Fix proto-lint-docker * Delete x/ibc/07-tendermint/tendermint_test.go * Follow convention to put signer last in msg function signature * 1) Add GetLatestTimestamp function to ClientStatus interface 2) Change Expired() signature to Expired(now time.Time) * 1) Add override flag to UpdateClient function 2) Fix tests * Refactor HandleClientUpdateProposal * 1) Extend exported Header interface with MarshalBinaryBare and UnmarshalBinaryBare methods 2) Move ClientUpdateProposal message to from ibc.proto to client.proto 3) Refactoring code 4) Add override flag to UpdateClient method 5) Fix tests * 1) Uncomment tests and clean up code 2) Add basic validation of the header (ValidateBasic) when the override flag is true * Cosmetic changes * Add TODO comments * Fix header MarshalBinaryBare, UnmarshalBinaryBare by using protobuf encoding/decoding * Fix proto comments * Fix override logic * undo gettimestamp for solo machine and localhost * add update after proposal func, some major refactoring in progress, various issues addressed * fix tendermint proposal update handling * run make proto-gen * remove timestamp from tendemint client * fix build issue for tm types * apply various review comments * add tests for 02-client functionality * self review fixes * typo * update tests slightly * update tendermint proposal handling tests * Update x/ibc/02-client/keeper/proposal.go Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/ibc/07-tendermint/types/proposal_handle.go Co-authored-by: Aditya * Update x/ibc/07-tendermint/types/proposal_handle.go Co-authored-by: Aditya * apply most of @fedekunze and some of @AdityaSripal suggestions * convert test to bools * update docs and increase code cov * fix build * fix typo, remove omitempty * add switch * apply @fedekunze latest suggestions * fix lint * Update x/ibc/02-client/keeper/proposal_test.go Co-authored-by: Christopher Goes Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Colin Axner Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Aditya Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- client/grpc/reflection/reflection.pb.gw.go | 1 + client/grpc/simulate/simulate.pb.gw.go | 1 + proto/ibc/client/client.proto | 15 + .../solomachine/v1/solomachine.proto | 4 + proto/ibc/tendermint/tendermint.proto | 9 + simapp/app.go | 24 +- x/auth/types/query.pb.gw.go | 1 + x/bank/types/query.pb.gw.go | 1 + x/distribution/types/query.pb.gw.go | 1 + x/evidence/types/query.pb.gw.go | 1 + x/gov/types/query.pb.gw.go | 1 + x/ibc-transfer/types/query.pb.gw.go | 1 + x/ibc/02-client/handler.go | 14 + x/ibc/02-client/handler_test.go | 76 ++++ x/ibc/02-client/keeper/client.go | 2 +- x/ibc/02-client/keeper/client_test.go | 28 +- x/ibc/02-client/keeper/grpc_query_test.go | 4 +- x/ibc/02-client/keeper/keeper_test.go | 32 +- x/ibc/02-client/keeper/proposal.go | 55 +++ x/ibc/02-client/keeper/proposal_test.go | 95 +++++ x/ibc/02-client/types/client.pb.go | 394 ++++++++++++++++-- x/ibc/02-client/types/codec_test.go | 2 +- x/ibc/02-client/types/errors.go | 2 + x/ibc/02-client/types/events.go | 7 +- x/ibc/02-client/types/genesis_test.go | 10 +- x/ibc/02-client/types/msgs_test.go | 6 +- x/ibc/02-client/types/proposal.go | 55 +++ x/ibc/02-client/types/proposal_test.go | 77 ++++ x/ibc/02-client/types/query.pb.gw.go | 1 + x/ibc/03-connection/types/msgs_test.go | 8 +- x/ibc/03-connection/types/query.pb.gw.go | 1 + x/ibc/04-channel/types/query.pb.gw.go | 1 + x/ibc/07-tendermint/client/cli/tx.go | 14 +- x/ibc/07-tendermint/types/client_state.go | 27 +- .../07-tendermint/types/client_state_test.go | 26 +- x/ibc/07-tendermint/types/codec.go | 2 +- .../types/misbehaviour_handle_test.go | 36 +- x/ibc/07-tendermint/types/proposal_handle.go | 127 ++++++ .../types/proposal_handle_test.go | 356 ++++++++++++++++ x/ibc/07-tendermint/types/tendermint.pb.go | 200 ++++++--- x/ibc/07-tendermint/types/update.go | 2 +- x/ibc/07-tendermint/types/update_test.go | 28 +- x/ibc/09-localhost/types/client_state.go | 8 + x/ibc/09-localhost/types/client_state_test.go | 15 + x/ibc/exported/client.go | 1 + x/ibc/genesis_test.go | 6 +- .../solomachine/client/cli/tx.go | 16 +- .../solomachine/types/client_state.go | 7 +- .../solomachine/types/client_state_test.go | 34 +- .../types/misbehaviour_handle_test.go | 2 +- .../solomachine/types/proposal_handle.go | 53 +++ .../solomachine/types/proposal_handle_test.go | 72 ++++ .../solomachine/types/solomachine.pb.go | 117 ++++-- .../solomachine/types/update_test.go | 2 +- x/ibc/spec/01_concepts.md | 22 + x/ibc/spec/06_events.md | 10 + x/ibc/spec/README.md | 3 +- x/ibc/testing/chain.go | 43 +- x/ibc/testing/solomachine.go | 3 +- x/mint/types/query.pb.gw.go | 1 + x/params/types/proposal/query.pb.gw.go | 1 + x/slashing/types/query.pb.gw.go | 1 + x/staking/types/query.pb.gw.go | 1 + x/upgrade/types/query.pb.gw.go | 1 + 64 files changed, 1869 insertions(+), 298 deletions(-) create mode 100644 x/ibc/02-client/handler_test.go create mode 100644 x/ibc/02-client/keeper/proposal.go create mode 100644 x/ibc/02-client/keeper/proposal_test.go create mode 100644 x/ibc/02-client/types/proposal.go create mode 100644 x/ibc/02-client/types/proposal_test.go create mode 100644 x/ibc/07-tendermint/types/proposal_handle.go create mode 100644 x/ibc/07-tendermint/types/proposal_handle_test.go create mode 100644 x/ibc/light-clients/solomachine/types/proposal_handle.go create mode 100644 x/ibc/light-clients/solomachine/types/proposal_handle_test.go diff --git a/client/grpc/reflection/reflection.pb.gw.go b/client/grpc/reflection/reflection.pb.gw.go index b3e8c4641..ab486750e 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 733449d03..78cb85437 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 d194cce52..a2c7d9ded 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 22d53ccd3..a6463ef75 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 007593238..2fce5b648 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 feac21872..a26d08bd0 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 7e80fa6fa..3b156842d 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 85d776791..ca9c1ddbf 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 b3a714f34..890e51e40 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 e9346629c..e15d16352 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 4e2c61f5f..e9fc72f30 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 9d5093a47..23b91aafc 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 466d6d173..67351e4ae 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 000000000..0d2955f39 --- /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 b4880b9d7..05db718ea 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 3733dc56a..c3c4ce837 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 6b9fe8d92..cd15bcd39 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 d3e03f0b8..a5cb4b481 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 000000000..c4b844bbc --- /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 000000000..11ca73504 --- /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 f0aff8873..e0a194d44 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 381b32d2f..cab2a2b70 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 2526f1618..fe9099eae 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 08e83a497..839ccb508 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 e3a251d27..c4d84bd00 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 b2089ade3..4b9113084 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 000000000..7f9abb004 --- /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 000000000..09ae1adc5 --- /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 3fd7e0efa..85d7a07e3 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 3ac72fc3c..5d0a0d84b 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 6e2f3323c..54c52c635 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 1181eca5a..933cba2d7 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 f2ba79a9c..81d074c90 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 258ec0cd7..0b919f77b 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 b275a50c3..bfd6ee2c4 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 9a2c953d9..f82b74efc 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 6d62948b3..ca891dcfb 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 000000000..fbcf9a0d1 --- /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 000000000..cc343c6d7 --- /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 686d96cad..c7e462974 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 284d9dba7..30a216a83 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 31033eb03..a30dea0e9 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 bd3a3728f..d1350783a 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 a219454f9..6e8c0ba92 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 2845d1a74..544dfd24f 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 042d820cc..ecb824b0f 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 0d8350424..853ef8118 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 45d1ff86b..aa2c03f22 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 944cb8de9..16b5ffe54 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 906d98a6d..aec4677ba 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 000000000..4c5f2325b --- /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 000000000..a5ff9180f --- /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 0329a3ace..907689ba7 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 5ab45e797..6baadf986 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 e948f3983..d5b400230 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 466a8bd2c..9f96b089b 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 78e6b489c..8dd5bc0ed 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 459ddec4b..a3982b10e 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 ecb57f15b..aba04b3f8 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 644c2782e..c70c3e60b 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 05f80279a..c7fab13fc 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 77c4688d9..5ac329602 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 8995f7645..0c4333302 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 004a27419..6d311b577 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) {