From 35e0428c6a7700dc6f131cd08e08ef6090f186d7 Mon Sep 17 00:00:00 2001 From: Joon Date: Tue, 5 Nov 2019 08:56:38 -0800 Subject: [PATCH] ICS 02 Implementation (#4516) * add mapping * rm unused mapping/*, rm interfaces * rm unused code * mv mapping -> state, rm x/ibc * rm GetIfExists * add key * rm custom encoding/decoding in enum/bool * fix lint * rm tests * add commitment * newtyped remote values * newtyped context * revert context newtype * add README, keypath * reflect downstream ics * add merkle * add test for proving * soft coded root keypath * add update * remove RootUpdate * separate keypath and keuprefix * add codec * separate root/path * add path to codec * add client * add counterpartymanager * fix manager * add Is() to counterobject * add readme, reflect ICS02 revision * reflect downstream ics * test in progress * add test * in progres * fin rebase * in progress * fin rebase * add CLIObject in progress * cli in progress * add CLIObject * separate testing from tendermint * add key to node * add root and storekey to tests/node, add codec * rm cli/query.go * fix test * fix lint * fix lint * add handler/msgs/client * rm relay * finalize rebase on 23 root/path sep * fix lint, fix syntax * rm freebase, reformat query * add docs in progre * add comments * add comments * Apply suggestions from code review Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * add comments in progress * add comments * fix comment * add comments in progress * recover IntEncoding scheme for integer * add uint tests, don't use codec in custom types * finalize merge * add godoc * add godoc in progress * reformat test * rm XXX * add godoc * modify store * add query * update query.go * update query.go * cli refactor in progress * cli refactor in progress * add Query to boolean.go * fix key * fix cli / merkle test * godoc cleanup * godoc cleanup * godoc cleanup * godoc cleanup * godoc cleanup * merge from ics04 branch * merge from ics04 branch * merge from ics04 branch * fix lint * Apply suggestions from code review Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * applying review in progress * apply review - make querier interface * fix dependency * fix dependency * revise querier interface to work both on cli & store * revise querier interface to work both on cli & store * reflect downstream change * fix cli * rm commented lines * address review in progress * rename Path -> Prefix * Store accessor upstream changes (#5119) * Store accessor upstream changes (#5119) * add comments, reformat merkle querier * rm merkle/utils * ICS 23 upstream changes (#5120) * ICS 23 upstream changes (#5120) * update Value * update test * fix * ICS 02 upstream changes (#5122) * ICS 02 upstream changes (#5122) * cleanup types and submodule * more cleanup and godocs * remove counterPartyManager/State and cleanup * implement SubmitMisbehaviour and refactor * errors * events * fix test * refactors * remove Mapping * remove store accessors * proposed refactor * remove store accessors from ICS02 * refactor queriers, handler and clean keeper * logger and tx long description * ineffassign * Apply suggestions from code review Co-Authored-By: Jack Zampolin * Apply suggestions from code review Co-Authored-By: Jack Zampolin * add verification functions * ICS02 module.go * top level x/ibc structure * Update x/ibc/02-client/client/cli/query.go Co-Authored-By: Jack Zampolin * Update x/ibc/02-client/types/tendermint/consensus_state.go Co-Authored-By: Jack Zampolin * address some of the review comments * minor UX improvements * rename pkg * fixes * refactor ICS23 * cleanup types * implement batch verification * gosimple suggestion * various fixes; remove legacy tests; remove commitment path query * alias * minor updates from ICS23 * renaming * update verification and rename root funcs * move querier to x/ibc * update query.go to use 'custom/...' query path * add tests * ICS 24 Implementation (#5229) * add validation functions * validate path in ics-23 * address @fede comments * move errors into host package * flatten ICS23 structure * fix ApplyPrefix * updates from ICS23 and ICS24 * msg.ValidateBasic and ADR09 evidence interface * complete types testing * delete empty test file * remove ibc errors from core error package * custom JSON marshaling * start batch-verify tests * minor changes on commitment types * R4R - Store consensus state correctly (#5242) * store consensus state correctly * fix client example * update alias * use testsuite * Integrate Evidence Implementation into ICS-02 (#5258) * implement evidence in ics-02 * fix build errors and import cycles * address fede comments * remove unnecessary pubkey and fix init * add tests * finish tendermint tests * complete merge * Add tests for msgs * upstream changes * fix * upstream changes * fix cons state * context changes --- x/ibc/02-client/alias.go | 85 ++++++ x/ibc/02-client/client/cli/query.go | 258 ++++++++++++++++++ x/ibc/02-client/client/cli/tx.go | 172 ++++++++++++ x/ibc/02-client/client/utils/utils.go | 75 +++++ x/ibc/02-client/doc.go | 10 + x/ibc/02-client/exported/exported.go | 123 +++++++++ x/ibc/02-client/handler.go | 78 ++++++ x/ibc/02-client/keeper/client.go | 104 +++++++ x/ibc/02-client/keeper/keeper.go | 205 ++++++++++++++ x/ibc/02-client/keeper/querier.go | 77 ++++++ x/ibc/02-client/module.go | 32 +++ x/ibc/02-client/types/codec.go | 30 ++ x/ibc/02-client/types/errors/errors.go | 73 +++++ x/ibc/02-client/types/events.go | 21 ++ x/ibc/02-client/types/keys.go | 67 +++++ x/ibc/02-client/types/msgs.go | 173 ++++++++++++ x/ibc/02-client/types/msgs_test.go | 140 ++++++++++ x/ibc/02-client/types/querier.go | 66 +++++ x/ibc/02-client/types/state.go | 24 ++ x/ibc/02-client/types/tendermint/codec.go | 18 ++ .../types/tendermint/consensus_state.go | 89 ++++++ .../types/tendermint/consensus_state_test.go | 52 ++++ x/ibc/02-client/types/tendermint/doc.go | 5 + x/ibc/02-client/types/tendermint/header.go | 28 ++ .../types/tendermint/misbehaviour.go | 96 +++++++ .../types/tendermint/misbehaviour_test.go | 64 +++++ .../types/tendermint/tendermint_test.go | 87 ++++++ .../02-client/types/tendermint/test_utils.go | 47 ++++ x/ibc/24-host/errors.go | 5 +- x/ibc/alias.go | 29 ++ x/ibc/client/cli/cli.go | 43 +++ x/ibc/handler.go | 30 ++ x/ibc/keeper/keeper.go | 19 ++ x/ibc/keeper/querier.go | 24 ++ x/ibc/module.go | 131 +++++++++ x/ibc/types/types.go | 15 + x/ibc/version/version.go | 13 + 37 files changed, 2607 insertions(+), 1 deletion(-) create mode 100644 x/ibc/02-client/alias.go create mode 100644 x/ibc/02-client/client/cli/query.go create mode 100644 x/ibc/02-client/client/cli/tx.go create mode 100644 x/ibc/02-client/client/utils/utils.go create mode 100644 x/ibc/02-client/doc.go create mode 100644 x/ibc/02-client/exported/exported.go create mode 100644 x/ibc/02-client/handler.go create mode 100644 x/ibc/02-client/keeper/client.go create mode 100644 x/ibc/02-client/keeper/keeper.go create mode 100644 x/ibc/02-client/keeper/querier.go create mode 100644 x/ibc/02-client/module.go create mode 100644 x/ibc/02-client/types/codec.go create mode 100644 x/ibc/02-client/types/errors/errors.go create mode 100644 x/ibc/02-client/types/events.go create mode 100644 x/ibc/02-client/types/keys.go create mode 100644 x/ibc/02-client/types/msgs.go create mode 100644 x/ibc/02-client/types/msgs_test.go create mode 100644 x/ibc/02-client/types/querier.go create mode 100644 x/ibc/02-client/types/state.go create mode 100644 x/ibc/02-client/types/tendermint/codec.go create mode 100644 x/ibc/02-client/types/tendermint/consensus_state.go create mode 100644 x/ibc/02-client/types/tendermint/consensus_state_test.go create mode 100644 x/ibc/02-client/types/tendermint/doc.go create mode 100644 x/ibc/02-client/types/tendermint/header.go create mode 100644 x/ibc/02-client/types/tendermint/misbehaviour.go create mode 100644 x/ibc/02-client/types/tendermint/misbehaviour_test.go create mode 100644 x/ibc/02-client/types/tendermint/tendermint_test.go create mode 100644 x/ibc/02-client/types/tendermint/test_utils.go create mode 100644 x/ibc/alias.go create mode 100644 x/ibc/client/cli/cli.go create mode 100644 x/ibc/handler.go create mode 100644 x/ibc/keeper/keeper.go create mode 100644 x/ibc/keeper/querier.go create mode 100644 x/ibc/types/types.go create mode 100644 x/ibc/version/version.go diff --git a/x/ibc/02-client/alias.go b/x/ibc/02-client/alias.go new file mode 100644 index 000000000000..00519d9da512 --- /dev/null +++ b/x/ibc/02-client/alias.go @@ -0,0 +1,85 @@ +package client + +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/02-client/types + +import ( + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/errors" +) + +const ( + DefaultCodespace = errors.DefaultCodespace + CodeClientExists = errors.CodeClientExists + CodeClientNotFound = errors.CodeClientNotFound + CodeClientFrozen = errors.CodeClientFrozen + CodeConsensusStateNotFound = errors.CodeConsensusStateNotFound + CodeInvalidConsensusState = errors.CodeInvalidConsensusState + CodeClientTypeNotFound = errors.CodeClientTypeNotFound + CodeInvalidClientType = errors.CodeInvalidClientType + CodeRootNotFound = errors.CodeRootNotFound + CodeInvalidHeader = errors.CodeInvalidHeader + CodeInvalidEvidence = errors.CodeInvalidEvidence + AttributeKeyClientID = types.AttributeKeyClientID + SubModuleName = types.SubModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + QueryClientState = types.QueryClientState + QueryConsensusState = types.QueryConsensusState + QueryVerifiedRoot = types.QueryVerifiedRoot +) + +var ( + // functions aliases + NewKeeper = keeper.NewKeeper + QuerierClientState = keeper.QuerierClientState + QuerierConsensusState = keeper.QuerierConsensusState + QuerierVerifiedRoot = keeper.QuerierVerifiedRoot + RegisterCodec = types.RegisterCodec + ErrClientExists = errors.ErrClientExists + ErrClientNotFound = errors.ErrClientNotFound + ErrClientFrozen = errors.ErrClientFrozen + ErrConsensusStateNotFound = errors.ErrConsensusStateNotFound + ErrInvalidConsensus = errors.ErrInvalidConsensus + ErrClientTypeNotFound = errors.ErrClientTypeNotFound + ErrInvalidClientType = errors.ErrInvalidClientType + ErrRootNotFound = errors.ErrRootNotFound + ErrInvalidHeader = errors.ErrInvalidHeader + ErrInvalidEvidence = errors.ErrInvalidEvidence + ClientStatePath = types.ClientStatePath + ClientTypePath = types.ClientTypePath + ConsensusStatePath = types.ConsensusStatePath + RootPath = types.RootPath + KeyClientState = types.KeyClientState + KeyClientType = types.KeyClientType + KeyConsensusState = types.KeyConsensusState + KeyRoot = types.KeyRoot + NewMsgCreateClient = types.NewMsgCreateClient + NewMsgUpdateClient = types.NewMsgUpdateClient + NewMsgSubmitMisbehaviour = types.NewMsgSubmitMisbehaviour + NewQueryClientStateParams = types.NewQueryClientStateParams + NewQueryCommitmentRootParams = types.NewQueryCommitmentRootParams + NewClientState = types.NewClientState + + // variable aliases + SubModuleCdc = types.SubModuleCdc + EventTypeCreateClient = types.EventTypeCreateClient + EventTypeUpdateClient = types.EventTypeUpdateClient + EventTypeSubmitMisbehaviour = types.EventTypeSubmitMisbehaviour + AttributeValueCategory = types.AttributeValueCategory +) + +type ( + Keeper = keeper.Keeper + MsgCreateClient = types.MsgCreateClient + MsgUpdateClient = types.MsgUpdateClient + MsgSubmitMisbehaviour = types.MsgSubmitMisbehaviour + QueryClientStateParams = types.QueryClientStateParams + QueryCommitmentRootParams = types.QueryCommitmentRootParams + State = types.State +) diff --git a/x/ibc/02-client/client/cli/query.go b/x/ibc/02-client/client/cli/query.go new file mode 100644 index 000000000000..6f6a487bbbf6 --- /dev/null +++ b/x/ibc/02-client/client/cli/query.go @@ -0,0 +1,258 @@ +package cli + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/client/utils" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// GetQueryCmd returns the query commands for IBC clients +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + ics02ClientQueryCmd := &cobra.Command{ + Use: "client", + Short: "IBC client query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + ics02ClientQueryCmd.AddCommand(client.GetCommands( + GetCmdQueryConsensusState(queryRoute, cdc), + GetCmdQueryHeader(cdc), + GetCmdQueryClientState(queryRoute, cdc), + GetCmdQueryRoot(queryRoute, cdc), + GetCmdNodeConsensusState(queryRoute, cdc), + GetCmdQueryPath(queryRoute, cdc), + )...) + return ics02ClientQueryCmd +} + +// GetCmdQueryClientState defines the command to query the state of a client with +// a given id as defined in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#query +func GetCmdQueryClientState(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "state [client-id]", + Short: "Query a client state", + Long: strings.TrimSpace( + fmt.Sprintf(`Query stored client state + +Example: +$ %s query ibc client state [client-id] + `, version.ClientName), + ), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + clientID := args[0] + if strings.TrimSpace(clientID) == "" { + return errors.New("client ID can't be blank") + } + + bz, err := cdc.MarshalJSON(types.NewQueryClientStateParams(clientID)) + if err != nil { + return err + } + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryClientState), bz) + if err != nil { + return err + } + + var clientState types.State + if err := cdc.UnmarshalJSON(res, &clientState); err != nil { + return err + } + + return cliCtx.PrintOutput(clientState) + }, + } +} + +// GetCmdQueryRoot defines the command to query +func GetCmdQueryRoot(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "root [client-id] [height]", + Short: "Query a verified commitment root", + Long: strings.TrimSpace( + fmt.Sprintf(`Query an already verified commitment root at a specific height for a particular client + +Example: +$ %s query ibc client root [client-id] [height] +`, version.ClientName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + clientID := args[0] + if strings.TrimSpace(clientID) == "" { + return errors.New("client ID can't be blank") + } + + height, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return fmt.Errorf("expected integer height, got: %v", args[1]) + } + + bz, err := cdc.MarshalJSON(types.NewQueryCommitmentRootParams(clientID, height)) + if err != nil { + return err + } + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryVerifiedRoot), bz) + if err != nil { + return err + } + + var root commitment.RootI + if err := cdc.UnmarshalJSON(res, &root); err != nil { + return err + } + + return cliCtx.PrintOutput(root) + }, + } +} + +// GetCmdQueryConsensusState defines the command to query the consensus state of +// the chain as defined in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#query +func GetCmdQueryConsensusState(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "consensus-state [client-id]", + Short: "Query the latest consensus state of the client", + Long: strings.TrimSpace( + fmt.Sprintf(`Query the consensus state for a particular client + +Example: +$ %s query ibc client consensus-state [client-id] + `, version.ClientName), + ), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + clientID := args[0] + if strings.TrimSpace(clientID) == "" { + return errors.New("client ID can't be blank") + } + + bz, err := cdc.MarshalJSON(types.NewQueryClientStateParams(clientID)) + if err != nil { + return err + } + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryConsensusState), bz) + if err != nil { + return err + } + + var consensusState exported.ConsensusState + if err := cdc.UnmarshalJSON(res, &consensusState); err != nil { + return err + } + + return cliCtx.PrintOutput(consensusState) + }, + } +} + +// GetCmdQueryHeader defines the command to query the latest header on the chain +func GetCmdQueryHeader(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "header", + Short: "Query the latest header of the running chain", + Long: strings.TrimSpace(fmt.Sprintf(`Query the latest Tendermint header + +Example: +$ %s query ibc client header + `, version.ClientName), + ), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + header, err := utils.GetTendermintHeader(cliCtx) + if err != nil { + return err + } + + return cliCtx.PrintOutput(header) + }, + } +} + +// GetCmdNodeConsensusState defines the command to query the latest consensus state of a node +// The result is feed to client creation +func GetCmdNodeConsensusState(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "node-state", + Short: "Query a node consensus state", + Long: strings.TrimSpace( + fmt.Sprintf(`Query a node consensus state. This result is feed to the client creation transaction. + +Example: +$ %s query ibc client node-state + `, version.ClientName), + ), + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + node, err := cliCtx.GetNode() + if err != nil { + return err + } + + info, err := node.ABCIInfo() + if err != nil { + return err + } + + height := info.Response.LastBlockHeight + prevHeight := height - 1 + + commit, err := node.Commit(&height) + if err != nil { + return err + } + + validators, err := node.Validators(&prevHeight) + if err != nil { + return err + } + + state := tendermint.ConsensusState{ + ChainID: commit.ChainID, + Height: uint64(commit.Height), + Root: commitment.NewRoot(commit.AppHash), + NextValidatorSet: tmtypes.NewValidatorSet(validators.Validators), + } + + return cliCtx.PrintOutput(state) + }, + } +} + +// GetCmdQueryPath defines the command to query the commitment path. +func GetCmdQueryPath(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "path", + Short: "Query the commitment path of the running chain", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.NewCLIContext().WithCodec(cdc) + path := commitment.NewPrefix([]byte("ibc")) + return ctx.PrintOutput(path) + }, + } +} diff --git a/x/ibc/02-client/client/cli/tx.go b/x/ibc/02-client/client/cli/tx.go new file mode 100644 index 000000000000..4cbc97b39842 --- /dev/null +++ b/x/ibc/02-client/client/cli/tx.go @@ -0,0 +1,172 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" +) + +// GetTxCmd returns the transaction commands for IBC Clients +func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + ics02ClientTxCmd := &cobra.Command{ + Use: "client", + Short: "Client transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + ics02ClientTxCmd.AddCommand(client.PostCommands( + GetCmdCreateClient(cdc), + GetCmdUpdateClient(cdc), + )...) + + return ics02ClientTxCmd +} + +// GetCmdCreateClient defines the command to create a new IBC Client as defined +// in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#create +func GetCmdCreateClient(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "create [client-id] [path/to/consensus_state.json]", + Short: "create new client with a consensus state", + Long: strings.TrimSpace(fmt.Sprintf(`create new client with a specified identifier and consensus state: + +Example: +$ %s tx ibc client create [client-id] [path/to/consensus_state.json] --from node0 --home ../node0/cli --chain-id $CID + `, version.ClientName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc).WithBroadcastMode(flags.BroadcastBlock) + + clientID := args[0] + + var state exported.ConsensusState + if err := cdc.UnmarshalJSON([]byte(args[1]), &state); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + + contents, err := ioutil.ReadFile(args[1]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &state); err != nil { + return fmt.Errorf("error unmarshalling consensus state file: %v", err) + } + } + + msg := types.NewMsgCreateClient( + clientID, state.ClientType().String(), state, + cliCtx.GetFromAddress(), + ) + + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + return cmd +} + +// GetCmdUpdateClient defines the command to update a client as defined in +// https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#update +func GetCmdUpdateClient(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "update [client-id] [path/to/header.json]", + Short: "update existing client with a header", + Long: strings.TrimSpace(fmt.Sprintf(`update existing client with a header: + +Example: +$ %s tx ibc client update [client-id] [path/to/header.json] --from node0 --home ../node0/cli --chain-id $CID + `, version.ClientName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + clientID := args[0] + + var header exported.Header + if err := cdc.UnmarshalJSON([]byte(args[1]), &header); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + + contents, err := ioutil.ReadFile(args[1]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &header); err != nil { + return fmt.Errorf("error unmarshalling header file: %v", err) + } + } + + msg := types.NewMsgUpdateClient(clientID, header, cliCtx.GetFromAddress()) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + return cmd +} + +// GetCmdSubmitMisbehaviour defines the command to submit a misbehaviour to invalidate +// previous state roots and prevent future updates as defined in +// https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#misbehaviour +func GetCmdSubmitMisbehaviour(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "misbehaviour [client-it] [path/to/evidence.json]", + Short: "submit a client misbehaviour", + Long: strings.TrimSpace(fmt.Sprintf(`submit a client misbehaviour to invalidate to invalidate previous state roots and prevent future updates: + +Example: +$ %s tx ibc client misbehaviour [client-id] [path/to/evidence.json] --from node0 --home ../node0/cli --chain-id $CID + `, version.ClientName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + clientID := args[0] + + var evidence exported.Evidence + if err := cdc.UnmarshalJSON([]byte(args[1]), &evidence); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + + contents, err := ioutil.ReadFile(args[1]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &evidence); err != nil { + return fmt.Errorf("error unmarshalling evidence file: %v", err) + } + } + + msg := types.NewMsgSubmitMisbehaviour(clientID, evidence, cliCtx.GetFromAddress()) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + return cmd +} diff --git a/x/ibc/02-client/client/utils/utils.go b/x/ibc/02-client/client/utils/utils.go new file mode 100644 index 000000000000..5d0786267373 --- /dev/null +++ b/x/ibc/02-client/client/utils/utils.go @@ -0,0 +1,75 @@ +package utils + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" +) + +// QueryConsensusStateProof queries the store to get the consensus state and a +// merkle proof. +func QueryConsensusStateProof(cliCtx client.CLIContext, clientID string) (types.ConsensusStateResponse, error) { + var conStateRes types.ConsensusStateResponse + req := abci.RequestQuery{ + Path: "store/ibc/key", + Data: []byte(fmt.Sprintf("clients/%s/consensusState", clientID)), + Prove: true, + } + + res, err := cliCtx.QueryABCI(req) + if err != nil { + return conStateRes, err + } + + var cs tendermint.ConsensusState + if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &cs); err != nil { + return conStateRes, err + } + return types.NewConsensusStateResponse(clientID, cs, res.Proof, res.Height), nil +} + +// GetTendermintHeader takes a client context and returns the appropriate +// tendermint header +func GetTendermintHeader(cliCtx context.CLIContext) (tendermint.Header, error) { + node, err := cliCtx.GetNode() + if err != nil { + return tendermint.Header{}, err + } + + info, err := node.ABCIInfo() + if err != nil { + return tendermint.Header{}, err + } + + height := info.Response.LastBlockHeight + prevheight := height - 1 + + commit, err := node.Commit(&height) + if err != nil { + return tendermint.Header{}, err + } + + validators, err := node.Validators(&prevheight) + if err != nil { + return tendermint.Header{}, err + } + + nextvalidators, err := node.Validators(&height) + if err != nil { + return tendermint.Header{}, err + } + + header := tendermint.Header{ + SignedHeader: commit.SignedHeader, + ValidatorSet: tmtypes.NewValidatorSet(validators.Validators), + NextValidatorSet: tmtypes.NewValidatorSet(nextvalidators.Validators), + } + + return header, nil +} diff --git a/x/ibc/02-client/doc.go b/x/ibc/02-client/doc.go new file mode 100644 index 000000000000..2b6952ba97eb --- /dev/null +++ b/x/ibc/02-client/doc.go @@ -0,0 +1,10 @@ +/* +Package client implements the ICS 02 - Client Semenatics specification +https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics. This +concrete implementations defines types and method to store and update light +clients which tracks on other chain's state. + +The main type is `Client`, which provides `commitment.Root` to verify state proofs and `ConsensusState` to +verify header proofs. +*/ +package client diff --git a/x/ibc/02-client/exported/exported.go b/x/ibc/02-client/exported/exported.go new file mode 100644 index 000000000000..e7d1ac5c5411 --- /dev/null +++ b/x/ibc/02-client/exported/exported.go @@ -0,0 +1,123 @@ +package exported + +import ( + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + cmn "github.com/tendermint/tendermint/libs/common" +) + +// Blockchain is consensus algorithm which generates valid Headers. It generates +// a unique list of headers starting from a genesis ConsensusState with arbitrary messages. +// This interface is implemented as defined in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#blockchain. +type Blockchain interface { + Genesis() ConsensusState // Consensus state defined in the genesis + Consensus() Header // Header generating function +} + +// ConsensusState is the state of the consensus process +type ConsensusState interface { + ClientType() ClientType // Consensus kind + GetHeight() uint64 + + // GetRoot returns the commitment root of the consensus state, + // which is used for key-value pair verification. + GetRoot() commitment.RootI + + // CheckValidityAndUpdateState returns the updated consensus state + // only if the header is a descendent of this consensus state. + CheckValidityAndUpdateState(Header) (ConsensusState, error) +} + +// Evidence from ADR 009: Evidence Module +// TODO: use evidence module interface once merged +type Evidence interface { + Route() string + Type() string + String() string + Hash() cmn.HexBytes + ValidateBasic() sdk.Error + + // The consensus address of the malicious validator at time of infraction + GetConsensusAddress() sdk.ConsAddress + + // Height at which the infraction occurred + GetHeight() int64 + + // The total power of the malicious validator at time of infraction + GetValidatorPower() int64 + + // The total validator set power at time of infraction + GetTotalPower() int64 +} + +// Misbehaviour defines a specific consensus kind and an evidence +type Misbehaviour interface { + ClientType() ClientType + Evidence() Evidence +} + +// Header is the consensus state update information +type Header interface { + ClientType() ClientType + GetHeight() uint64 +} + +// ClientType defines the type of the consensus algorithm +type ClientType byte + +// available client types +const ( + Tendermint ClientType = iota + 1 // 1 +) + +// string representation of the client types +const ( + ClientTypeTendermint string = "tendermint" +) + +func (ct ClientType) String() string { + switch ct { + case Tendermint: + return ClientTypeTendermint + default: + return "" + } +} + +// MarshalJSON marshal to JSON using string. +func (ct ClientType) MarshalJSON() ([]byte, error) { + return json.Marshal(ct.String()) +} + +// UnmarshalJSON decodes from JSON. +func (ct *ClientType) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + bz2, err := ClientTypeFromString(s) + if err != nil { + return err + } + + *ct = bz2 + return nil +} + +// ClientTypeFromString returns a byte that corresponds to the registered client +// type. It returns 0 if the type is not found/registered. +func ClientTypeFromString(clientType string) (ClientType, error) { + switch clientType { + case ClientTypeTendermint: + return Tendermint, nil + + default: + return 0, fmt.Errorf("'%s' is not a valid client type", clientType) + } +} diff --git a/x/ibc/02-client/handler.go b/x/ibc/02-client/handler.go new file mode 100644 index 000000000000..1b1e1807b54a --- /dev/null +++ b/x/ibc/02-client/handler.go @@ -0,0 +1,78 @@ +package client + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + exported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" +) + +// HandleMsgCreateClient defines the sdk.Handler for MsgCreateClient +func HandleMsgCreateClient(ctx sdk.Context, k Keeper, msg MsgCreateClient) sdk.Result { + clientType, err := exported.ClientTypeFromString(msg.ClientType) + if err != nil { + return sdk.ResultFromError(ErrInvalidClientType(DefaultCodespace, err.Error())) + } + + // TODO: should we create an event with the new client state id ? + _, err = k.CreateClient(ctx, msg.ClientID, clientType, msg.ConsensusState) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + EventTypeCreateClient, + sdk.NewAttribute(AttributeKeyClientID, msg.ClientID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgUpdateClient defines the sdk.Handler for MsgUpdateClient +func HandleMsgUpdateClient(ctx sdk.Context, k Keeper, msg MsgUpdateClient) sdk.Result { + err := k.UpdateClient(ctx, msg.ClientID, msg.Header) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + EventTypeUpdateClient, + sdk.NewAttribute(AttributeKeyClientID, msg.ClientID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgSubmitMisbehaviour defines the sdk.Handler for MsgSubmitMisbehaviour +func HandleMsgSubmitMisbehaviour(ctx sdk.Context, k Keeper, msg MsgSubmitMisbehaviour) sdk.Result { + err := k.CheckMisbehaviourAndUpdateState(ctx, msg.ClientID, msg.Evidence) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + EventTypeSubmitMisbehaviour, + sdk.NewAttribute(AttributeKeyClientID, msg.ClientID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} diff --git a/x/ibc/02-client/keeper/client.go b/x/ibc/02-client/keeper/client.go new file mode 100644 index 000000000000..b75f8174939a --- /dev/null +++ b/x/ibc/02-client/keeper/client.go @@ -0,0 +1,104 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/errors" +) + +// CreateClient creates a new client state and populates it with a given consensus +// state as defined in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#create +func (k Keeper) CreateClient( + ctx sdk.Context, clientID string, + clientType exported.ClientType, consensusState exported.ConsensusState, +) (types.State, error) { + _, found := k.GetClientState(ctx, clientID) + if found { + return types.State{}, errors.ErrClientExists(k.codespace, clientID) + } + + _, found = k.GetClientType(ctx, clientID) + if found { + panic(fmt.Sprintf("consensus type is already defined for client %s", clientID)) + } + + clientState := k.initialize(ctx, clientID, consensusState) + k.SetVerifiedRoot(ctx, clientID, consensusState.GetHeight(), consensusState.GetRoot()) + k.SetClientState(ctx, clientState) + k.SetClientType(ctx, clientID, clientType) + k.Logger(ctx).Info(fmt.Sprintf("client %s created at height %d", clientID, consensusState.GetHeight())) + return clientState, nil +} + +// UpdateClient updates the consensus state and the state root from a provided header +func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error { + clientType, found := k.GetClientType(ctx, clientID) + if !found { + return sdkerrors.Wrap(errors.ErrClientTypeNotFound(k.codespace), "cannot update client") + } + + // check that the header consensus matches the client one + if header.ClientType() != clientType { + return sdkerrors.Wrap(errors.ErrInvalidConsensus(k.codespace), "cannot update client") + } + + clientState, found := k.GetClientState(ctx, clientID) + if !found { + return sdkerrors.Wrap(errors.ErrClientNotFound(k.codespace, clientID), "cannot update client") + } + + if clientState.Frozen { + return sdkerrors.Wrap(errors.ErrClientFrozen(k.codespace, clientID), "cannot update client") + } + + consensusState, found := k.GetConsensusState(ctx, clientID) + if !found { + return sdkerrors.Wrap(errors.ErrConsensusStateNotFound(k.codespace), "cannot update client") + } + + if header.GetHeight() < consensusState.GetHeight() { + return sdkerrors.Wrap( + sdk.ErrInvalidSequence( + fmt.Sprintf("header height < consensus height (%d < %d)", header.GetHeight(), consensusState.GetHeight()), + ), + "cannot update client", + ) + } + + consensusState, err := consensusState.CheckValidityAndUpdateState(header) + if err != nil { + return sdkerrors.Wrap(err, "cannot update client") + } + + k.SetConsensusState(ctx, clientID, consensusState) + k.SetVerifiedRoot(ctx, clientID, consensusState.GetHeight(), consensusState.GetRoot()) + k.Logger(ctx).Info(fmt.Sprintf("client %s updated to height %d", clientID, consensusState.GetHeight())) + return nil +} + +// CheckMisbehaviourAndUpdateState checks for client misbehaviour and freezes the +// client if so. +func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, clientID string, evidence exported.Evidence) error { + clientState, found := k.GetClientState(ctx, clientID) + if !found { + sdk.ResultFromError(errors.ErrClientNotFound(k.codespace, clientID)) + } + + err := k.checkMisbehaviour(ctx, evidence) + if err != nil { + return err + } + + clientState, err = k.freeze(ctx, clientState) + if err != nil { + return err + } + + k.SetClientState(ctx, clientState) + k.Logger(ctx).Info(fmt.Sprintf("client %s frozen due to misbehaviour", clientID)) + return nil +} diff --git a/x/ibc/02-client/keeper/keeper.go b/x/ibc/02-client/keeper/keeper.go new file mode 100644 index 000000000000..ce0d54e99782 --- /dev/null +++ b/x/ibc/02-client/keeper/keeper.go @@ -0,0 +1,205 @@ +package keeper + +import ( + "fmt" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/errors" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// Keeper represents a type that grants read and write permissions to any client +// state information +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + codespace sdk.CodespaceType + prefix []byte // prefix bytes for accessing the store +} + +// NewKeeper creates a new NewKeeper instance +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType) Keeper { + return Keeper{ + storeKey: key, + cdc: cdc, + codespace: sdk.CodespaceType(fmt.Sprintf("%s/%s", codespace, errors.DefaultCodespace)), // "ibc/client", + prefix: []byte{}, + // prefix: []byte(types.SubModuleName + "/"), // "client/" + } +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s/%s", ibctypes.ModuleName, types.SubModuleName)) +} + +// GetClientState gets a particular client from the store +func (k Keeper) GetClientState(ctx sdk.Context, clientID string) (types.State, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyClientState(clientID)) + if bz == nil { + return types.State{}, false + } + + var clientState types.State + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &clientState) + return clientState, true +} + +// SetClientState sets a particular Client to the store +func (k Keeper) SetClientState(ctx sdk.Context, clientState types.State) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(clientState) + store.Set(types.KeyClientState(clientState.ID()), bz) +} + +// GetClientType gets the consensus type for a specific client +func (k Keeper) GetClientType(ctx sdk.Context, clientID string) (exported.ClientType, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyClientType(clientID)) + if bz == nil { + return 0, false + } + + return exported.ClientType(bz[0]), true +} + +// SetClientType sets the specific client consensus type to the provable store +func (k Keeper) SetClientType(ctx sdk.Context, clientID string, clientType exported.ClientType) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + store.Set(types.KeyClientType(clientID), []byte{byte(clientType)}) +} + +// GetConsensusState creates a new client state and populates it with a given consensus state +func (k Keeper) GetConsensusState(ctx sdk.Context, clientID string) (exported.ConsensusState, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyConsensusState(clientID)) + if bz == nil { + return nil, false + } + + var consensusState exported.ConsensusState + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &consensusState) + return consensusState, true +} + +// SetConsensusState sets a ConsensusState to a particular client +func (k Keeper) SetConsensusState(ctx sdk.Context, clientID string, consensusState exported.ConsensusState) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(consensusState) + store.Set(types.KeyConsensusState(clientID), bz) +} + +// GetVerifiedRoot gets a verified commitment Root from a particular height to +// a client +func (k Keeper) GetVerifiedRoot(ctx sdk.Context, clientID string, height uint64) (commitment.RootI, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + + bz := store.Get(types.KeyRoot(clientID, height)) + if bz == nil { + return nil, false + } + + var root commitment.RootI + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &root) + return root, true +} + +// SetVerifiedRoot sets a verified commitment Root from a particular height to +// a client +func (k Keeper) SetVerifiedRoot(ctx sdk.Context, clientID string, height uint64, root commitment.RootI) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(root) + store.Set(types.KeyRoot(clientID, height), bz) +} + +// State returns a new client state with a given id as defined in +// https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#example-implementation +func (k Keeper) initialize(ctx sdk.Context, clientID string, consensusState exported.ConsensusState) types.State { + clientState := types.NewClientState(clientID) + k.SetConsensusState(ctx, clientID, consensusState) + return clientState +} + +func (k Keeper) checkMisbehaviour(ctx sdk.Context, evidence exported.Evidence) error { + switch evidence.Type() { + case exported.ClientTypeTendermint: + var tmEvidence tendermint.Evidence + _, ok := evidence.(tendermint.Evidence) + if !ok { + return errors.ErrInvalidClientType(k.codespace, "consensus type is not Tendermint") + } + err := tendermint.CheckMisbehaviour(tmEvidence) + if err != nil { + return errors.ErrInvalidEvidence(k.codespace, err.Error()) + } + default: + panic(fmt.Sprintf("unregistered evidence type: %s", evidence.Type())) + } + return nil +} + +// freeze updates the state of the client in the event of a misbehaviour +func (k Keeper) freeze(ctx sdk.Context, clientState types.State) (types.State, error) { + if clientState.Frozen { + return types.State{}, sdkerrors.Wrap(errors.ErrClientFrozen(k.codespace, clientState.ID()), "already frozen") + } + + clientState.Frozen = true + return clientState, nil +} + +// VerifyMembership state membership verification function defined by the client type +func (k Keeper) VerifyMembership( + ctx sdk.Context, + clientID string, + height uint64, // sequence + proof commitment.ProofI, + path commitment.PathI, + value []byte, +) bool { + // XXX: commented out for demo + /* + if clientState.Frozen { + return false + } + */ + + root, found := k.GetVerifiedRoot(ctx, clientID, height) + if !found { + return false + } + + return proof.VerifyMembership(root, path, value) +} + +// VerifyNonMembership state non-membership function defined by the client type +func (k Keeper) VerifyNonMembership( + ctx sdk.Context, + clientID string, + height uint64, // sequence + proof commitment.ProofI, + path commitment.PathI, +) bool { + // XXX: commented out for demo + /* + if clientState.Frozen { + return false + } + */ + root, found := k.GetVerifiedRoot(ctx, clientID, height) + if !found { + return false + } + + return proof.VerifyNonMembership(root, path) +} diff --git a/x/ibc/02-client/keeper/querier.go b/x/ibc/02-client/keeper/querier.go new file mode 100644 index 000000000000..5f56b8d6b01a --- /dev/null +++ b/x/ibc/02-client/keeper/querier.go @@ -0,0 +1,77 @@ +package keeper + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/errors" +) + +// QuerierClientState defines the sdk.Querier to query the IBC client state +func QuerierClientState(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params types.QueryClientStateParams + + err := types.SubModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + + clientState, found := k.GetClientState(ctx, params.ClientID) + if !found { + return nil, errors.ErrClientTypeNotFound(k.codespace) + } + + bz, err := types.SubModuleCdc.MarshalJSON(clientState) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} + +// QuerierConsensusState defines the sdk.Querier to query a consensus state +func QuerierConsensusState(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params types.QueryClientStateParams + + err := types.SubModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + + consensusState, found := k.GetConsensusState(ctx, params.ClientID) + if !found { + return nil, errors.ErrConsensusStateNotFound(k.codespace) + } + + bz, err := types.SubModuleCdc.MarshalJSON(consensusState) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} + +// QuerierVerifiedRoot defines the sdk.Querier to query a verified commitment root +func QuerierVerifiedRoot(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params types.QueryCommitmentRootParams + + err := types.SubModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + + root, found := k.GetVerifiedRoot(ctx, params.ClientID, params.Height) + if !found { + return nil, errors.ErrRootNotFound(k.codespace) + } + + bz, err := types.SubModuleCdc.MarshalJSON(root) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} diff --git a/x/ibc/02-client/module.go b/x/ibc/02-client/module.go new file mode 100644 index 000000000000..3df05cdfbdf2 --- /dev/null +++ b/x/ibc/02-client/module.go @@ -0,0 +1,32 @@ +package client + +import ( + "fmt" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/client/cli" +) + +// Name returns the IBC client name +func Name() string { + return SubModuleName +} + +// RegisterRESTRoutes registers the REST routes for the IBC client +func RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + // TODO: +} + +// GetTxCmd returns the root tx command for the IBC client +func GetTxCmd(cdc *codec.Codec, storeKey string) *cobra.Command { + return cli.GetTxCmd(fmt.Sprintf("%s/%s", storeKey, SubModuleName), cdc) +} + +// GetQueryCmd returns no root query command for the IBC client +func GetQueryCmd(cdc *codec.Codec, queryRoute string) *cobra.Command { + return cli.GetQueryCmd(fmt.Sprintf("%s/%s", queryRoute, SubModuleName), cdc) +} diff --git a/x/ibc/02-client/types/codec.go b/x/ibc/02-client/types/codec.go new file mode 100644 index 000000000000..7a141e811a0a --- /dev/null +++ b/x/ibc/02-client/types/codec.go @@ -0,0 +1,30 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" +) + +// SubModuleCdc defines the IBC client codec. +var SubModuleCdc = codec.New() + +// RegisterCodec registers the IBC client interfaces and types +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterInterface((*exported.Blockchain)(nil), nil) + cdc.RegisterInterface((*exported.ConsensusState)(nil), nil) + cdc.RegisterInterface((*exported.Evidence)(nil), nil) + cdc.RegisterInterface((*exported.Header)(nil), nil) + cdc.RegisterInterface((*exported.Misbehaviour)(nil), nil) + + cdc.RegisterConcrete(MsgCreateClient{}, "ibc/client/MsgCreateClient", nil) + cdc.RegisterConcrete(MsgUpdateClient{}, "ibc/client/MsgUpdateClient", nil) + + cdc.RegisterConcrete(tendermint.ConsensusState{}, "ibc/client/tendermint/ConsensusState", nil) + cdc.RegisterConcrete(tendermint.Header{}, "ibc/client/tendermint/Header", nil) + cdc.RegisterConcrete(tendermint.Evidence{}, "ibc/client/tendermint/Evidence", nil) +} + +func init() { + RegisterCodec(SubModuleCdc) +} diff --git a/x/ibc/02-client/types/errors/errors.go b/x/ibc/02-client/types/errors/errors.go new file mode 100644 index 000000000000..c133bcfef808 --- /dev/null +++ b/x/ibc/02-client/types/errors/errors.go @@ -0,0 +1,73 @@ +package errors + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// client error codes +const ( + DefaultCodespace sdk.CodespaceType = "client" + + CodeClientExists sdk.CodeType = 101 + CodeClientNotFound sdk.CodeType = 102 + CodeClientFrozen sdk.CodeType = 103 + CodeConsensusStateNotFound sdk.CodeType = 104 + CodeInvalidConsensusState sdk.CodeType = 105 + CodeClientTypeNotFound sdk.CodeType = 106 + CodeInvalidClientType sdk.CodeType = 107 + CodeRootNotFound sdk.CodeType = 108 + CodeInvalidHeader sdk.CodeType = 109 + CodeInvalidEvidence sdk.CodeType = 110 +) + +// ErrClientExists implements sdk.Error +func ErrClientExists(codespace sdk.CodespaceType, clientID string) sdk.Error { + return sdk.NewError(codespace, CodeClientExists, fmt.Sprintf("client with ID %s already exists", clientID)) +} + +// ErrClientNotFound implements sdk.Error +func ErrClientNotFound(codespace sdk.CodespaceType, clientID string) sdk.Error { + return sdk.NewError(codespace, CodeClientNotFound, fmt.Sprintf("client with ID %s not found", clientID)) +} + +// ErrClientFrozen implements sdk.Error +func ErrClientFrozen(codespace sdk.CodespaceType, clientID string) sdk.Error { + return sdk.NewError(codespace, CodeClientFrozen, fmt.Sprintf("client with ID %s is frozen due to misbehaviour", clientID)) +} + +// ErrConsensusStateNotFound implements sdk.Error +func ErrConsensusStateNotFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeConsensusStateNotFound, "consensus state not found") +} + +// ErrInvalidConsensus implements sdk.Error +func ErrInvalidConsensus(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidConsensusState, "invalid consensus state") +} + +// ErrClientTypeNotFound implements sdk.Error +func ErrClientTypeNotFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeClientTypeNotFound, "client type not found") +} + +// ErrInvalidClientType implements sdk.Error +func ErrInvalidClientType(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidClientType, msg) +} + +// ErrRootNotFound implements sdk.Error +func ErrRootNotFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeRootNotFound, "commitment root not found") +} + +// ErrInvalidHeader implements sdk.Error +func ErrInvalidHeader(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidHeader, "invalid header") +} + +// ErrInvalidEvidence implements sdk.Error +func ErrInvalidEvidence(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidEvidence, "invalid evidence: %s", msg) +} diff --git a/x/ibc/02-client/types/events.go b/x/ibc/02-client/types/events.go new file mode 100644 index 000000000000..7e7f9a508cee --- /dev/null +++ b/x/ibc/02-client/types/events.go @@ -0,0 +1,21 @@ +package types + +import ( + "fmt" + + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// IBC client events +const ( + AttributeKeyClientID = "client_id" +) + +// IBC client events vars +var ( + EventTypeCreateClient = MsgCreateClient{}.Type() + EventTypeUpdateClient = MsgUpdateClient{}.Type() + EventTypeSubmitMisbehaviour = MsgSubmitMisbehaviour{}.Type() + + AttributeValueCategory = fmt.Sprintf("%s_%s", ibctypes.ModuleName, SubModuleName) +) diff --git a/x/ibc/02-client/types/keys.go b/x/ibc/02-client/types/keys.go new file mode 100644 index 000000000000..8d97ca893324 --- /dev/null +++ b/x/ibc/02-client/types/keys.go @@ -0,0 +1,67 @@ +package types + +import ( + "fmt" +) + +const ( + // SubModuleName defines the IBC client name + SubModuleName = "client" + + // StoreKey is the store key string for IBC client + StoreKey = SubModuleName + + // RouterKey is the message route for IBC client + RouterKey = SubModuleName + + // QuerierRoute is the querier route for IBC client + QuerierRoute = SubModuleName +) + +// The following paths are the keys to the store as defined in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#path-space + +// ClientStatePath takes an Identifier and returns a Path under which to store a +// particular client state +func ClientStatePath(clientID string) string { + return fmt.Sprintf("clients/%s/state", clientID) +} + +// ClientTypePath takes an Identifier and returns Path under which to store the +// type of a particular client. +func ClientTypePath(clientID string) string { + return fmt.Sprintf("clients/%s/type", clientID) +} + +// ConsensusStatePath takes an Identifier and returns a Path under which to +// store the consensus state of a client. +func ConsensusStatePath(clientID string) string { + return fmt.Sprintf("clients/%s/consensusState", clientID) +} + +// RootPath takes an Identifier and returns a Path under which to +// store the consensus state of a client. +func RootPath(clientID string, height uint64) string { + return fmt.Sprintf("clients/%s/roots/%d", clientID, height) +} + +// KeyClientState returns the store key for a particular client state +func KeyClientState(clientID string) []byte { + return []byte(ClientStatePath(clientID)) +} + +// KeyClientType returns the store key for type of a particular client +func KeyClientType(clientID string) []byte { + return []byte(ClientTypePath(clientID)) +} + +// KeyConsensusState returns the store key for the consensus state of a particular +// client +func KeyConsensusState(clientID string) []byte { + return []byte(ConsensusStatePath(clientID)) +} + +// KeyRoot returns the store key for a commitment root of a particular +// client at a given height +func KeyRoot(clientID string, height uint64) []byte { + return []byte(RootPath(clientID, height)) +} diff --git a/x/ibc/02-client/types/msgs.go b/x/ibc/02-client/types/msgs.go new file mode 100644 index 000000000000..612f7e543e53 --- /dev/null +++ b/x/ibc/02-client/types/msgs.go @@ -0,0 +1,173 @@ +package types + +import ( + "fmt" + + 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/types/errors" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +var _ sdk.Msg = MsgCreateClient{} + +// MsgCreateClient defines a message to create an IBC client +type MsgCreateClient struct { + ClientID string `json:"client_id" yaml:"client_id"` + ClientType string `json:"client_type" yaml:"client_type"` + ConsensusState exported.ConsensusState `json:"consensus_state" yaml:"consensus_address"` + Signer sdk.AccAddress `json:"address" yaml:"address"` +} + +// NewMsgCreateClient creates a new MsgCreateClient instance +func NewMsgCreateClient(id, clientType string, consensusState exported.ConsensusState, signer sdk.AccAddress) MsgCreateClient { + return MsgCreateClient{ + ClientID: id, + ClientType: clientType, + ConsensusState: consensusState, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgCreateClient) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgCreateClient) Type() string { + return "create_client" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgCreateClient) ValidateBasic() sdk.Error { + if err := host.DefaultClientIdentifierValidator(msg.ClientID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid client ID: %s", err.Error())) + } + if _, err := exported.ClientTypeFromString(msg.ClientType); err != nil { + return errors.ErrInvalidClientType(errors.DefaultCodespace, err.Error()) + } + if msg.ConsensusState == nil { + return errors.ErrInvalidConsensus(errors.DefaultCodespace) + } + if msg.Signer.Empty() { + return sdk.ErrInvalidAddress("empty address") + } + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgCreateClient) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgCreateClient) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +var _ sdk.Msg = MsgUpdateClient{} + +// MsgUpdateClient defines a message to update an IBC client +type MsgUpdateClient struct { + ClientID string `json:"client_id" yaml:"client_id"` + Header exported.Header `json:"header" yaml:"header"` + Signer sdk.AccAddress `json:"address" yaml:"address"` +} + +// NewMsgUpdateClient creates a new MsgUpdateClient instance +func NewMsgUpdateClient(id string, header exported.Header, signer sdk.AccAddress) MsgUpdateClient { + return MsgUpdateClient{ + ClientID: id, + Header: header, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgUpdateClient) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgUpdateClient) Type() string { + return "update_client" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgUpdateClient) ValidateBasic() sdk.Error { + if err := host.DefaultClientIdentifierValidator(msg.ClientID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid client ID: %s", err.Error())) + } + if msg.Header == nil { + return errors.ErrInvalidHeader(errors.DefaultCodespace) + } + if msg.Signer.Empty() { + return sdk.ErrInvalidAddress("empty address") + } + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgUpdateClient) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgUpdateClient) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// MsgSubmitMisbehaviour defines a message to update an IBC client +type MsgSubmitMisbehaviour struct { + ClientID string `json:"id" yaml:"id"` + Evidence exported.Evidence `json:"evidence" yaml:"evidence"` + Signer sdk.AccAddress `json:"address" yaml:"address"` +} + +// NewMsgSubmitMisbehaviour creates a new MsgSubmitMisbehaviour instance +func NewMsgSubmitMisbehaviour(id string, evidence exported.Evidence, signer sdk.AccAddress) MsgSubmitMisbehaviour { + return MsgSubmitMisbehaviour{ + ClientID: id, + Evidence: evidence, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgSubmitMisbehaviour) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgSubmitMisbehaviour) Type() string { + return "submit_misbehaviour" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgSubmitMisbehaviour) ValidateBasic() sdk.Error { + if err := host.DefaultClientIdentifierValidator(msg.ClientID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid client ID: %s", err.Error())) + } + if msg.Evidence == nil { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, "evidence is nil") + } + if err := msg.Evidence.ValidateBasic(); err != nil { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, err.Error()) + } + if msg.Signer.Empty() { + return sdk.ErrInvalidAddress("empty address") + } + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgSubmitMisbehaviour) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgSubmitMisbehaviour) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} diff --git a/x/ibc/02-client/types/msgs_test.go b/x/ibc/02-client/types/msgs_test.go new file mode 100644 index 000000000000..cd416fabdfe8 --- /dev/null +++ b/x/ibc/02-client/types/msgs_test.go @@ -0,0 +1,140 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + 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/types/errors" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" + "github.com/tendermint/tendermint/crypto/secp256k1" + cmn "github.com/tendermint/tendermint/libs/common" +) + +func TestMsgCreateClientValidateBasic(t *testing.T) { + cs := tendermint.ConsensusState{} + privKey := secp256k1.GenPrivKey() + signer := sdk.AccAddress(privKey.PubKey().Address()) + testMsgs := []MsgCreateClient{ + NewMsgCreateClient(exported.ClientTypeTendermint, exported.ClientTypeTendermint, cs, signer), // valid msg + NewMsgCreateClient("badClient", exported.ClientTypeTendermint, cs, signer), // invalid client id + NewMsgCreateClient("goodChain", "bad_type", cs, signer), // invalid client type + NewMsgCreateClient("goodChain", exported.ClientTypeTendermint, nil, signer), // nil Consensus State + NewMsgCreateClient("goodChain", exported.ClientTypeTendermint, cs, sdk.AccAddress{}), // empty signer + } + + cases := []struct { + msg MsgCreateClient + expPass bool + errMsg string + }{ + {testMsgs[0], true, ""}, + {testMsgs[1], false, "invalid client id passed"}, + {testMsgs[2], false, "unregistered client type passed"}, + {testMsgs[3], false, "Nil Consensus State in msg passed"}, + {testMsgs[4], false, "Empty address passed"}, + } + + for i, tc := range cases { + err := tc.msg.ValidateBasic() + if tc.expPass { + require.Nil(t, err, "Msg %d failed: %v", i, err) + } else { + require.NotNil(t, err, "Invalid Msg %d passed: %s", i, tc.errMsg) + } + } +} + +func TestMsgUpdateClient(t *testing.T) { + privKey := secp256k1.GenPrivKey() + signer := sdk.AccAddress(privKey.PubKey().Address()) + testMsgs := []MsgUpdateClient{ + NewMsgUpdateClient(exported.ClientTypeTendermint, tendermint.Header{}, signer), // valid msg + NewMsgUpdateClient("badClient", tendermint.Header{}, signer), // bad client id + NewMsgUpdateClient(exported.ClientTypeTendermint, nil, signer), // nil Header + NewMsgUpdateClient(exported.ClientTypeTendermint, tendermint.Header{}, sdk.AccAddress{}), // empty address + } + + cases := []struct { + msg MsgUpdateClient + expPass bool + errMsg string + }{ + {testMsgs[0], true, ""}, + {testMsgs[1], false, "invalid client id passed"}, + {testMsgs[2], false, "Nil Header passed"}, + {testMsgs[3], false, "Empty address passed"}, + } + + for i, tc := range cases { + err := tc.msg.ValidateBasic() + if tc.expPass { + require.Nil(t, err, "Msg %d failed: %v", i, err) + } else { + require.NotNil(t, err, "Invalid Msg %d passed: %s", i, tc.errMsg) + } + } +} + +var _ exported.Evidence = mockEvidence{} + +const mockStr = "mock" + +// mock GoodEvidence +type mockEvidence struct{} + +// Implement Evidence interface +func (me mockEvidence) Route() string { return mockStr } +func (me mockEvidence) Type() string { return mockStr } +func (me mockEvidence) String() string { return mockStr } +func (me mockEvidence) Hash() cmn.HexBytes { return cmn.HexBytes([]byte(mockStr)) } +func (me mockEvidence) ValidateBasic() sdk.Error { return nil } +func (me mockEvidence) GetConsensusAddress() sdk.ConsAddress { return sdk.ConsAddress{} } +func (me mockEvidence) GetHeight() int64 { return 3 } +func (me mockEvidence) GetValidatorPower() int64 { return 3 } +func (me mockEvidence) GetTotalPower() int64 { return 5 } + +// mock bad evidence +type mockBadEvidence struct { + mockEvidence +} + +// Override ValidateBasic +func (mbe mockBadEvidence) ValidateBasic() sdk.Error { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, "invalid evidence") +} + +func TestMsgSubmitMisbehaviour(t *testing.T) { + privKey := secp256k1.GenPrivKey() + signer := sdk.AccAddress(privKey.PubKey().Address()) + testMsgs := []MsgSubmitMisbehaviour{ + NewMsgSubmitMisbehaviour(exported.ClientTypeTendermint, mockEvidence{}, signer), // valid msg + NewMsgSubmitMisbehaviour("badClient", mockEvidence{}, signer), // bad client id + NewMsgSubmitMisbehaviour(exported.ClientTypeTendermint, nil, signer), // nil evidence + NewMsgSubmitMisbehaviour(exported.ClientTypeTendermint, mockBadEvidence{}, signer), // invalid evidence + NewMsgSubmitMisbehaviour(exported.ClientTypeTendermint, mockEvidence{}, sdk.AccAddress{}), // empty signer + } + + cases := []struct { + msg MsgSubmitMisbehaviour + expPass bool + errMsg string + }{ + {testMsgs[0], true, ""}, + {testMsgs[1], false, "invalid client id passed"}, + {testMsgs[2], false, "Nil Evidence passed"}, + {testMsgs[3], false, "Invalid Evidence passed"}, + {testMsgs[4], false, "Empty address passed"}, + } + + for i, tc := range cases { + err := tc.msg.ValidateBasic() + if tc.expPass { + require.Nil(t, err, "Msg %d failed: %v", i, err) + } else { + require.NotNil(t, err, "Invalid Msg %d passed: %s", i, tc.errMsg) + } + } +} diff --git a/x/ibc/02-client/types/querier.go b/x/ibc/02-client/types/querier.go new file mode 100644 index 000000000000..7638c0200882 --- /dev/null +++ b/x/ibc/02-client/types/querier.go @@ -0,0 +1,66 @@ +package types + +import ( + "strings" + + tmtypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + "github.com/tendermint/tendermint/crypto/merkle" +) + +// query routes supported by the IBC client Querier +const ( + QueryClientState = "client_state" + QueryConsensusState = "consensus_state" + QueryVerifiedRoot = "roots" +) + +// QueryClientStateParams defines the params for the following queries: +// - 'custom/ibc/clients//client_state' +// - 'custom/ibc/clients//consensus_state' +type QueryClientStateParams struct { + ClientID string +} + +// NewQueryClientStateParams creates a new QueryClientStateParams instance +func NewQueryClientStateParams(id string) QueryClientStateParams { + return QueryClientStateParams{ + ClientID: id, + } +} + +// QueryCommitmentRootParams defines the params for the following queries: +// - 'custom/ibc/clients//roots/' +type QueryCommitmentRootParams struct { + ClientID string + Height uint64 +} + +// NewQueryCommitmentRootParams creates a new QueryCommitmentRootParams instance +func NewQueryCommitmentRootParams(id string, height uint64) QueryCommitmentRootParams { + return QueryCommitmentRootParams{ + ClientID: id, + Height: height, + } +} + +// ConsensusStateResponse defines the client response for a Consensus state query. +// It includes the commitment proof and the height of the proof. +type ConsensusStateResponse struct { + ConsensusState tmtypes.ConsensusState + Proof commitment.Proof `json:"proof,omitempty" yaml:"proof,omitempty"` + ProofPath commitment.Path `json:"proof_path,omitempty" yaml:"proof_path,omitempty"` + ProofHeight uint64 `json:"proof_height,omitempty" yaml:"proof_height,omitempty"` +} + +// NewConsensusStateResponse creates a new ConsensusStateResponse instance. +func NewConsensusStateResponse( + clientID string, cs tmtypes.ConsensusState, proof *merkle.Proof, height int64, +) ConsensusStateResponse { + return ConsensusStateResponse{ + ConsensusState: cs, + Proof: commitment.Proof{Proof: proof}, + ProofPath: commitment.NewPath(strings.Split(ConsensusStatePath(clientID), "/")), + ProofHeight: uint64(height), + } +} diff --git a/x/ibc/02-client/types/state.go b/x/ibc/02-client/types/state.go new file mode 100644 index 000000000000..8c60260df0a2 --- /dev/null +++ b/x/ibc/02-client/types/state.go @@ -0,0 +1,24 @@ +package types + +// State is a type that represents the state of a client. +// Any actor holding the Stage can access on and modify that client information. +type State struct { + // Client ID + id string + // Boolean that states if the client is frozen when a misbehaviour proof is + // submitted in the event of an equivocation. + Frozen bool `json:"frozen" yaml:"frozen"` +} + +// NewClientState creates a new ClientState instance +func NewClientState(id string) State { + return State{ + id: id, + Frozen: false, + } +} + +// ID returns the client identifier +func (s State) ID() string { + return s.id +} diff --git a/x/ibc/02-client/types/tendermint/codec.go b/x/ibc/02-client/types/tendermint/codec.go new file mode 100644 index 000000000000..46814fec2479 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/codec.go @@ -0,0 +1,18 @@ +package tendermint + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +var SubModuleCdc = codec.New() + +// RegisterCodec registers the Tendermint types +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(ConsensusState{}, "ibc/client/tendermint/ConsensusState", nil) + cdc.RegisterConcrete(Header{}, "ibc/client/tendermint/Header", nil) + cdc.RegisterConcrete(Evidence{}, "ibc/client/tendermint/Evidence", nil) +} + +func init() { + RegisterCodec(SubModuleCdc) +} diff --git a/x/ibc/02-client/types/tendermint/consensus_state.go b/x/ibc/02-client/types/tendermint/consensus_state.go new file mode 100644 index 000000000000..3ad0892a9ea8 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/consensus_state.go @@ -0,0 +1,89 @@ +package tendermint + +import ( + "bytes" + "errors" + + lerr "github.com/tendermint/tendermint/lite/errors" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +var _ exported.ConsensusState = ConsensusState{} + +// ConsensusState defines a Tendermint consensus state +type ConsensusState struct { + ChainID string `json:"chain_id" yaml:"chain_id"` + Height uint64 `json:"height" yaml:"height"` // NOTE: defined as 'sequence' in the spec + Root commitment.RootI `json:"root" yaml:"root"` + NextValidatorSet *tmtypes.ValidatorSet `json:"next_validator_set" yaml:"next_validator_set"` // contains the PublicKey +} + +// ClientType returns Tendermint +func (ConsensusState) ClientType() exported.ClientType { + return exported.Tendermint +} + +// GetHeight returns the ConsensusState height +func (cs ConsensusState) GetHeight() uint64 { + return cs.Height +} + +// GetRoot returns the commitment Root for the specific +func (cs ConsensusState) GetRoot() commitment.RootI { + return cs.Root +} + +// CheckValidityAndUpdateState checks if the provided header is valid and updates +// the consensus state if appropriate +func (cs ConsensusState) CheckValidityAndUpdateState(header exported.Header) (exported.ConsensusState, error) { + tmHeader, ok := header.(Header) + if !ok { + return nil, errors.New("header not a valid tendermint header") + } + + if err := cs.checkValidity(tmHeader); err != nil { + return nil, err + } + + return cs.update(tmHeader), nil +} + +// checkValidity checks if the Tendermint header is valid +// +// CONTRACT: assumes header.Height > consensusState.Height +func (cs ConsensusState) checkValidity(header Header) error { + // check if the hash from the consensus set and header + // matches + nextHash := cs.NextValidatorSet.Hash() + if cs.Height == uint64(header.Height-1) && + !bytes.Equal(nextHash, header.ValidatorsHash) { + return lerr.ErrUnexpectedValidators(nextHash, header.ValidatorsHash) + } + + // validate the next validator set hash from the header + nextHash = header.NextValidatorSet.Hash() + if !bytes.Equal(header.NextValidatorsHash, nextHash) { + return lerr.ErrUnexpectedValidators(header.NextValidatorsHash, nextHash) + } + + // basic consistency check + if err := header.ValidateBasic(cs.ChainID); err != nil { + return err + } + + // abortTransactionUnless(consensusState.publicKey.verify(header.signature)) + return header.ValidatorSet.VerifyFutureCommit( + cs.NextValidatorSet, cs.ChainID, header.Commit.BlockID, header.Height, header.Commit, + ) +} + +// update the consensus state from a new header +func (cs ConsensusState) update(header Header) ConsensusState { + cs.Height = header.GetHeight() + cs.Root = commitment.NewRoot(header.AppHash) + cs.NextValidatorSet = header.NextValidatorSet + return cs +} diff --git a/x/ibc/02-client/types/tendermint/consensus_state_test.go b/x/ibc/02-client/types/tendermint/consensus_state_test.go new file mode 100644 index 000000000000..2f4129226228 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/consensus_state_test.go @@ -0,0 +1,52 @@ +package tendermint + +import ( + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/tmhash" + tmtypes "github.com/tendermint/tendermint/types" +) + +func (suite *TendermintTestSuite) TestCheckValidity() { + // valid header + err := suite.cs.checkValidity(suite.header) + require.Nil(suite.T(), err, "validity failed") + + // switch out header ValidatorsHash + suite.header.ValidatorsHash = tmhash.Sum([]byte("hello")) + err = suite.cs.checkValidity(suite.header) + require.NotNil(suite.T(), err, "validator hash is wrong") + + // reset suite and make header.NextValidatorSet different + // from NextValidatorSetHash + suite.SetupTest() + privVal := tmtypes.NewMockPV() + val := tmtypes.NewValidator(privVal.GetPubKey(), 5) + suite.header.NextValidatorSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) + err = suite.cs.checkValidity(suite.header) + require.NotNil(suite.T(), err, "header's next validator set is not consistent with hash") + + // reset and make header fail validatebasic + suite.SetupTest() + suite.header.ChainID = "not_mychain" + err = suite.cs.checkValidity(suite.header) + require.NotNil(suite.T(), err, "invalid header should fail ValidateBasic") +} + +func (suite *TendermintTestSuite) TestCheckUpdate() { + // valid header should successfully update consensus state + cs, err := suite.cs.CheckValidityAndUpdateState(suite.header) + + require.Nil(suite.T(), err, "valid update failed") + require.Equal(suite.T(), suite.header.GetHeight(), cs.GetHeight(), "height not updated") + require.Equal(suite.T(), suite.header.AppHash.Bytes(), cs.GetRoot().GetHash(), "root not updated") + tmCS, _ := cs.(ConsensusState) + require.Equal(suite.T(), suite.header.NextValidatorSet, tmCS.NextValidatorSet, "validator set did not update") + + // make header invalid so update should be unsuccessful + suite.SetupTest() + suite.header.ChainID = "not_mychain" + + cs, err = suite.cs.CheckValidityAndUpdateState(suite.header) + require.NotNil(suite.T(), err) + require.Nil(suite.T(), cs) +} diff --git a/x/ibc/02-client/types/tendermint/doc.go b/x/ibc/02-client/types/tendermint/doc.go new file mode 100644 index 000000000000..f0e27c7696a7 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/doc.go @@ -0,0 +1,5 @@ +/* +Package tendermint implements a concrete `ConsensusState`, `Header` and `Equivocation` +for the Tendermint consensus algorithm. +*/ +package tendermint diff --git a/x/ibc/02-client/types/tendermint/header.go b/x/ibc/02-client/types/tendermint/header.go new file mode 100644 index 000000000000..c2d9cfebc9c5 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/header.go @@ -0,0 +1,28 @@ +package tendermint + +import ( + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" +) + +var _ exported.Header = Header{} + +// Header defines the Tendermint consensus Header +type Header struct { + tmtypes.SignedHeader + ValidatorSet *tmtypes.ValidatorSet `json:"validator_set" yaml:"validator_set"` + NextValidatorSet *tmtypes.ValidatorSet `json:"next_validator_set" yaml:"next_validator_set"` +} + +// ClientType defines that the Header is a Tendermint consensus algorithm +func (h Header) ClientType() exported.ClientType { + return exported.Tendermint +} + +// GetHeight returns the current height +// +// NOTE: also referred as `sequence` +func (h Header) GetHeight() uint64 { + return uint64(h.Height) +} diff --git a/x/ibc/02-client/types/tendermint/misbehaviour.go b/x/ibc/02-client/types/tendermint/misbehaviour.go new file mode 100644 index 000000000000..512bb0f72161 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/misbehaviour.go @@ -0,0 +1,96 @@ +package tendermint + +import ( + "fmt" + + yaml "gopkg.in/yaml.v2" + + 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/types/errors" + + "github.com/tendermint/tendermint/crypto/tmhash" + cmn "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" +) + +var _ exported.Evidence = Evidence{} + +// Evidence is a wrapper over tendermint's DuplicateVoteEvidence +// that implements Evidence interface expected by ICS-02 +type Evidence struct { + *tmtypes.DuplicateVoteEvidence + ChainID string `json:"chain_id" yaml:"chain_id"` + ValidatorPower int64 `json:"val_power" yaml:"val_power"` + TotalPower int64 `json:"total_power" yaml:"total_power"` +} + +// Type implements exported.Evidence interface +func (ev Evidence) Route() string { + return exported.ClientTypeTendermint +} + +// Type implements exported.Evidence interface +func (ev Evidence) Type() string { + return exported.ClientTypeTendermint +} + +// String implements exported.Evidence interface +func (ev Evidence) String() string { + bz, err := yaml.Marshal(ev) + if err != nil { + panic(err) + } + return string(bz) +} + +// Hash implements exported.Evidence interface +func (ev Evidence) Hash() cmn.HexBytes { + return tmhash.Sum(SubModuleCdc.MustMarshalBinaryBare(ev)) +} + +// ValidateBasic implements exported.Evidence interface +func (ev Evidence) ValidateBasic() sdk.Error { + if ev.DuplicateVoteEvidence == nil { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, "duplicate evidence is nil") + } + err := ev.DuplicateVoteEvidence.ValidateBasic() + if err != nil { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, err.Error()) + } + if ev.ChainID == "" { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, "chainID is empty") + } + if ev.ValidatorPower <= 0 { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, fmt.Sprintf("Invalid Validator Power: %d", ev.ValidatorPower)) + } + if ev.TotalPower < ev.ValidatorPower { + return errors.ErrInvalidEvidence(errors.DefaultCodespace, fmt.Sprintf("Invalid Total Power: %d", ev.TotalPower)) + } + return nil +} + +// GetConsensusAddress implements exported.Evidence interface +func (ev Evidence) GetConsensusAddress() sdk.ConsAddress { + return sdk.ConsAddress(ev.DuplicateVoteEvidence.Address()) +} + +// GetHeight implements exported.Evidence interface +func (ev Evidence) GetHeight() int64 { + return ev.DuplicateVoteEvidence.Height() +} + +// GetValidatorPower implements exported.Evidence interface +func (ev Evidence) GetValidatorPower() int64 { + return ev.ValidatorPower +} + +// GetTotalPower implements exported.Evidence interface +func (ev Evidence) GetTotalPower() int64 { + return ev.TotalPower +} + +// CheckMisbehaviour checks if the evidence provided is a misbehaviour +func CheckMisbehaviour(evidence Evidence) error { + return evidence.DuplicateVoteEvidence.Verify(evidence.ChainID, evidence.DuplicateVoteEvidence.PubKey) +} diff --git a/x/ibc/02-client/types/tendermint/misbehaviour_test.go b/x/ibc/02-client/types/tendermint/misbehaviour_test.go new file mode 100644 index 000000000000..f5e75e12f785 --- /dev/null +++ b/x/ibc/02-client/types/tendermint/misbehaviour_test.go @@ -0,0 +1,64 @@ +package tendermint + +import ( + "testing" + + "github.com/stretchr/testify/require" + + yaml "gopkg.in/yaml.v2" +) + +func TestString(t *testing.T) { + dupEv := randomDuplicatedVoteEvidence() + ev := Evidence{ + DuplicateVoteEvidence: dupEv, + ChainID: "mychain", + ValidatorPower: 10, + TotalPower: 50, + } + + byteStr, err := yaml.Marshal(ev) + require.Nil(t, err) + require.Equal(t, string(byteStr), ev.String(), "Evidence String method does not work as expected") + +} + +func TestValidateBasic(t *testing.T) { + dupEv := randomDuplicatedVoteEvidence() + + // good evidence + ev := Evidence{ + DuplicateVoteEvidence: dupEv, + ChainID: "mychain", + ValidatorPower: 10, + TotalPower: 50, + } + + err := ev.ValidateBasic() + require.Nil(t, err, "good evidence failed on ValidateBasic: %v", err) + + // invalid duplicate evidence + ev.DuplicateVoteEvidence.VoteA = nil + err = ev.ValidateBasic() + require.NotNil(t, err, "invalid duplicate evidence passed on ValidateBasic") + + // reset duplicate evidence to be valid, and set empty chainID + dupEv = randomDuplicatedVoteEvidence() + ev.DuplicateVoteEvidence = dupEv + ev.ChainID = "" + err = ev.ValidateBasic() + require.NotNil(t, err, "invalid chain-id passed on ValidateBasic") + + // reset chainID and set 0 validator power + ev.ChainID = "mychain" + ev.ValidatorPower = 0 + err = ev.ValidateBasic() + require.NotNil(t, err, "invalid validator power passed on ValidateBasic") + + // reset validator power and set invalid total power + ev.ValidatorPower = 10 + ev.TotalPower = 9 + err = ev.ValidateBasic() + require.NotNil(t, err, "invalid total power passed on ValidateBasic") + +} diff --git a/x/ibc/02-client/types/tendermint/tendermint_test.go b/x/ibc/02-client/types/tendermint/tendermint_test.go new file mode 100644 index 000000000000..11817f73e9ff --- /dev/null +++ b/x/ibc/02-client/types/tendermint/tendermint_test.go @@ -0,0 +1,87 @@ +package tendermint + +import ( + "math" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" + + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +type TendermintTestSuite struct { + suite.Suite + + privVal tmtypes.PrivValidator + valSet *tmtypes.ValidatorSet + header Header + cs ConsensusState +} + +func (suite *TendermintTestSuite) SetupTest() { + privVal := tmtypes.NewMockPV() + val := tmtypes.NewValidator(privVal.GetPubKey(), 10) + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) + vsetHash := valSet.Hash() + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + tmHeader := tmtypes.Header{ + Version: version.Consensus{Block: 2, App: 2}, + ChainID: "mychain", + Height: 3, + Time: timestamp, + NumTxs: 100, + TotalTxs: 1000, + LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt64, make([]byte, tmhash.Size)), + LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: vsetHash, + NextValidatorsHash: vsetHash, + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: tmhash.Sum([]byte("app_hash")), + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: privVal.GetPubKey().Address(), + } + hhash := tmHeader.Hash() + blockID := makeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) + voteSet := tmtypes.NewVoteSet("mychain", 3, 1, tmtypes.PrecommitType, valSet) + commit, err := tmtypes.MakeCommit(blockID, 3, 1, voteSet, []tmtypes.PrivValidator{privVal}) + if err != nil { + panic(err) + } + + signedHeader := tmtypes.SignedHeader{ + Header: &tmHeader, + Commit: commit, + } + + header := Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + NextValidatorSet: valSet, + } + + root := commitment.NewRoot(tmhash.Sum([]byte("my root"))) + + cs := ConsensusState{ + ChainID: "mychain", + Height: 3, + Root: root, + NextValidatorSet: valSet, + } + + // set fields in suite + suite.privVal = privVal + suite.valSet = valSet + suite.header = header + suite.cs = cs +} + +func TestTendermintTestSuite(t *testing.T) { + suite.Run(t, new(TendermintTestSuite)) +} diff --git a/x/ibc/02-client/types/tendermint/test_utils.go b/x/ibc/02-client/types/tendermint/test_utils.go new file mode 100644 index 000000000000..a88bd13d3b1c --- /dev/null +++ b/x/ibc/02-client/types/tendermint/test_utils.go @@ -0,0 +1,47 @@ +package tendermint + +import ( + "github.com/tendermint/tendermint/crypto/tmhash" + tmtypes "github.com/tendermint/tendermint/types" +) + +// Copied unimported test functions from tmtypes to use them here +func makeBlockID(hash []byte, partSetSize int, partSetHash []byte) tmtypes.BlockID { + return tmtypes.BlockID{ + Hash: hash, + PartsHeader: tmtypes.PartSetHeader{ + Total: partSetSize, + Hash: partSetHash, + }, + } + +} + +func makeVote(val tmtypes.PrivValidator, chainID string, valIndex int, height int64, round, step int, blockID tmtypes.BlockID) *tmtypes.Vote { + addr := val.GetPubKey().Address() + v := &tmtypes.Vote{ + ValidatorAddress: addr, + ValidatorIndex: valIndex, + Height: height, + Round: round, + Type: tmtypes.SignedMsgType(step), + BlockID: blockID, + } + err := val.SignVote(chainID, v) + if err != nil { + panic(err) + } + return v +} + +func randomDuplicatedVoteEvidence() *tmtypes.DuplicateVoteEvidence { + val := tmtypes.NewMockPV() + blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), 1000, tmhash.Sum([]byte("partshash"))) + blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), 1000, tmhash.Sum([]byte("partshash"))) + const chainID = "mychain" + return &tmtypes.DuplicateVoteEvidence{ + PubKey: val.GetPubKey(), + VoteA: makeVote(val, chainID, 0, 10, 2, 1, blockID), + VoteB: makeVote(val, chainID, 0, 10, 2, 1, blockID2), + } +} diff --git a/x/ibc/24-host/errors.go b/x/ibc/24-host/errors.go index afc1c26ea303..e6a526e6d0f8 100644 --- a/x/ibc/24-host/errors.go +++ b/x/ibc/24-host/errors.go @@ -14,6 +14,9 @@ var ( // ErrInvalidPath is returned if path string is invalid ErrInvalidPath = sdkerrors.Register(IBCCodeSpace, 2, "invalid path") + // ErrInvalidEvidence is returned if evidence is invalid + ErrInvalidEvidence = sdkerrors.Register(IBCCodeSpace, 3, "invalid evidence") + // ErrInvalidPacket is returned if packets embedded in msg are invalid - ErrInvalidPacket = sdkerrors.Register(IBCCodeSpace, 3, "invalid packet extracted from msg") + ErrInvalidPacket = sdkerrors.Register(IBCCodeSpace, 4, "invalid packet extracted from msg") ) diff --git a/x/ibc/alias.go b/x/ibc/alias.go new file mode 100644 index 000000000000..7c1ad02349e2 --- /dev/null +++ b/x/ibc/alias.go @@ -0,0 +1,29 @@ +package ibc + +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/keeper +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/types + +import ( + "github.com/cosmos/cosmos-sdk/x/ibc/keeper" + "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +const ( + ModuleName = types.ModuleName + StoreKey = types.StoreKey + QuerierRoute = types.QuerierRoute + RouterKey = types.RouterKey +) + +var ( + // functions aliases + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier +) + +type ( + Keeper = keeper.Keeper +) diff --git a/x/ibc/client/cli/cli.go b/x/ibc/client/cli/cli.go new file mode 100644 index 000000000000..5657d2aad3c7 --- /dev/null +++ b/x/ibc/client/cli/cli.go @@ -0,0 +1,43 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + ibcclient "github.com/cosmos/cosmos-sdk/x/ibc/02-client" + "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + ibcTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "IBC transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + ibcTxCmd.AddCommand(client.PostCommands( + ibcclient.GetTxCmd(cdc, storeKey), + )...) + return ibcTxCmd +} + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + // Group ibc queries under a subcommand + ibcQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the IBC module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + ibcQueryCmd.AddCommand(client.GetCommands( + ibcclient.GetQueryCmd(cdc, queryRoute), + )...) + return ibcQueryCmd +} diff --git a/x/ibc/handler.go b/x/ibc/handler.go new file mode 100644 index 000000000000..5dc802b6f063 --- /dev/null +++ b/x/ibc/handler.go @@ -0,0 +1,30 @@ +package ibc + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" +) + +// NewHandler defines the IBC handler +func NewHandler(k Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case client.MsgCreateClient: + return client.HandleMsgCreateClient(ctx, k.ClientKeeper, msg) + + case client.MsgUpdateClient: + return client.HandleMsgUpdateClient(ctx, k.ClientKeeper, msg) + + case client.MsgSubmitMisbehaviour: + return client.HandleMsgSubmitMisbehaviour(ctx, k.ClientKeeper, msg) + + default: + errMsg := fmt.Sprintf("unrecognized IBC Client message type: %T", msg) + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} diff --git a/x/ibc/keeper/keeper.go b/x/ibc/keeper/keeper.go new file mode 100644 index 000000000000..fce91fb353bd --- /dev/null +++ b/x/ibc/keeper/keeper.go @@ -0,0 +1,19 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" +) + +// Keeper defines each ICS keeper for IBC +type Keeper struct { + ClientKeeper client.Keeper +} + +// NewKeeper creates a new ibc Keeper +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType) Keeper { + return Keeper{ + ClientKeeper: client.NewKeeper(cdc, key, codespace), + } +} diff --git a/x/ibc/keeper/querier.go b/x/ibc/keeper/querier.go new file mode 100644 index 000000000000..7cbd4ec9c499 --- /dev/null +++ b/x/ibc/keeper/querier.go @@ -0,0 +1,24 @@ +package keeper + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" +) + +// NewQuerier creates a querier for the IBC module +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case client.QueryClientState: + return client.QuerierClientState(ctx, req, k.ClientKeeper) + case client.QueryConsensusState: + return client.QuerierConsensusState(ctx, req, k.ClientKeeper) + case client.QueryVerifiedRoot: + return client.QuerierVerifiedRoot(ctx, req, k.ClientKeeper) + default: + return nil, sdk.ErrUnknownRequest("unknown IBC client query endpoint") + } + } +} diff --git a/x/ibc/module.go b/x/ibc/module.go index a81836a9c7ed..675b18aa1ddf 100644 --- a/x/ibc/module.go +++ b/x/ibc/module.go @@ -1 +1,132 @@ package ibc + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" + "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// TODO: AppModuleSimulation +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic defines the basic application module used by the staking module. +type AppModuleBasic struct{} + +var _ module.AppModuleBasic = AppModuleBasic{} + +// Name returns the staking module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterCodec registers the staking module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + client.RegisterCodec(cdc) + commitment.RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the staking +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return nil +} + +// ValidateGenesis performs genesis state validation for the staking module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + return nil +} + +// RegisterRESTRoutes registers the REST routes for the staking module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + /// TODO: +} + +// GetTxCmd returns the root tx command for the staking module. +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(StoreKey, cdc) +} + +// GetQueryCmd returns no root query command for the staking module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetQueryCmd(QuerierRoute, cdc) +} + +// AppModule implements an application module for the staking module. +type AppModule struct { + AppModuleBasic + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(k Keeper) AppModule { + return AppModule{ + keeper: k, + } +} + +// Name returns the staking module's name. +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants registers the staking module invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + // TODO: +} + +// Route returns the message routing key for the staking module. +func (AppModule) Route() string { + return RouterKey +} + +// NewHandler returns an sdk.Handler for the staking module. +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} + +// QuerierRoute returns the staking module's querier route name. +func (AppModule) QuerierRoute() string { + return QuerierRoute +} + +// NewQuerierHandler returns the staking module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the staking module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the staking +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + return nil +} + +// BeginBlock returns the begin blocker for the staking module. +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { +} + +// EndBlock returns the end blocker for the staking module. It returns no validator +// updates. +func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/ibc/types/types.go b/x/ibc/types/types.go new file mode 100644 index 000000000000..535a8f2461e5 --- /dev/null +++ b/x/ibc/types/types.go @@ -0,0 +1,15 @@ +package types + +const ( + // ModuleName is the name of the IBC module + ModuleName = "ibc" + + // StoreKey is the string store representation + StoreKey = ModuleName + + // QuerierRoute is the querier route for the IBC module + QuerierRoute = ModuleName + + // RouterKey is the msg router key for the IBC module + RouterKey = ModuleName +) diff --git a/x/ibc/version/version.go b/x/ibc/version/version.go new file mode 100644 index 000000000000..a7d3275fae5b --- /dev/null +++ b/x/ibc/version/version.go @@ -0,0 +1,13 @@ +package version + +import "strconv" + +const Version int64 = 1 + +func DefaultPrefix() []byte { + return Prefix(Version) +} + +func Prefix(version int64) []byte { + return []byte("v" + strconv.FormatInt(version, 10) + "/") +}