Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ibc/02-client: import export GenesisState #6073

Merged
merged 12 commits into from
Apr 27, 2020
10 changes: 8 additions & 2 deletions x/ibc/02-client/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
)
1 change: 1 addition & 0 deletions x/ibc/02-client/exported/exported.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type ClientState interface {
ClientType() ClientType
GetLatestHeight() uint64
IsFrozen() bool
Validate() error

// State verification functions

Expand Down
27 changes: 27 additions & 0 deletions x/ibc/02-client/genesis.go
Original file line number Diff line number Diff line change
@@ -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),
}
}
61 changes: 61 additions & 0 deletions x/ibc/02-client/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down
39 changes: 38 additions & 1 deletion x/ibc/02-client/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
}
69 changes: 69 additions & 0 deletions x/ibc/02-client/types/genesis.go
Original file line number Diff line number Diff line change
@@ -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
}
135 changes: 135 additions & 0 deletions x/ibc/02-client/types/genesis_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Loading