Skip to content

Commit

Permalink
feat: adding ConsensusHost interface for custom self client/consens…
Browse files Browse the repository at this point in the history
…us state validation (backport #6055) (#6547)

* feat: adding `ConsensusHost` interface for custom self client/consensus state validation (#6055)

Co-authored-by: chatton <github.qpeyb@simplelogin.fr>
(cherry picked from commit 50d2a08)

# Conflicts:
#	CHANGELOG.md
#	modules/core/02-client/keeper/keeper.go
#	modules/core/02-client/keeper/keeper_test.go
#	modules/core/02-client/types/errors.go

* restore changes from release/v8.3.x

* chore: update changelog

---------

Co-authored-by: Damian Nolan <damiannolan@gmail.com>
  • Loading branch information
mergify[bot] and damiannolan authored Jun 16, 2024
1 parent bf158a5 commit 427bf7f
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 1 deletion.
4 changes: 3 additions & 1 deletion modules/light-clients/08-wasm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* [\#5923](https://github.com/cosmos/ibc-go/pull/5923) imp: adding 08-wasm build opts for libwasmvm linking disabled
* [\#5923](https://github.com/cosmos/ibc-go/pull/5923) imp: add 08-wasm build opts for libwasmvm linking disabled

### Features

* [\#6055](https://github.com/cosmos/ibc-go/pull/6055) feat: add 08-wasm `ConsensusHost` implementation for custom self client/consensus state validation in 03-connection handshake.

### Bug Fixes

<!-- markdown-link-check-disable-next-line -->
Expand Down
1 change: 1 addition & 0 deletions modules/light-clients/08-wasm/testing/simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ func NewSimApp(
app.IBCKeeper = ibckeeper.NewKeeper(
appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)

// Register the proposal types
// Deprecated: Avoid adding new handlers, instead use the new proposal flow
// by granting the governance module the right to execute the message.
Expand Down
77 changes: 77 additions & 0 deletions modules/light-clients/08-wasm/types/consensus_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package types

import (
"fmt"

errorsmod "cosmossdk.io/errors"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"

clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
)

// WasmConsensusHost implements the 02-client types.ConsensusHost interface.
type WasmConsensusHost struct {
cdc codec.BinaryCodec
delegate clienttypes.ConsensusHost
}

var _ clienttypes.ConsensusHost = (*WasmConsensusHost)(nil)

// NewWasmConsensusHost creates and returns a new ConsensusHost for wasm wrapped consensus client state and consensus state self validation.
func NewWasmConsensusHost(cdc codec.BinaryCodec, delegate clienttypes.ConsensusHost) (*WasmConsensusHost, error) {
if cdc == nil {
return nil, fmt.Errorf("wasm consensus host codec is nil")
}

if delegate == nil {
return nil, fmt.Errorf("wasm delegate consensus host is nil")
}

return &WasmConsensusHost{
cdc: cdc,
delegate: delegate,
}, nil
}

// GetSelfConsensusState implements the 02-client types.ConsensusHost interface.
func (w *WasmConsensusHost) GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) {
consensusState, err := w.delegate.GetSelfConsensusState(ctx, height)
if err != nil {
return nil, err
}

// encode consensusState to wasm.ConsensusState.Data
bz, err := w.cdc.MarshalInterface(consensusState)
if err != nil {
return nil, err
}

wasmConsensusState := &ConsensusState{
Data: bz,
}

return wasmConsensusState, nil
}

// ValidateSelfClient implements the 02-client types.ConsensusHost interface.
func (w *WasmConsensusHost) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error {
wasmClientState, ok := clientState.(*ClientState)
if !ok {
return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "client must be a wasm client, expected: %T, got: %T", ClientState{}, wasmClientState)
}

if wasmClientState.Data == nil {
return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "wasm client state data is nil")
}

// unmarshal the wasmClientState bytes into the ClientState interface and call self validation
var unwrappedClientState exported.ClientState
if err := w.cdc.UnmarshalInterface(wasmClientState.Data, &unwrappedClientState); err != nil {
return err
}

return w.delegate.ValidateSelfClient(ctx, unwrappedClientState)
}
151 changes: 151 additions & 0 deletions modules/light-clients/08-wasm/types/consensus_host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package types_test

import (
sdk "github.com/cosmos/cosmos-sdk/types"

wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
"github.com/cosmos/ibc-go/v8/testing/mock"
)

func (suite *TypesTestSuite) TestGetSelfConsensusState() {
var (
consensusHost clienttypes.ConsensusHost
consensusState exported.ConsensusState
height clienttypes.Height
)

cases := []struct {
name string
malleate func()
expError error
}{
{
name: "success",
malleate: func() {},
expError: nil,
},
{
name: "failure: delegate error",
malleate: func() {
consensusHost.(*mock.ConsensusHost).GetSelfConsensusStateFn = func(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) {
return nil, mock.MockApplicationCallbackError
}
},
expError: mock.MockApplicationCallbackError,
},
}

for i, tc := range cases {
tc := tc
suite.Run(tc.name, func() {
suite.SetupTest()
height = clienttypes.ZeroHeight()

wrappedClientConsensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientConsensusState)
consensusState = types.NewConsensusState(wrappedClientConsensusStateBz)

consensusHost = &mock.ConsensusHost{
GetSelfConsensusStateFn: func(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) {
return consensusState, nil
},
}

tc.malleate()

var err error
consensusHost, err = types.NewWasmConsensusHost(suite.chainA.Codec, consensusHost)
suite.Require().NoError(err)

suite.chainA.App.GetIBCKeeper().ClientKeeper.SetConsensusHost(
consensusHost,
)

cs, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetSelfConsensusState(suite.chainA.GetContext(), height)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err, "Case %d should have passed: %s", i, tc.name)
suite.Require().NotNil(cs, "Case %d should have passed: %s", i, tc.name)
suite.Require().NotNil(cs.(*types.ConsensusState).Data, "Case %d should have passed: %s", i, tc.name)
} else {
suite.Require().ErrorIs(err, tc.expError, "Case %d should have failed: %s", i, tc.name)
suite.Require().Nil(cs, "Case %d should have failed: %s", i, tc.name)
}
})
}
}

func (suite *TypesTestSuite) TestValidateSelfClient() {
var (
clientState exported.ClientState
consensusHost clienttypes.ConsensusHost
)

testCases := []struct {
name string
malleate func()
expError error
}{
{
name: "success",
malleate: func() {},
expError: nil,
},
{
name: "failure: invalid data",
malleate: func() {
clientState = types.NewClientState(nil, wasmtesting.Code, clienttypes.ZeroHeight())
},
expError: clienttypes.ErrInvalidClient,
},
{
name: "failure: invalid clientstate type",
malleate: func() {
clientState = &ibctm.ClientState{}
},
expError: clienttypes.ErrInvalidClient,
},
{
name: "failure: delegate error propagates",
malleate: func() {
consensusHost.(*mock.ConsensusHost).ValidateSelfClientFn = func(ctx sdk.Context, clientState exported.ClientState) error {
return mock.MockApplicationCallbackError
}
},
expError: mock.MockApplicationCallbackError,
},
}

for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
suite.SetupTest()

clientState = types.NewClientState(wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum), wasmtesting.Code, clienttypes.ZeroHeight())
consensusHost = &mock.ConsensusHost{}

tc.malleate()

var err error
consensusHost, err = types.NewWasmConsensusHost(suite.chainA.Codec, consensusHost)
suite.Require().NoError(err)

suite.chainA.App.GetIBCKeeper().ClientKeeper.SetConsensusHost(
consensusHost,
)

err = suite.chainA.App.GetIBCKeeper().ClientKeeper.ValidateSelfClient(suite.chainA.GetContext(), clientState)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err, "expected valid client for case: %s", tc.name)
} else {
suite.Require().ErrorIs(err, tc.expError, "expected %s got %s", tc.expError, err)
}
})
}
}

0 comments on commit 427bf7f

Please sign in to comment.