ibc/02-client: import export GenesisState (#6073)

* ibc/02-client: import export GenesisState

* client validation

* genesis validation

* ibc genesis tests

* GetAllConsensusStates test

* fix non-determinism

* lint

* fix test
This commit is contained in:
Federico Kunze 2020-04-27 14:04:26 -04:00 committed by GitHub
parent f0b72b93be
commit 9b51908597
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 616 additions and 37 deletions

View File

@ -37,6 +37,9 @@ var (
ErrRootNotFound = types.ErrRootNotFound
ErrInvalidHeader = types.ErrInvalidHeader
ErrInvalidEvidence = types.ErrInvalidEvidence
DefaultGenesisState = types.DefaultGenesisState
NewGenesisState = types.NewGenesisState
NewClientConsensusStates = types.NewClientConsensusStates
// variable aliases
SubModuleCdc = types.SubModuleCdc
@ -45,7 +48,10 @@ var (
AttributeValueCategory = types.AttributeValueCategory
)
// nolint
type (
Keeper = keeper.Keeper
StakingKeeper = types.StakingKeeper
Keeper = keeper.Keeper
StakingKeeper = types.StakingKeeper
GenesisState = types.GenesisState
ClientConsensusStates = types.ClientConsensusStates
)

View File

@ -20,6 +20,7 @@ type ClientState interface {
ClientType() ClientType
GetLatestHeight() uint64
IsFrozen() bool
Validate() error
// State verification functions

View File

@ -0,0 +1,27 @@
package client
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// InitGenesis initializes the ibc client submodule's state from a provided genesis
// state.
func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) {
for _, client := range gs.Clients {
k.SetClientState(ctx, client)
k.SetClientType(ctx, client.GetID(), client.ClientType())
}
for _, cs := range gs.ClientsConsensus {
for _, consState := range cs.ConsensusStates {
k.SetClientConsensusState(ctx, cs.ClientID, consState.GetHeight(), consState)
}
}
}
// ExportGenesis returns the ibc client submodule's exported genesis.
func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
return GenesisState{
Clients: k.GetAllClients(ctx),
ClientsConsensus: k.GetAllConsensusStates(ctx),
}
}

View File

@ -98,6 +98,67 @@ func (k Keeper) SetClientConsensusState(ctx sdk.Context, clientID string, height
store.Set(ibctypes.KeyConsensusState(height), bz)
}
// IterateConsensusStates provides an iterator over all stored consensus states.
// objects. For each State object, cb will be called. If the cb returns true,
// the iterator will close and stop.
func (k Keeper) IterateConsensusStates(ctx sdk.Context, cb func(clientID string, cs exported.ConsensusState) bool) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ibctypes.KeyClientStorePrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
keySplit := strings.Split(string(iterator.Key()), "/")
// consensus key is in the format "clients/<clientID>/consensusState/<height>"
if len(keySplit) != 4 || keySplit[2] != "consensusState" {
continue
}
clientID := keySplit[1]
var consensusState exported.ConsensusState
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &consensusState)
if cb(clientID, consensusState) {
break
}
}
}
// GetAllConsensusStates returns all stored client consensus states.
// NOTE: non deterministic.
func (k Keeper) GetAllConsensusStates(ctx sdk.Context) (clientConsStates []types.ClientConsensusStates) {
var clientIDs []string
// create map to add consensus states to the existing clients
cons := make(map[string][]exported.ConsensusState)
k.IterateConsensusStates(ctx, func(clientID string, cs exported.ConsensusState) bool {
consensusStates, ok := cons[clientID]
if !ok {
clientIDs = append(clientIDs, clientID)
cons[clientID] = []exported.ConsensusState{cs}
return false
}
cons[clientID] = append(consensusStates, cs)
return false
})
// create ClientConsensusStates in the same order of iteration to prevent non-determinism
for len(clientIDs) > 0 {
id := clientIDs[len(clientIDs)-1]
consensusStates, ok := cons[id]
if !ok {
panic(fmt.Sprintf("consensus states from client id %s not found", id))
}
clientConsState := types.NewClientConsensusStates(id, consensusStates)
clientConsStates = append(clientConsStates, clientConsState)
// remove the last element
clientIDs = clientIDs[:len(clientIDs)-1]
}
return clientConsStates
}
// HasClientConsensusState returns if keeper has a ConsensusState for a particular
// client at the given height
func (k Keeper) HasClientConsensusState(ctx sdk.Context, clientID string, height uint64) bool {

View File

@ -15,6 +15,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
"github.com/cosmos/cosmos-sdk/x/staking"
@ -161,7 +162,9 @@ func (suite KeeperTestSuite) TestGetConsensusState() {
func (suite KeeperTestSuite) TestConsensusStateHelpers() {
// initial setup
clientState, _ := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, maxClockDrift, suite.header)
clientState, err := ibctmtypes.Initialize(testClientID, trustingPeriod, ubdPeriod, maxClockDrift, suite.header)
suite.Require().NoError(err)
suite.keeper.SetClientState(suite.ctx, clientState)
suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight, suite.consensusState)
@ -192,3 +195,37 @@ func (suite KeeperTestSuite) TestConsensusStateHelpers() {
suite.Require().True(ok)
suite.Require().Equal(suite.consensusState, lte, "LTE helper function did not return latest client state below height: %d", testClientHeight+3)
}
func (suite KeeperTestSuite) TestGetAllConsensusStates() {
expConsensus := []types.ClientConsensusStates{
types.NewClientConsensusStates(
testClientID,
[]exported.ConsensusState{
ibctmtypes.NewConsensusState(
suite.consensusState.Timestamp, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.consensusState.GetHeight(), &tmtypes.ValidatorSet{},
),
ibctmtypes.NewConsensusState(
suite.consensusState.Timestamp.Add(time.Minute), commitmenttypes.NewMerkleRoot([]byte("app_hash")), suite.consensusState.GetHeight()+1, &tmtypes.ValidatorSet{},
),
},
),
types.NewClientConsensusStates(
testClientID2,
[]exported.ConsensusState{
ibctmtypes.NewConsensusState(
suite.consensusState.Timestamp.Add(2*time.Minute), commitmenttypes.NewMerkleRoot([]byte("app_hash_2")), suite.consensusState.GetHeight()+2, &tmtypes.ValidatorSet{},
),
},
),
}
for i := range expConsensus {
for _, cons := range expConsensus[i].ConsensusStates {
suite.keeper.SetClientConsensusState(suite.ctx, expConsensus[i].ClientID, cons.GetHeight(), cons)
}
}
consStates := suite.keeper.GetAllConsensusStates(suite.ctx)
suite.Require().Len(consStates, len(expConsensus))
suite.Require().Equal(expConsensus, consStates)
}

View File

@ -0,0 +1,69 @@
package types
import (
"fmt"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
// ClientConsensusStates defines all the stored consensus states for a given client.
type ClientConsensusStates struct {
ClientID string `json:"client_id" yaml:"client_id"`
ConsensusStates []exported.ConsensusState `json:"consensus_states" yaml:"consensus_states"`
}
// NewClientConsensusStates creates a new ClientConsensusStates instance.
func NewClientConsensusStates(id string, states []exported.ConsensusState) ClientConsensusStates {
return ClientConsensusStates{
ClientID: id,
ConsensusStates: states,
}
}
// GenesisState defines the ibc client submodule's genesis state.
type GenesisState struct {
Clients []exported.ClientState `json:"clients" yaml:"clients"`
ClientsConsensus []ClientConsensusStates `json:"clients_consensus" yaml:"clients_consensus"`
}
// NewGenesisState creates a GenesisState instance.
func NewGenesisState(
clients []exported.ClientState, clientsConsensus []ClientConsensusStates,
) GenesisState {
return GenesisState{
Clients: clients,
ClientsConsensus: clientsConsensus,
}
}
// DefaultGenesisState returns the ibc client submodule's default genesis state.
func DefaultGenesisState() GenesisState {
return GenesisState{
Clients: []exported.ClientState{},
ClientsConsensus: []ClientConsensusStates{},
}
}
// Validate performs basic genesis state validation returning an error upon any
// failure.
func (gs GenesisState) Validate() error {
for i, client := range gs.Clients {
if err := client.Validate(); err != nil {
return fmt.Errorf("invalid client %d: %w", i, err)
}
}
for i, cs := range gs.ClientsConsensus {
if err := host.DefaultClientIdentifierValidator(cs.ClientID); err != nil {
return fmt.Errorf("invalid client consensus state %d: %w", i, err)
}
for _, consensusState := range cs.ConsensusStates {
if err := consensusState.ValidateBasic(); err != nil {
return fmt.Errorf("invalid client consensus state %d: %w", i, err)
}
}
}
return nil
}

View File

@ -0,0 +1,135 @@
package types_test
import (
"testing"
"time"
"github.com/stretchr/testify/require"
tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/store/cachekv"
"github.com/cosmos/cosmos-sdk/store/dbadapter"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
)
const (
clientID = "ethbridge"
trustingPeriod time.Duration = time.Hour * 24 * 7 * 2
ubdPeriod time.Duration = time.Hour * 24 * 7 * 3
maxClockDrift time.Duration = time.Second * 10
)
func TestValidateGenesis(t *testing.T) {
privVal := tmtypes.NewMockPV()
pubKey, err := privVal.GetPubKey()
require.NoError(t, err)
now := time.Now().UTC()
val := tmtypes.NewValidator(pubKey, 10)
valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val})
mem := dbadapter.Store{DB: dbm.NewMemDB()}
store := cachekv.NewStore(mem)
header := ibctmtypes.CreateTestHeader("chainID", 10, now, valSet, []tmtypes.PrivValidator{privVal})
testCases := []struct {
name string
genState types.GenesisState
expPass bool
}{
{
name: "default",
genState: types.DefaultGenesisState(),
expPass: true,
},
{
name: "valid genesis",
genState: types.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(clientID, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 10),
},
[]types.ClientConsensusStates{
{
clientID,
[]exported.ConsensusState{
ibctmtypes.NewConsensusState(
header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), header.GetHeight(), header.ValidatorSet,
),
},
},
},
),
expPass: true,
},
{
name: "invalid client",
genState: types.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(clientID, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 0),
},
nil,
),
expPass: false,
},
{
name: "invalid consensus state",
genState: types.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(clientID, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 10),
},
[]types.ClientConsensusStates{
{
"CLIENTID2",
[]exported.ConsensusState{
ibctmtypes.NewConsensusState(
header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), 0, header.ValidatorSet,
),
},
},
},
),
expPass: false,
},
{
name: "invalid consensus state",
genState: types.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(clientID, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 10),
},
[]types.ClientConsensusStates{
types.NewClientConsensusStates(
clientID,
[]exported.ConsensusState{
ibctmtypes.NewConsensusState(
header.Time, commitmenttypes.NewMerkleRoot(header.AppHash), 0, header.ValidatorSet,
),
},
),
},
),
expPass: false,
},
}
for _, tc := range testCases {
tc := tc
err := tc.genState.Validate()
if tc.expPass {
require.NoError(t, err, tc.name)
} else {
require.Error(t, err, tc.name)
}
}
}

View File

@ -15,6 +15,7 @@ import (
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types"
)
@ -87,7 +88,10 @@ func (cs ClientState) GetID() string {
// GetChainID returns the chain-id from the last header
func (cs ClientState) GetChainID() string {
return cs.LastHeader.ChainID
if cs.LastHeader.SignedHeader.Header == nil {
return ""
}
return cs.LastHeader.SignedHeader.Header.ChainID
}
// ClientType is tendermint.
@ -110,6 +114,23 @@ func (cs ClientState) IsFrozen() bool {
return cs.FrozenHeight != 0
}
// Validate performs a basic validation of the client state fields.
func (cs ClientState) Validate() error {
if err := host.DefaultClientIdentifierValidator(cs.ID); err != nil {
return err
}
if cs.TrustingPeriod == 0 {
return errors.New("trusting period cannot be zero")
}
if cs.UnbondingPeriod == 0 {
return errors.New("unbonding period cannot be zero")
}
if cs.MaxClockDrift == 0 {
return errors.New("max clock drift cannot be zero")
}
return cs.LastHeader.ValidateBasic(cs.GetChainID())
}
// VerifyClientConsensusState verifies a proof of the consensus state of the
// Tendermint client stored on the target machine.
func (cs ClientState) VerifyClientConsensusState(

View File

@ -5,17 +5,67 @@ import (
connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported"
channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel"
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
)
const (
testClientID = "clientidone"
testConnectionID = "connectionid"
testPortID = "testportid"
testChannelID = "testchannelid"
testSequence = 1
)
func (suite *TendermintTestSuite) TestValidate() {
testCases := []struct {
name string
clientState types.ClientState
expPass bool
}{
{
name: "valid client",
clientState: ibctmtypes.NewClientState(testClientID, trustingPeriod, ubdPeriod, maxClockDrift, suite.header),
expPass: true,
},
{
name: "invalid client id",
clientState: ibctmtypes.NewClientState("testClientID", trustingPeriod, ubdPeriod, maxClockDrift, suite.header),
expPass: false,
},
{
name: "invalid trusting period",
clientState: ibctmtypes.NewClientState(testClientID, 0, ubdPeriod, maxClockDrift, suite.header),
expPass: false,
},
{
name: "invalid unbonding period",
clientState: ibctmtypes.NewClientState(testClientID, trustingPeriod, 0, maxClockDrift, suite.header),
expPass: false,
},
{
name: "invalid max clock drift",
clientState: ibctmtypes.NewClientState(testClientID, trustingPeriod, ubdPeriod, 0, suite.header),
expPass: false,
},
{
name: "invalid header",
clientState: ibctmtypes.NewClientState(testClientID, trustingPeriod, ubdPeriod, maxClockDrift, ibctmtypes.Header{}),
expPass: false,
},
}
for _, tc := range testCases {
err := tc.clientState.Validate()
if tc.expPass {
suite.Require().NoError(err, tc.name)
} else {
suite.Require().Error(err, tc.name)
}
}
}
func (suite *TendermintTestSuite) TestVerifyClientConsensusState() {
testCases := []struct {
name string

View File

@ -19,6 +19,19 @@ type ConsensusState struct {
ValidatorSet *tmtypes.ValidatorSet `json:"validator_set" yaml:"validator_set"`
}
// NewConsensusState creates a new ConsensusState instance.
func NewConsensusState(
timestamp time.Time, root commitmentexported.Root, height uint64,
valset *tmtypes.ValidatorSet,
) ConsensusState {
return ConsensusState{
Timestamp: timestamp,
Root: root,
Height: height,
ValidatorSet: valset,
}
}
// ClientType returns Tendermint
func (ConsensusState) ClientType() clientexported.ClientType {
return clientexported.Tendermint

View File

@ -73,6 +73,9 @@ func (msg MsgCreateClient) ValidateBasic() error {
if msg.Signer.Empty() {
return sdkerrors.ErrInvalidAddress
}
if msg.Header.SignedHeader.Header == nil {
return sdkerrors.Wrap(ErrInvalidHeader, "header cannot be nil")
}
// ValidateBasic of provided header with self-attested chain-id
if err := msg.Header.ValidateBasic(msg.Header.ChainID); err != nil {
return sdkerrors.Wrapf(ErrInvalidHeader, "header failed validatebasic with its own chain-id: %v", err)

View File

@ -19,18 +19,16 @@ func (suite *TendermintTestSuite) TestMsgCreateClientValidateBasic() {
}{
{ibctmtypes.NewMsgCreateClient(exported.ClientTypeTendermint, suite.header, trustingPeriod, ubdPeriod, maxClockDrift, signer), true, "success msg should pass"},
{ibctmtypes.NewMsgCreateClient("BADCHAIN", suite.header, trustingPeriod, ubdPeriod, maxClockDrift, signer), false, "invalid client id passed"},
{ibctmtypes.NewMsgCreateClient("goodchain", suite.header, trustingPeriod, ubdPeriod, maxClockDrift, signer), false, "unregistered client type passed"},
{ibctmtypes.NewMsgCreateClient("goodchain", suite.header, trustingPeriod, ubdPeriod, maxClockDrift, signer), false, "invalid Consensus State in msg passed"},
{ibctmtypes.NewMsgCreateClient("goodchain", suite.header, 0, ubdPeriod, maxClockDrift, signer), false, "zero trusting period passed"},
{ibctmtypes.NewMsgCreateClient("goodchain", suite.header, trustingPeriod, 0, maxClockDrift, signer), false, "zero unbonding period passed"},
{ibctmtypes.NewMsgCreateClient("goodchain", suite.header, trustingPeriod, ubdPeriod, maxClockDrift, nil), false, "Empty address passed"},
{ibctmtypes.NewMsgCreateClient("goodchain", suite.header, trustingPeriod, ubdPeriod, maxClockDrift, nil), false, "Empty chain ID"},
{ibctmtypes.NewMsgCreateClient(exported.ClientTypeTendermint, suite.header, 0, ubdPeriod, maxClockDrift, signer), false, "zero trusting period passed"},
{ibctmtypes.NewMsgCreateClient(exported.ClientTypeTendermint, suite.header, trustingPeriod, 0, maxClockDrift, signer), false, "zero unbonding period passed"},
{ibctmtypes.NewMsgCreateClient(exported.ClientTypeTendermint, suite.header, trustingPeriod, ubdPeriod, maxClockDrift, nil), false, "Empty address passed"},
{ibctmtypes.NewMsgCreateClient(exported.ClientTypeTendermint, ibctmtypes.Header{}, trustingPeriod, ubdPeriod, maxClockDrift, signer), false, "nil header"},
}
for i, tc := range cases {
err := tc.msg.ValidateBasic()
if tc.expPass {
suite.Require().NoError(err, "Msg %d failed: %v", i, err)
suite.Require().NoError(err, "Msg %d failed: %v", i, tc.errMsg)
} else {
suite.Require().Error(err, "Invalid Msg %d passed: %s", i, tc.errMsg)
}

View File

@ -3,7 +3,9 @@ package types
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -15,6 +17,7 @@ import (
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types"
)
@ -63,6 +66,23 @@ func (cs ClientState) IsFrozen() bool {
return false
}
// Validate performs a basic validation of the client state fields.
func (cs ClientState) Validate() error {
if err := host.DefaultClientIdentifierValidator(cs.ID); err != nil {
return err
}
if strings.TrimSpace(cs.ChainID) == "" {
return errors.New("chain id cannot be blank")
}
if cs.Height <= 0 {
return fmt.Errorf("height must be positive: %d", cs.Height)
}
if cs.store == nil {
return errors.New("KVStore cannot be nil")
}
return nil
}
// VerifyClientConsensusState verifies a proof of the consensus
// state of the loop-back client.
// VerifyClientConsensusState verifies a proof of the consensus state of the

View File

@ -16,6 +16,44 @@ const (
testSequence = 1
)
func (suite *LocalhostTestSuite) TestValidate() {
testCases := []struct {
name string
clientState types.ClientState
expPass bool
}{
{
name: "valid client",
clientState: types.NewClientState(suite.store, "chainID", 10),
expPass: true,
},
{
name: "invalid chain id",
clientState: types.NewClientState(suite.store, " ", 10),
expPass: false,
},
{
name: "invalid height",
clientState: types.NewClientState(suite.store, "chainID", 0),
expPass: false,
},
{
name: "invalid store",
clientState: types.NewClientState(nil, "chainID", 10),
expPass: false,
},
}
for _, tc := range testCases {
err := tc.clientState.Validate()
if tc.expPass {
suite.Require().NoError(err, tc.name)
} else {
suite.Require().Error(err, tc.name)
}
}
}
func (suite *LocalhostTestSuite) TestVerifyClientConsensusState() {
testCases := []struct {
name string

View File

@ -21,7 +21,7 @@ func defaultIdentifierValidator(id string, min, max int) error {
if strings.Contains(id, "/") {
return sdkerrors.Wrapf(ErrInvalidID, "identifier %s cannot contain separator '/'", id)
}
// valid id must be between 10 and 20 characters
// valid id must be between 9 and 20 characters
if len(id) < min || len(id) > max {
return sdkerrors.Wrapf(ErrInvalidID, "identifier %s has invalid length: %d, must be between %d-%d characters", id, len(id), min, max)
}
@ -33,10 +33,10 @@ func defaultIdentifierValidator(id string, min, max int) error {
}
// DefaultClientIdentifierValidator is the default validator function for Client identifiers
// A valid Identifier must be between 10-20 characters and only contain lowercase
// A valid Identifier must be between 9-20 characters and only contain lowercase
// alphabetic characters,
func DefaultClientIdentifierValidator(id string) error {
return defaultIdentifierValidator(id, 10, 20)
return defaultIdentifierValidator(id, 9, 20)
}
// DefaultConnectionIdentifierValidator is the default validator function for Connection identifiers

View File

@ -2,17 +2,20 @@ package ibc
import (
sdk "github.com/cosmos/cosmos-sdk/types"
client "github.com/cosmos/cosmos-sdk/x/ibc/02-client"
connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection"
)
// GenesisState defines the ibc module's genesis state.
type GenesisState struct {
ClientGenesis client.GenesisState `json:"client_genesis" yaml:"client_genesis"`
ConnectionGenesis connection.GenesisState `json:"connection_genesis" yaml:"connection_genesis"`
}
// DefaultGenesisState returns the ibc module's default genesis state.
func DefaultGenesisState() GenesisState {
return GenesisState{
ClientGenesis: client.DefaultGenesisState(),
ConnectionGenesis: connection.DefaultGenesisState(),
}
}
@ -20,18 +23,24 @@ func DefaultGenesisState() GenesisState {
// Validate performs basic genesis state validation returning an error upon any
// failure.
func (gs GenesisState) Validate() error {
if err := gs.ClientGenesis.Validate(); err != nil {
return err
}
return gs.ConnectionGenesis.Validate()
}
// InitGenesis initializes the ibc connection submodule's state from a provided genesis
// InitGenesis initializes the ibc state from a provided genesis
// state.
func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) {
client.InitGenesis(ctx, k.ClientKeeper, gs.ClientGenesis)
connection.InitGenesis(ctx, k.ConnectionKeeper, gs.ConnectionGenesis)
}
// ExportGenesis returns the ibc connection submodule's exported genesis.
// ExportGenesis returns the ibc exported genesis.
func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
return GenesisState{
ClientGenesis: client.ExportGenesis(ctx, k.ClientKeeper),
ConnectionGenesis: connection.ExportGenesis(ctx, k.ConnectionKeeper),
}
}

View File

@ -1,38 +1,47 @@
package ibc
package ibc_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/x/ibc"
client "github.com/cosmos/cosmos-sdk/x/ibc/02-client"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection"
connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types"
commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types"
ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types"
)
var (
connectionID = "connectionidone"
clientID = "clientidone"
connectionID2 = "connectionidtwo"
clientID2 = "clientidtwo"
)
func TestValidateGenesis(t *testing.T) {
func (suite *IBCTestSuite) TestValidateGenesis() {
testCases := []struct {
name string
genState GenesisState
genState ibc.GenesisState
expPass bool
}{
{
name: "default",
genState: DefaultGenesisState(),
genState: ibc.DefaultGenesisState(),
expPass: true,
},
{
name: "valid genesis",
genState: GenesisState{
genState: ibc.GenesisState{
ClientGenesis: client.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(clientID, trustingPeriod, ubdPeriod, maxClockDrift, suite.header),
localhosttypes.NewClientState(suite.store, "chaindID", 10),
},
[]client.ClientConsensusStates{
client.NewClientConsensusStates(
clientID,
[]exported.ConsensusState{
ibctmtypes.NewConsensusState(
suite.header.Time, commitmenttypes.NewMerkleRoot(suite.header.AppHash), suite.header.GetHeight(), suite.header.ValidatorSet,
),
},
),
},
),
ConnectionGenesis: connection.NewGenesisState(
[]connection.ConnectionEnd{
connection.NewConnectionEnd(connectionexported.INIT, connectionID, clientID, connection.NewCounterparty(clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []string{"1.0.0"}),
@ -44,14 +53,31 @@ func TestValidateGenesis(t *testing.T) {
},
expPass: true,
},
{
name: "invalid client genesis",
genState: ibc.GenesisState{
ClientGenesis: client.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(clientID, trustingPeriod, ubdPeriod, maxClockDrift, suite.header),
localhosttypes.NewClientState(suite.store, "chaindID", 0),
},
nil,
),
ConnectionGenesis: connection.DefaultGenesisState(),
},
expPass: false,
},
{
name: "invalid connection genesis",
genState: GenesisState{
genState: ibc.GenesisState{
ClientGenesis: client.DefaultGenesisState(),
ConnectionGenesis: connection.NewGenesisState(
[]connection.ConnectionEnd{
connection.NewConnectionEnd(connectionexported.INIT, connectionID, "CLIENTIDONE", connection.NewCounterparty(clientID, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []string{"1.0.0"}),
},
nil,
[]connection.ConnectionPaths{
connection.NewConnectionPaths(clientID, []string{ibctypes.ConnectionPath(connectionID)}),
},
),
},
expPass: false,
@ -62,9 +88,9 @@ func TestValidateGenesis(t *testing.T) {
tc := tc
err := tc.genState.Validate()
if tc.expPass {
require.NoError(t, err, tc.name)
suite.Require().NoError(err, tc.name)
} else {
require.Error(t, err, tc.name)
suite.Require().Error(err, tc.name)
}
}
}

65
x/ibc/ibc_test.go Normal file
View File

@ -0,0 +1,65 @@
package ibc_test
import (
"testing"
"time"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/store/cachekv"
"github.com/cosmos/cosmos-sdk/store/dbadapter"
sdk "github.com/cosmos/cosmos-sdk/types"
ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
)
const (
connectionID = "connectionidone"
clientID = "clientidone"
connectionID2 = "connectionidtwo"
clientID2 = "clientidtwo"
trustingPeriod time.Duration = time.Hour * 24 * 7 * 2
ubdPeriod time.Duration = time.Hour * 24 * 7 * 3
maxClockDrift time.Duration = time.Second * 10
)
type IBCTestSuite struct {
suite.Suite
cdc *codec.Codec
ctx sdk.Context
app *simapp.SimApp
store sdk.KVStore
header ibctmtypes.Header
}
func (suite *IBCTestSuite) SetupTest() {
isCheckTx := false
suite.app = simapp.Setup(isCheckTx)
privVal := tmtypes.NewMockPV()
pubKey, err := privVal.GetPubKey()
suite.Require().NoError(err)
now := time.Now().UTC()
val := tmtypes.NewValidator(pubKey, 10)
valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val})
mem := dbadapter.Store{DB: dbm.NewMemDB()}
suite.store = cachekv.NewStore(mem)
suite.header = ibctmtypes.CreateTestHeader("chainID", 10, now, valSet, []tmtypes.PrivValidator{privVal})
suite.cdc = suite.app.Codec()
suite.ctx = suite.app.BaseApp.NewContext(isCheckTx, abci.Header{})
}
func TestIBCTestSuite(t *testing.T) {
suite.Run(t, new(IBCTestSuite))
}