Skip to content

Commit

Permalink
Merge branch 'release/v8.3.x' into mergify/bp/release/v8.2.x/pr-5785
Browse files Browse the repository at this point in the history
  • Loading branch information
crodriguezvega authored Apr 10, 2024
2 parents c70c9c6 + 98674c7 commit 44e7c4e
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 239 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* (core) [\#6055](https://github.com/cosmos/ibc-go/pull/6055) Introduce a new interface `ConsensusHost` used to validate an IBC `ClientState` and `ConsensusState` against the host chain's underlying consensus parameters.
* (core/02-client) [\#5821](https://github.com/cosmos/ibc-go/pull/5821) Add rpc `VerifyMembershipProof` (querier approach for conditional clients).
* (core/04-channel) [\#5788](https://github.com/cosmos/ibc-go/pull/5788) Add `NewErrorAcknowledgementWithCodespace` to allow codespaces in ack errors.
* (apps/27-interchain-accounts) [\#5785](https://github.com/cosmos/ibc-go/pull/5785) Introduce a new tx message that ICA host submodule can use to query the chain (only those marked with `module_query_safe`) and write the responses to the acknowledgement.
Expand Down
108 changes: 16 additions & 92 deletions modules/core/02-client/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package keeper
import (
"errors"
"fmt"
"reflect"
"strings"

errorsmod "cosmossdk.io/errors"
Expand All @@ -15,14 +14,9 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cometbft/cometbft/light"

"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
)

Expand All @@ -31,6 +25,7 @@ import (
type Keeper struct {
storeKey storetypes.StoreKey
cdc codec.BinaryCodec
consensusHost types.ConsensusHost
legacySubspace types.ParamSubspace
stakingKeeper types.StakingKeeper
upgradeKeeper types.UpgradeKeeper
Expand Down Expand Up @@ -63,6 +58,15 @@ func (k Keeper) UpdateLocalhostClient(ctx sdk.Context, clientState exported.Clie
return clientState.UpdateState(ctx, k.cdc, k.ClientStore(ctx, exported.LocalhostClientID), nil)
}

// SetConsensusHost sets a custom ConsensusHost for self client state and consensus state validation.
func (k *Keeper) SetConsensusHost(consensusHost types.ConsensusHost) {
if consensusHost == nil {
panic(fmt.Errorf("cannot set a nil self consensus host"))
}

k.consensusHost = consensusHost
}

// GenerateClientIdentifier returns the next client identifier.
func (k Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) string {
nextClientSeq := k.GetNextClientSequence(ctx)
Expand Down Expand Up @@ -253,95 +257,15 @@ func (k Keeper) GetLatestClientConsensusState(ctx sdk.Context, clientID string)
// and returns the expected consensus state at that height.
// For now, can only retrieve self consensus states for the current revision
func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) {
selfHeight, ok := height.(types.Height)
if !ok {
return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", types.Height{}, height)
}
// check that height revision matches chainID revision
revision := types.ParseChainID(ctx.ChainID())
if revision != height.GetRevisionNumber() {
return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "chainID revision number does not match height revision number: expected %d, got %d", revision, height.GetRevisionNumber())
}
histInfo, err := k.stakingKeeper.GetHistoricalInfo(ctx, int64(selfHeight.RevisionHeight))
if err != nil {
return nil, errorsmod.Wrapf(err, "height %d", selfHeight.RevisionHeight)
}

consensusState := &ibctm.ConsensusState{
Timestamp: histInfo.Header.Time,
Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()),
NextValidatorsHash: histInfo.Header.NextValidatorsHash,
}
return consensusState, nil
return k.consensusHost.GetSelfConsensusState(ctx, height)
}

// ValidateSelfClient validates the client parameters for a client of the running chain
// This function is only used to validate the client state the counterparty stores for this chain
// Client must be in same revision as the executing chain
// ValidateSelfClient validates the client parameters for a client of the running chain.
// This function is only used to validate the client state the counterparty stores for this chain.
// NOTE: If the client type is not of type Tendermint then delegate to a custom client validator function.
// This allows support for non-Tendermint clients, for example 08-wasm clients.
func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error {
tmClient, ok := clientState.(*ibctm.ClientState)
if !ok {
return errorsmod.Wrapf(types.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T",
&ibctm.ClientState{}, tmClient)
}

if !tmClient.FrozenHeight.IsZero() {
return types.ErrClientFrozen
}

if ctx.ChainID() != tmClient.ChainId {
return errorsmod.Wrapf(types.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s",
ctx.ChainID(), tmClient.ChainId)
}

revision := types.ParseChainID(ctx.ChainID())

// client must be in the same revision as executing chain
if tmClient.LatestHeight.RevisionNumber != revision {
return errorsmod.Wrapf(types.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d",
tmClient.LatestHeight.RevisionNumber, revision)
}

selfHeight := types.NewHeight(revision, uint64(ctx.BlockHeight()))
if tmClient.LatestHeight.GTE(selfHeight) {
return errorsmod.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d",
tmClient.LatestHeight, selfHeight)
}

expectedProofSpecs := commitmenttypes.GetSDKSpecs()
if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) {
return errorsmod.Wrapf(types.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v",
expectedProofSpecs, tmClient.ProofSpecs)
}

if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil {
return errorsmod.Wrapf(types.ErrInvalidClient, "trust-level invalid: %v", err)
}

expectedUbdPeriod, err := k.stakingKeeper.UnbondingTime(ctx)
if err != nil {
return errorsmod.Wrapf(err, "failed to retrieve unbonding period")
}

if expectedUbdPeriod != tmClient.UnbondingPeriod {
return errorsmod.Wrapf(types.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s",
expectedUbdPeriod, tmClient.UnbondingPeriod)
}

if tmClient.UnbondingPeriod < tmClient.TrustingPeriod {
return errorsmod.Wrapf(types.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)",
tmClient.UnbondingPeriod, tmClient.TrustingPeriod)
}

if len(tmClient.UpgradePath) != 0 {
// For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module
expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState}
if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) {
return errorsmod.Wrapf(types.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v",
expectedUpgradePath, tmClient.UpgradePath)
}
}
return nil
return k.consensusHost.ValidateSelfClient(ctx, clientState)
}

// GetUpgradePlan executes the upgrade keeper GetUpgradePlan function.
Expand Down
124 changes: 5 additions & 119 deletions modules/core/02-client/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine"
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
Expand All @@ -44,10 +43,7 @@ const (
maxClockDrift time.Duration = time.Second * 10
)

var (
testClientHeight = types.NewHeight(0, 5)
testClientHeightRevision1 = types.NewHeight(1, 5)
)
var testClientHeight = types.NewHeight(0, 5)

type KeeperTestSuite struct {
testifysuite.Suite
Expand Down Expand Up @@ -144,91 +140,7 @@ func (suite *KeeperTestSuite) TestSetClientConsensusState() {
suite.Require().Equal(suite.consensusState, tmConsState, "ConsensusState not stored correctly")
}

func (suite *KeeperTestSuite) TestValidateSelfClient() {
testClientHeight := types.GetSelfHeight(suite.chainA.GetContext())
testClientHeight.RevisionHeight--

testCases := []struct {
name string
clientState exported.ClientState
expPass bool
}{
{
"success",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
true,
},
{
"success with nil UpgradePath",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), nil),
true,
},
{
"frozen client",
&ibctm.ClientState{ChainId: suite.chainA.ChainID, TrustLevel: ibctm.DefaultTrustLevel, TrustingPeriod: trustingPeriod, UnbondingPeriod: ubdPeriod, MaxClockDrift: maxClockDrift, FrozenHeight: testClientHeight, LatestHeight: testClientHeight, ProofSpecs: commitmenttypes.GetSDKSpecs(), UpgradePath: ibctesting.UpgradePath},
false,
},
{
"incorrect chainID",
ibctm.NewClientState("gaiatestnet", ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
false,
},
{
"invalid client height",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.GetSelfHeight(suite.chainA.GetContext()).Increment().(types.Height), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
false,
},
{
"invalid client type",
solomachine.NewClientState(0, &solomachine.ConsensusState{PublicKey: suite.solomachine.ConsensusState().PublicKey, Diversifier: suite.solomachine.Diversifier, Timestamp: suite.solomachine.Time}),
false,
},
{
"invalid client revision",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeightRevision1, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
false,
},
{
"invalid proof specs",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, nil, ibctesting.UpgradePath),
false,
},
{
"invalid trust level",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), false,
},
{
"invalid unbonding period",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod+10, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
false,
},
{
"invalid trusting period",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ubdPeriod+10, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
false,
},
{
"invalid upgrade path",
ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), []string{"bad", "upgrade", "path"}),
false,
},
}

for _, tc := range testCases {
tc := tc

suite.Run(tc.name, func() {
err := suite.chainA.App.GetIBCKeeper().ClientKeeper.ValidateSelfClient(suite.chainA.GetContext(), tc.clientState)
if tc.expPass {
suite.Require().NoError(err, "expected valid client for case: %s", tc.name)
} else {
suite.Require().Error(err, "expected invalid client for case: %s", tc.name)
}
})
}
}

func (suite KeeperTestSuite) TestGetAllGenesisClients() { //nolint:govet // this is a test, we are okay with copying locks
func (suite *KeeperTestSuite) TestGetAllGenesisClients() {
clientIDs := []string{
exported.LocalhostClientID, testClientID2, testClientID3, testClientID,
}
Expand All @@ -251,7 +163,7 @@ func (suite KeeperTestSuite) TestGetAllGenesisClients() { //nolint:govet // this
suite.Require().Equal(expGenClients.Sort(), genClients)
}

func (suite KeeperTestSuite) TestGetAllGenesisMetadata() { //nolint:govet // this is a test, we are okay with copying locks
func (suite *KeeperTestSuite) TestGetAllGenesisMetadata() { //nolint:govet // this is a test, we are okay with copying locks
expectedGenMetadata := []types.IdentifiedGenesisMetadata{
types.NewIdentifiedGenesisMetadata(
"07-tendermint-1",
Expand Down Expand Up @@ -281,35 +193,9 @@ func (suite KeeperTestSuite) TestGetAllGenesisMetadata() { //nolint:govet // thi
suite.Require().Equal(expectedGenMetadata, actualGenMetadata, "retrieved metadata is unexpected")
}

func (suite KeeperTestSuite) TestGetConsensusState() { //nolint:govet // this is a test, we are okay with copying locks
suite.ctx = suite.ctx.WithBlockHeight(10)
cases := []struct {
name string
height types.Height
expPass bool
}{
{"zero height", types.ZeroHeight(), false},
{"height > latest height", types.NewHeight(0, uint64(suite.ctx.BlockHeight())+1), false},
{"latest height - 1", types.NewHeight(0, uint64(suite.ctx.BlockHeight())-1), true},
{"latest height", types.GetSelfHeight(suite.ctx), true},
}

for i, tc := range cases {
tc := tc
cs, err := suite.keeper.GetSelfConsensusState(suite.ctx, tc.height)
if tc.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)
} else {
suite.Require().Error(err, "Case %d should have failed: %s", i, tc.name)
suite.Require().Nil(cs, "Case %d should have failed: %s", i, tc.name)
}
}
}

// 2 clients in total are created on chainA. The first client is updated so it contains an initial consensus state
// and a consensus state at the update height.
func (suite KeeperTestSuite) TestGetAllConsensusStates() { //nolint:govet // this is a test, we are okay with copying locks
func (suite *KeeperTestSuite) TestGetAllConsensusStates() { //nolint:govet // this is a test, we are okay with copying locks
path := ibctesting.NewPath(suite.chainA, suite.chainB)
suite.coordinator.SetupClients(path)

Expand Down Expand Up @@ -358,7 +244,7 @@ func (suite KeeperTestSuite) TestGetAllConsensusStates() { //nolint:govet // thi
suite.Require().Equal(expConsensusStates, consStates, "%s \n\n%s", expConsensusStates, consStates)
}

func (suite KeeperTestSuite) TestIterateClientStates() { //nolint:govet // this is a test, we are okay with copying locks
func (suite *KeeperTestSuite) TestIterateClientStates() { //nolint:govet // this is a test, we are okay with copying locks
paths := []*ibctesting.Path{
ibctesting.NewPath(suite.chainA, suite.chainB),
ibctesting.NewPath(suite.chainA, suite.chainB),
Expand Down
7 changes: 7 additions & 0 deletions modules/core/02-client/types/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
errorsmod "cosmossdk.io/errors"

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

host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
Expand All @@ -21,6 +22,12 @@ var (
_ codectypes.UnpackInterfacesMessage = (*ConsensusStateWithHeight)(nil)
)

// ConsensusHost defines an interface used to validate an IBC ClientState and ConsensusState against the host chain's underlying consensus parameters.
type ConsensusHost interface {
GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error)
ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error
}

// NewIdentifiedClientState creates a new IdentifiedClientState instance
func NewIdentifiedClientState(clientID string, clientState exported.ClientState) IdentifiedClientState {
msg, ok := clientState.(proto.Message)
Expand Down
1 change: 1 addition & 0 deletions modules/core/02-client/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ var (
ErrClientNotActive = errorsmod.Register(SubModuleName, 29, "client state is not active")
ErrFailedMembershipVerification = errorsmod.Register(SubModuleName, 30, "membership verification failed")
ErrFailedNonMembershipVerification = errorsmod.Register(SubModuleName, 31, "non-membership verification failed")
ErrClientTypeNotSupported = errorsmod.Register(SubModuleName, 32, "client type not supported")
)
8 changes: 4 additions & 4 deletions modules/core/03-connection/keeper/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ func (k Keeper) ConnOpenTry(
}

// validate client parameters of a chainB client stored on chainA
if err := k.clientKeeper.ValidateSelfClient(ctx, clientState); err != nil {
if err := k.consensusHost.ValidateSelfClient(ctx, clientState); err != nil {
return "", err
}

expectedConsensusState, err := k.clientKeeper.GetSelfConsensusState(ctx, consensusHeight)
expectedConsensusState, err := k.consensusHost.GetSelfConsensusState(ctx, consensusHeight)
if err != nil {
return "", errorsmod.Wrapf(err, "self consensus state not found for height %s", consensusHeight.String())
}
Expand Down Expand Up @@ -205,12 +205,12 @@ func (k Keeper) ConnOpenAck(
}

// validate client parameters of a chainA client stored on chainB
if err := k.clientKeeper.ValidateSelfClient(ctx, clientState); err != nil {
if err := k.consensusHost.ValidateSelfClient(ctx, clientState); err != nil {
return err
}

// Retrieve chainA's consensus state at consensusheight
expectedConsensusState, err := k.clientKeeper.GetSelfConsensusState(ctx, consensusHeight)
expectedConsensusState, err := k.consensusHost.GetSelfConsensusState(ctx, consensusHeight)
if err != nil {
return errorsmod.Wrapf(err, "self consensus state not found for height %s", consensusHeight.String())
}
Expand Down
Loading

0 comments on commit 44e7c4e

Please sign in to comment.