Skip to content

Commit 7200c7f

Browse files
damiannolanchatton
andauthored
refactor: remove hardcoding of tendermint self client validation (#5836)
* chore(08-wasm): add VerifyMembershipProof to stargate query acceptlist * chore: update service definition URL in 08-wasm stargate accepted queries * chore: add doc comment to querier test, address nit to move defaultAcceptList * feat(draft): add custom client validator func * feat: add SelfClientValidator type alias func and refactor tests to confirm it works * refactor: updated ibc client keeper to use interface type for self client validation of consensus parameters * lint: make lint-fix * chore: merge main and fix linter * test: cleaned up GetSelfConsensusState tests * test: added test cases for custom validator logic * nit: rename receiver arg * fix: put back ibctm import from merge conflicts --------- Co-authored-by: chatton <[email protected]>
1 parent 055a3e3 commit 7200c7f

File tree

6 files changed

+328
-143
lines changed

6 files changed

+328
-143
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package keeper
2+
3+
import (
4+
"reflect"
5+
6+
errorsmod "cosmossdk.io/errors"
7+
upgradetypes "cosmossdk.io/x/upgrade/types"
8+
9+
sdk "github.com/cosmos/cosmos-sdk/types"
10+
11+
"github.com/cometbft/cometbft/light"
12+
13+
"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
14+
commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
15+
ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors"
16+
"github.com/cosmos/ibc-go/v8/modules/core/exported"
17+
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
18+
)
19+
20+
var _ types.SelfClientValidator = (*TendermintClientValidator)(nil)
21+
22+
// TendermintClientValidator implements the SelfClientValidator interface.
23+
type TendermintClientValidator struct {
24+
stakingKeeper types.StakingKeeper
25+
}
26+
27+
// NewTendermintClientValidator creates and returns a new SelfClientValidator for tendermint consensus.
28+
func NewTendermintClientValidator(stakingKeeper types.StakingKeeper) *TendermintClientValidator {
29+
return &TendermintClientValidator{
30+
stakingKeeper: stakingKeeper,
31+
}
32+
}
33+
34+
// GetSelfConsensusState implements types.SelfClientValidatorI.
35+
func (tcv *TendermintClientValidator) GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) {
36+
selfHeight, ok := height.(types.Height)
37+
if !ok {
38+
return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", types.Height{}, height)
39+
}
40+
41+
// check that height revision matches chainID revision
42+
revision := types.ParseChainID(ctx.ChainID())
43+
if revision != height.GetRevisionNumber() {
44+
return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "chainID revision number does not match height revision number: expected %d, got %d", revision, height.GetRevisionNumber())
45+
}
46+
47+
histInfo, err := tcv.stakingKeeper.GetHistoricalInfo(ctx, int64(selfHeight.RevisionHeight))
48+
if err != nil {
49+
return nil, errorsmod.Wrapf(err, "height %d", selfHeight.RevisionHeight)
50+
}
51+
52+
consensusState := &ibctm.ConsensusState{
53+
Timestamp: histInfo.Header.Time,
54+
Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()),
55+
NextValidatorsHash: histInfo.Header.NextValidatorsHash,
56+
}
57+
58+
return consensusState, nil
59+
}
60+
61+
// ValidateSelfClient implements types.SelfClientValidatorI.
62+
func (tcv *TendermintClientValidator) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error {
63+
tmClient, ok := clientState.(*ibctm.ClientState)
64+
if !ok {
65+
return errorsmod.Wrapf(types.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T", &ibctm.ClientState{}, tmClient)
66+
}
67+
68+
if !tmClient.FrozenHeight.IsZero() {
69+
return types.ErrClientFrozen
70+
}
71+
72+
if ctx.ChainID() != tmClient.ChainId {
73+
return errorsmod.Wrapf(types.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s",
74+
ctx.ChainID(), tmClient.ChainId)
75+
}
76+
77+
revision := types.ParseChainID(ctx.ChainID())
78+
79+
// client must be in the same revision as executing chain
80+
if tmClient.LatestHeight.RevisionNumber != revision {
81+
return errorsmod.Wrapf(types.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d",
82+
tmClient.LatestHeight.RevisionNumber, revision)
83+
}
84+
85+
selfHeight := types.NewHeight(revision, uint64(ctx.BlockHeight()))
86+
if tmClient.LatestHeight.GTE(selfHeight) {
87+
return errorsmod.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d",
88+
tmClient.LatestHeight, selfHeight)
89+
}
90+
91+
expectedProofSpecs := commitmenttypes.GetSDKSpecs()
92+
if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) {
93+
return errorsmod.Wrapf(types.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v",
94+
expectedProofSpecs, tmClient.ProofSpecs)
95+
}
96+
97+
if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil {
98+
return errorsmod.Wrapf(types.ErrInvalidClient, "trust-level invalid: %v", err)
99+
}
100+
101+
expectedUbdPeriod, err := tcv.stakingKeeper.UnbondingTime(ctx)
102+
if err != nil {
103+
return errorsmod.Wrapf(err, "failed to retrieve unbonding period")
104+
}
105+
106+
if expectedUbdPeriod != tmClient.UnbondingPeriod {
107+
return errorsmod.Wrapf(types.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s",
108+
expectedUbdPeriod, tmClient.UnbondingPeriod)
109+
}
110+
111+
if tmClient.UnbondingPeriod < tmClient.TrustingPeriod {
112+
return errorsmod.Wrapf(types.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)",
113+
tmClient.UnbondingPeriod, tmClient.TrustingPeriod)
114+
}
115+
116+
if len(tmClient.UpgradePath) != 0 {
117+
// For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module
118+
expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState}
119+
if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) {
120+
return errorsmod.Wrapf(types.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v",
121+
expectedUpgradePath, tmClient.UpgradePath)
122+
}
123+
}
124+
125+
return nil
126+
}

modules/core/02-client/keeper/keeper.go

+27-102
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package keeper
33
import (
44
"errors"
55
"fmt"
6-
"reflect"
76
"strings"
87

98
errorsmod "cosmossdk.io/errors"
@@ -15,12 +14,8 @@ import (
1514
"github.com/cosmos/cosmos-sdk/codec"
1615
sdk "github.com/cosmos/cosmos-sdk/types"
1716

18-
"github.com/cometbft/cometbft/light"
19-
2017
"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
21-
commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
2218
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
23-
ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors"
2419
"github.com/cosmos/ibc-go/v8/modules/core/exported"
2520
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
2621
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
@@ -29,21 +24,23 @@ import (
2924
// Keeper represents a type that grants read and write permissions to any client
3025
// state information
3126
type Keeper struct {
32-
storeKey storetypes.StoreKey
33-
cdc codec.BinaryCodec
34-
legacySubspace types.ParamSubspace
35-
stakingKeeper types.StakingKeeper
36-
upgradeKeeper types.UpgradeKeeper
27+
storeKey storetypes.StoreKey
28+
cdc codec.BinaryCodec
29+
legacySubspace types.ParamSubspace
30+
selfClientValidator types.SelfClientValidator
31+
stakingKeeper types.StakingKeeper
32+
upgradeKeeper types.UpgradeKeeper
3733
}
3834

3935
// NewKeeper creates a new NewKeeper instance
4036
func NewKeeper(cdc codec.BinaryCodec, key storetypes.StoreKey, legacySubspace types.ParamSubspace, sk types.StakingKeeper, uk types.UpgradeKeeper) Keeper {
4137
return Keeper{
42-
storeKey: key,
43-
cdc: cdc,
44-
legacySubspace: legacySubspace,
45-
stakingKeeper: sk,
46-
upgradeKeeper: uk,
38+
storeKey: key,
39+
cdc: cdc,
40+
legacySubspace: legacySubspace,
41+
selfClientValidator: NewTendermintClientValidator(sk),
42+
stakingKeeper: sk,
43+
upgradeKeeper: uk,
4744
}
4845
}
4946

@@ -63,6 +60,15 @@ func (k Keeper) UpdateLocalhostClient(ctx sdk.Context, clientState exported.Clie
6360
return clientState.UpdateState(ctx, k.cdc, k.ClientStore(ctx, exported.LocalhostClientID), nil)
6461
}
6562

63+
// SetSelfClientValidator sets a custom self client validation function.
64+
func (k *Keeper) SetSelfClientValidator(selfClientValidator types.SelfClientValidator) {
65+
if selfClientValidator == nil {
66+
panic(fmt.Errorf("cannot set a nil self client validator"))
67+
}
68+
69+
k.selfClientValidator = selfClientValidator
70+
}
71+
6672
// GenerateClientIdentifier returns the next client identifier.
6773
func (k Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) string {
6874
nextClientSeq := k.GetNextClientSequence(ctx)
@@ -282,96 +288,15 @@ func (k Keeper) GetLatestClientConsensusState(ctx sdk.Context, clientID string)
282288
// and returns the expected consensus state at that height.
283289
// For now, can only retrieve self consensus states for the current revision
284290
func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) {
285-
selfHeight, ok := height.(types.Height)
286-
if !ok {
287-
return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", types.Height{}, height)
288-
}
289-
// check that height revision matches chainID revision
290-
revision := types.ParseChainID(ctx.ChainID())
291-
if revision != height.GetRevisionNumber() {
292-
return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "chainID revision number does not match height revision number: expected %d, got %d", revision, height.GetRevisionNumber())
293-
}
294-
histInfo, err := k.stakingKeeper.GetHistoricalInfo(ctx, int64(selfHeight.RevisionHeight))
295-
if err != nil {
296-
return nil, errorsmod.Wrapf(err, "height %d", selfHeight.RevisionHeight)
297-
}
298-
299-
consensusState := &ibctm.ConsensusState{
300-
Timestamp: histInfo.Header.Time,
301-
Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()),
302-
NextValidatorsHash: histInfo.Header.NextValidatorsHash,
303-
}
304-
305-
return consensusState, nil
291+
return k.selfClientValidator.GetSelfConsensusState(ctx, height)
306292
}
307293

308-
// ValidateSelfClient validates the client parameters for a client of the running chain
309-
// This function is only used to validate the client state the counterparty stores for this chain
310-
// Client must be in same revision as the executing chain
294+
// ValidateSelfClient validates the client parameters for a client of the running chain.
295+
// This function is only used to validate the client state the counterparty stores for this chain.
296+
// NOTE: If the client type is not of type Tendermint then delegate to a custom client validator function.
297+
// This allows support for non-Tendermint clients, for example 08-wasm clients.
311298
func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error {
312-
tmClient, ok := clientState.(*ibctm.ClientState)
313-
if !ok {
314-
return errorsmod.Wrapf(types.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T",
315-
&ibctm.ClientState{}, tmClient)
316-
}
317-
318-
if !tmClient.FrozenHeight.IsZero() {
319-
return types.ErrClientFrozen
320-
}
321-
322-
if ctx.ChainID() != tmClient.ChainId {
323-
return errorsmod.Wrapf(types.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s",
324-
ctx.ChainID(), tmClient.ChainId)
325-
}
326-
327-
revision := types.ParseChainID(ctx.ChainID())
328-
329-
// client must be in the same revision as executing chain
330-
if tmClient.LatestHeight.RevisionNumber != revision {
331-
return errorsmod.Wrapf(types.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d",
332-
tmClient.LatestHeight.RevisionNumber, revision)
333-
}
334-
335-
selfHeight := types.NewHeight(revision, uint64(ctx.BlockHeight()))
336-
if tmClient.LatestHeight.GTE(selfHeight) {
337-
return errorsmod.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d",
338-
tmClient.LatestHeight, selfHeight)
339-
}
340-
341-
expectedProofSpecs := commitmenttypes.GetSDKSpecs()
342-
if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) {
343-
return errorsmod.Wrapf(types.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v",
344-
expectedProofSpecs, tmClient.ProofSpecs)
345-
}
346-
347-
if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil {
348-
return errorsmod.Wrapf(types.ErrInvalidClient, "trust-level invalid: %v", err)
349-
}
350-
351-
expectedUbdPeriod, err := k.stakingKeeper.UnbondingTime(ctx)
352-
if err != nil {
353-
return errorsmod.Wrapf(err, "failed to retrieve unbonding period")
354-
}
355-
356-
if expectedUbdPeriod != tmClient.UnbondingPeriod {
357-
return errorsmod.Wrapf(types.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s",
358-
expectedUbdPeriod, tmClient.UnbondingPeriod)
359-
}
360-
361-
if tmClient.UnbondingPeriod < tmClient.TrustingPeriod {
362-
return errorsmod.Wrapf(types.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)",
363-
tmClient.UnbondingPeriod, tmClient.TrustingPeriod)
364-
}
365-
366-
if len(tmClient.UpgradePath) != 0 {
367-
// For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module
368-
expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState}
369-
if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) {
370-
return errorsmod.Wrapf(types.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v",
371-
expectedUpgradePath, tmClient.UpgradePath)
372-
}
373-
}
374-
return nil
299+
return k.selfClientValidator.ValidateSelfClient(ctx, clientState)
375300
}
376301

377302
// GetUpgradePlan executes the upgrade keeper GetUpgradePlan function.

0 commit comments

Comments
 (0)