-
Notifications
You must be signed in to change notification settings - Fork 628
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adding
ConsensusHost
interface for custom self client/consens…
…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
1 parent
bf158a5
commit 427bf7f
Showing
4 changed files
with
232 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
151
modules/light-clients/08-wasm/types/consensus_host_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |