diff --git a/modules/core/errors/errors.go b/modules/core/errors/errors.go index 30dd983151a..6ffa5561756 100644 --- a/modules/core/errors/errors.go +++ b/modules/core/errors/errors.go @@ -60,4 +60,7 @@ var ( // ErrNotFound defines an error when requested entity doesn't exist in the state. ErrNotFound = errorsmod.Register(codespace, 16, "not found") + + // ErrClientStatusNotActive defines an error when an operation expects a client to be active + ErrClientStatusNotActive = errorsmod.Register(codespace, 17, "client is not active") ) diff --git a/modules/light-clients/07-tendermint/client_state.go b/modules/light-clients/07-tendermint/client_state.go index 826b7554a0b..11a21de6e9c 100644 --- a/modules/light-clients/07-tendermint/client_state.go +++ b/modules/light-clients/07-tendermint/client_state.go @@ -219,6 +219,10 @@ func (cs ClientState) VerifyMembership( path exported.Path, value []byte, ) error { + if cs.Status(ctx, clientStore, cdc) != exported.Active { + return errorsmod.Wrapf(ibcerrors.ErrClientStatusNotActive, "client status is not active") + } + if cs.GetLatestHeight().LT(height) { return errorsmod.Wrapf( ibcerrors.ErrInvalidHeight, diff --git a/modules/light-clients/07-tendermint/client_state_test.go b/modules/light-clients/07-tendermint/client_state_test.go index 7c436531297..d5c750d3842 100644 --- a/modules/light-clients/07-tendermint/client_state_test.go +++ b/modules/light-clients/07-tendermint/client_state_test.go @@ -503,6 +503,28 @@ func (suite *TendermintTestSuite) TestVerifyMembership() { proof = []byte{} }, false, }, + { + name: "client state is frozen", + malleate: func() { + clientState := testingpath.EndpointA.GetClientState().(*ibctm.ClientState) + // Set the FrozenHeight to a non-zero value to indicate the client is frozen (inactive). + clientState.FrozenHeight = clienttypes.NewHeight(1, 1) + // Update the client state in the testing path to reflect the frozen state. + testingpath.EndpointA.SetClientState(clientState) + }, + expPass: false, // Expect this test to fail since the state is not active (frozen). + }, + { + name: "client state is expired", + malleate: func() { + clientState := testingpath.EndpointA.GetClientState().(*ibctm.ClientState) + // Advance the LatestHeight to simulate expiration. + clientState.LatestHeight = clientState.LatestHeight.Increment().(clienttypes.Height) + // Update the client state in the testing path to reflect the expired state. + testingpath.EndpointA.SetClientState(clientState) + }, + expPass: false, // Expect this test to fail since the client state is expired. + }, } for _, tc := range testCases { diff --git a/modules/light-clients/08-wasm/types/client_state.go b/modules/light-clients/08-wasm/types/client_state.go index cced48113d1..0ad9b59a991 100644 --- a/modules/light-clients/08-wasm/types/client_state.go +++ b/modules/light-clients/08-wasm/types/client_state.go @@ -137,6 +137,10 @@ func (cs ClientState) VerifyMembership( path exported.Path, value []byte, ) error { + if cs.Status(ctx, clientStore, cdc) != exported.Active { + return errorsmod.Wrapf(ibcerrors.ErrClientStatusNotActive, "client status is not active") + } + proofHeight, ok := height.(clienttypes.Height) if !ok { return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) diff --git a/modules/light-clients/08-wasm/types/client_state_test.go b/modules/light-clients/08-wasm/types/client_state_test.go index 5b7f4d94d3a..33383d24af2 100644 --- a/modules/light-clients/08-wasm/types/client_state_test.go +++ b/modules/light-clients/08-wasm/types/client_state_test.go @@ -448,6 +448,32 @@ func (suite *TypesTestSuite) TestVerifyMembership() { }, ibcerrors.ErrInvalidType, }, + { + "client state is frozen", + func() { + // Simulate the client state being frozen by registering a query callback + // that returns a status indicating the client is frozen. + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + ibcerrors.ErrClientStatusNotActive, // Adjust based on the expected outcome when the client is frozen. + }, + { + "client state is expired", + func() { + // Simulate the client state being expired by registering a query callback + // that returns a status indicating the client is expired. + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Expired.String()}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + ibcerrors.ErrClientStatusNotActive, // This needs to be defined according to your error handling for expired clients. + }, } for _, tc := range testCases {