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
8 changes: 6 additions & 2 deletions x/ibc/02-client/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var (
ErrRootNotFound = types.ErrRootNotFound
ErrInvalidHeader = types.ErrInvalidHeader
ErrInvalidEvidence = types.ErrInvalidEvidence
DefaultGenesisState = types.DefaultGenesisState
NewGenesisState = types.NewGenesisState

// variable aliases
SubModuleCdc = types.SubModuleCdc
Expand All @@ -46,6 +48,8 @@ var (
)

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),
}
}
48 changes: 48 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,54 @@ 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()), "/")
fmt.Println(keySplit)
if keySplit[len(keySplit)-2] != "consensusState" {
continue
}
clientID := keySplit[len(keySplit)-3]
var consensusState exported.ConsensusState
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &consensusState)

if cb(clientID, consensusState) {
break
}
}
}

// GetAllConsensusStates returns all stored client consensus states
func (k Keeper) GetAllConsensusStates(ctx sdk.Context) (clientConsStates []types.ClientConsensusStates) {
// 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 {
cons[clientID] = []exported.ConsensusState{cs}
return false
}

cons[clientID] = append(consensusStates, cs)
return false
})

for clientID, consensusStates := range cons {
clientConsState := types.NewClientConsensusStates(clientID, consensusStates)
clientConsStates = append(clientConsStates, clientConsState)
}

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
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
}
137 changes: 137 additions & 0 deletions x/ibc/02-client/types/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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 (
testClientID2 = "ethbridge"

testClientHeight = 5
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
fedekunze marked this conversation as resolved.
Show resolved Hide resolved

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(testClientID2, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 10),
},
[]types.ClientConsensusStates{
{
testClientID2,
[]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(testClientID2, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 0),
},
nil,
),
expPass: false,
},
{
name: "invalid consensus state",
genState: types.NewGenesisState(
[]exported.ClientState{
ibctmtypes.NewClientState(testClientID2, 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(testClientID2, trustingPeriod, ubdPeriod, maxClockDrift, header),
localhosttypes.NewClientState(store, "chaindID", 10),
},
[]types.ClientConsensusStates{
types.NewClientConsensusStates(
testClientID2,
[]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)
}
}
}
23 changes: 22 additions & 1 deletion x/ibc/07-tendermint/types/client_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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.
Expand All @@ -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(
Expand Down
Loading