From 397ffbe119320e5f480bcb13658867f3a38d5454 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Fri, 17 Apr 2020 22:36:12 -0400 Subject: [PATCH] x/ibc: update ICS09 Loopback Client (#6018) * x/ibc: update ICS09 Loopback Client * ibc/09-localhost: client state errors * ibc/09-localhost: add messages for create client * x/ibc: BeginBlocker for localhost client * test fixes * update client REST * add tests --- CHANGELOG.md | 2 +- simapp/app.go | 5 +- x/ibc/02-client/abci.go | 21 + x/ibc/02-client/abci_test.go | 60 +++ x/ibc/02-client/client/utils/utils.go | 4 +- x/ibc/02-client/exported/exported.go | 6 +- x/ibc/02-client/handler.go | 18 +- x/ibc/02-client/keeper/client.go | 44 +- x/ibc/02-client/keeper/client_test.go | 30 +- x/ibc/02-client/keeper/keeper.go | 34 +- x/ibc/03-connection/client/utils/utils.go | 4 +- x/ibc/03-connection/keeper/keeper.go | 10 +- x/ibc/04-channel/client/utils/utils.go | 2 +- x/ibc/04-channel/keeper/keeper.go | 6 +- x/ibc/07-tendermint/client/rest/tx.go | 4 +- x/ibc/07-tendermint/types/client_state.go | 8 +- x/ibc/09-localhost/client/cli/cli.go | 25 ++ x/ibc/09-localhost/client/cli/tx.go | 47 ++ x/ibc/09-localhost/client/rest/rest.go | 18 + x/ibc/09-localhost/client/rest/tx.go | 56 +++ x/ibc/09-localhost/client_state_test.go | 410 ------------------ x/ibc/09-localhost/consensus_state.go | 31 -- x/ibc/09-localhost/evidence.go | 67 --- x/ibc/09-localhost/header.go | 26 -- x/ibc/09-localhost/module.go | 29 ++ .../09-localhost/{ => types}/client_state.go | 93 ++-- x/ibc/09-localhost/types/client_state_test.go | 343 +++++++++++++++ x/ibc/09-localhost/{ => types}/codec.go | 11 +- .../{ => types}/localhost_test.go | 2 +- x/ibc/09-localhost/types/msgs.go | 72 +++ x/ibc/20-transfer/module.go | 2 +- x/ibc/ante/ante.go | 2 +- x/ibc/client/cli/cli.go | 2 + x/ibc/client/rest/rest.go | 4 + x/ibc/module.go | 4 +- 35 files changed, 856 insertions(+), 646 deletions(-) create mode 100644 x/ibc/02-client/abci.go create mode 100644 x/ibc/02-client/abci_test.go create mode 100644 x/ibc/09-localhost/client/cli/cli.go create mode 100644 x/ibc/09-localhost/client/cli/tx.go create mode 100644 x/ibc/09-localhost/client/rest/rest.go create mode 100644 x/ibc/09-localhost/client/rest/tx.go delete mode 100644 x/ibc/09-localhost/client_state_test.go delete mode 100644 x/ibc/09-localhost/consensus_state.go delete mode 100644 x/ibc/09-localhost/evidence.go delete mode 100644 x/ibc/09-localhost/header.go create mode 100644 x/ibc/09-localhost/module.go rename x/ibc/09-localhost/{ => types}/client_state.go (71%) create mode 100644 x/ibc/09-localhost/types/client_state_test.go rename x/ibc/09-localhost/{ => types}/codec.go (65%) rename x/ibc/09-localhost/{ => types}/localhost_test.go (96%) create mode 100644 x/ibc/09-localhost/types/msgs.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 77214b4b1a..a1e5dc794b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,7 +105,7 @@ information on how to implement the new `Keyring` interface. * (ibc/ante) Implement IBC `AnteHandler` as per [ADR 15 - IBC Packet Receiver](https://github.com/cosmos/tree/master/docs/architecture/adr-015-ibc-packet-receiver.md). * (x/capability) [\#5828](https://github.com/cosmos/cosmos-sdk/pull/5828) Capability module integration as outlined in [ADR 3 - Dynamic Capability Store](https://github.com/cosmos/tree/master/docs/architecture/adr-003-dynamic-capability-store.md). * (x/params) [\#6005](https://github.com/cosmos/cosmos-sdk/pull/6005) Add new CLI command for querying raw x/params parameters by subspace and key. - * (x/ibc) [\#5769] Implementation of localhost client. + * (x/ibc) [\#5769](https://github.com/cosmos/cosmos-sdk/pull/5769) [ICS 009 - Loopback Client](https://github.com/cosmos/ics/tree/master/spec/ics-009-loopback-client) subpackage ### Bug Fixes diff --git a/simapp/app.go b/simapp/app.go index 60de2b6de4..4ac27bbe51 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -288,7 +288,10 @@ func NewSimApp( // During begin block slashing happens after distr.BeginBlocker so that // there is nothing left over in the validator fee pool, so as to keep the // CanWithdrawInvariant invariant. - app.mm.SetOrderBeginBlockers(upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, evidence.ModuleName, staking.ModuleName) + app.mm.SetOrderBeginBlockers( + upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, + evidence.ModuleName, staking.ModuleName, ibc.ModuleName, + ) app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName) // NOTE: The genutils moodule must occur after staking so that pools are diff --git a/x/ibc/02-client/abci.go b/x/ibc/02-client/abci.go new file mode 100644 index 0000000000..97982b06b6 --- /dev/null +++ b/x/ibc/02-client/abci.go @@ -0,0 +1,21 @@ +package client + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/keeper" +) + +// BeginBlocker updates an existing localhost client with the latest block height. +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { + localhostClient, found := k.GetClientState(ctx, exported.ClientTypeLocalHost) + if !found { + return + } + + // update the localhost client with the latest block height + _, err := k.UpdateClient(ctx, localhostClient.GetID(), nil) + if err != nil { + panic(err) + } +} diff --git a/x/ibc/02-client/abci_test.go b/x/ibc/02-client/abci_test.go new file mode 100644 index 0000000000..0654a9b332 --- /dev/null +++ b/x/ibc/02-client/abci_test.go @@ -0,0 +1,60 @@ +package client_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" + "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" +) + +type ClientTestSuite struct { + suite.Suite + + cdc *codec.Codec + ctx sdk.Context + app *simapp.SimApp +} + +func (suite *ClientTestSuite) SetupTest() { + isCheckTx := false + + suite.app = simapp.Setup(isCheckTx) + suite.cdc = suite.app.Codec() + suite.ctx = suite.app.BaseApp.NewContext(isCheckTx, abci.Header{Height: 1, ChainID: "localhost_chain"}) + +} + +func TestClientTestSuite(t *testing.T) { + suite.Run(t, new(ClientTestSuite)) +} + +func (suite *ClientTestSuite) TestBeginBlocker() { + localHostClient := localhosttypes.NewClientState( + suite.app.IBCKeeper.ClientKeeper.ClientStore(suite.ctx, exported.ClientTypeLocalHost), + suite.ctx.ChainID(), + suite.ctx.BlockHeight(), + ) + _, err := suite.app.IBCKeeper.ClientKeeper.CreateClient(suite.ctx, localHostClient, nil) + suite.Require().NoError(err) + + // increase height + suite.ctx = suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 1) + + var prevHeight uint64 + for i := 0; i < 10; i++ { + prevHeight = localHostClient.GetLatestHeight() + suite.Require().NotPanics(func() { + client.BeginBlocker(suite.ctx, suite.app.IBCKeeper.ClientKeeper) + }, "BeginBlocker shouldn't panic") + localHostClient, found := suite.app.IBCKeeper.ClientKeeper.GetClientState(suite.ctx, localHostClient.GetID()) + suite.Require().True(found) + suite.Require().Equal(prevHeight+1, localHostClient.GetLatestHeight()) + } +} diff --git a/x/ibc/02-client/client/utils/utils.go b/x/ibc/02-client/client/utils/utils.go index 76142fa905..8dae0bc0de 100644 --- a/x/ibc/02-client/client/utils/utils.go +++ b/x/ibc/02-client/client/utils/utils.go @@ -54,7 +54,7 @@ func QueryClientState( } var clientState exported.ClientState - if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &clientState); err != nil { + if err := cliCtx.Codec.UnmarshalBinaryBare(res.Value, &clientState); err != nil { return types.StateResponse{}, err } @@ -82,7 +82,7 @@ func QueryConsensusState( } var cs exported.ConsensusState - if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &cs); err != nil { + if err := cliCtx.Codec.UnmarshalBinaryBare(res.Value, &cs); err != nil { return conStateRes, err } diff --git a/x/ibc/02-client/exported/exported.go b/x/ibc/02-client/exported/exported.go index 8b61453eec..221f774f15 100644 --- a/x/ibc/02-client/exported/exported.go +++ b/x/ibc/02-client/exported/exported.go @@ -148,12 +148,15 @@ const ( // string representation of the client types const ( ClientTypeTendermint string = "tendermint" + ClientTypeLocalHost string = "localhost" ) func (ct ClientType) String() string { switch ct { case Tendermint: return ClientTypeTendermint + case Localhost: + return ClientTypeLocalHost default: return "" } @@ -187,7 +190,8 @@ func ClientTypeFromString(clientType string) ClientType { switch clientType { case ClientTypeTendermint: return Tendermint - + case ClientTypeLocalHost: + return Localhost default: return 0 } diff --git a/x/ibc/02-client/handler.go b/x/ibc/02-client/handler.go index 3c8d135e93..abafdedb4b 100644 --- a/x/ibc/02-client/handler.go +++ b/x/ibc/02-client/handler.go @@ -1,6 +1,8 @@ package client import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/evidence" @@ -8,7 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" - localhost "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost" + localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" ) // HandleMsgCreateClient defines the sdk.Handler for MsgCreateClient @@ -29,7 +31,12 @@ func HandleMsgCreateClient(ctx sdk.Context, k Keeper, msg exported.MsgCreateClie return nil, err } case exported.Localhost: - clientState = localhost.NewClientState(ctx.MultiStore().GetKVStore(k.GetStoreKey())) + // msg client id is always "localhost" + clientState = localhosttypes.NewClientState( + k.ClientStore(ctx, msg.GetClientID()), + ctx.ChainID(), + ctx.BlockHeight(), + ) default: return nil, sdkerrors.Wrap(ErrInvalidClientType, msg.GetClientType()) } @@ -66,7 +73,8 @@ func HandleMsgCreateClient(ctx sdk.Context, k Keeper, msg exported.MsgCreateClie // HandleMsgUpdateClient defines the sdk.Handler for MsgUpdateClient func HandleMsgUpdateClient(ctx sdk.Context, k Keeper, msg exported.MsgUpdateClient) (*sdk.Result, error) { - if err := k.UpdateClient(ctx, msg.GetClientID(), msg.GetHeader()); err != nil { + clientState, err := k.UpdateClient(ctx, msg.GetClientID(), msg.GetHeader()) + if err != nil { return nil, err } @@ -76,11 +84,13 @@ func HandleMsgUpdateClient(ctx sdk.Context, k Keeper, msg exported.MsgUpdateClie attributes[i+1] = sdk.NewAttribute(sdk.AttributeKeySender, signer.String()) } + k.Logger(ctx).Info(fmt.Sprintf("client %s updated to height %d", msg.GetClientID(), clientState.GetLatestHeight())) + ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( EventTypeUpdateClient, sdk.NewAttribute(AttributeKeyClientID, msg.GetClientID()), - sdk.NewAttribute(AttrbuteKeyClientType, msg.GetHeader().ClientType().String()), + sdk.NewAttribute(AttrbuteKeyClientType, clientState.ClientType().String()), ), sdk.NewEvent( sdk.EventTypeMessage, diff --git a/x/ibc/02-client/keeper/client.go b/x/ibc/02-client/keeper/client.go index 98f2220727..9bec54da4b 100644 --- a/x/ibc/02-client/keeper/client.go +++ b/x/ibc/02-client/keeper/client.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" tendermint "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" ) // CreateClient creates a new client state and populates it with a given consensus @@ -29,9 +30,8 @@ func (k Keeper) CreateClient( panic(fmt.Sprintf("client type is already defined for client %s", clientID)) } - height := consensusState.GetHeight() if consensusState != nil { - k.SetClientConsensusState(ctx, clientID, height, consensusState) + k.SetClientConsensusState(ctx, clientID, consensusState.GetHeight(), consensusState) } k.SetClientState(ctx, clientState) @@ -53,25 +53,26 @@ func (k Keeper) CreateClient( } // 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 { +func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) (exported.ClientState, error) { clientType, found := k.GetClientType(ctx, clientID) if !found { - return sdkerrors.Wrapf(types.ErrClientTypeNotFound, "cannot update client with ID %s", clientID) + return nil, sdkerrors.Wrapf(types.ErrClientTypeNotFound, "cannot update client with ID %s", clientID) } // check that the header consensus matches the client one - if header.ClientType() != clientType { - return sdkerrors.Wrapf(types.ErrInvalidConsensus, "cannot update client with ID %s", clientID) + // NOTE: not checked for localhost client + if header != nil && clientType != exported.Localhost && header.ClientType() != clientType { + return nil, sdkerrors.Wrapf(types.ErrInvalidConsensus, "cannot update client with ID %s", clientID) } clientState, found := k.GetClientState(ctx, clientID) if !found { - return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) + return nil, sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) } // addittion to spec: prevent update if the client is frozen if clientState.IsFrozen() { - return sdkerrors.Wrapf(types.ErrClientFrozen, "cannot update client with ID %s", clientID) + return nil, sdkerrors.Wrapf(types.ErrClientFrozen, "cannot update client with ID %s", clientID) } var ( @@ -84,30 +85,29 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H clientState, consensusState, err = tendermint.CheckValidityAndUpdateState( clientState, header, ctx.BlockTime(), ) + case exported.Localhost: + // override client state and update the block height + clientState = localhosttypes.NewClientState( + k.ClientStore(ctx, clientState.GetID()), + clientState.GetChainID(), + ctx.BlockHeight(), + ) default: err = types.ErrInvalidClientType } if err != nil { - return sdkerrors.Wrapf(err, "cannot update client with ID %s", clientID) + return nil, sdkerrors.Wrapf(err, "cannot update client with ID %s", clientID) } k.SetClientState(ctx, clientState) - k.SetClientConsensusState(ctx, clientID, header.GetHeight(), consensusState) - k.Logger(ctx).Info(fmt.Sprintf("client %s updated to height %d", clientID, header.GetHeight())) - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeUpdateClient, - sdk.NewAttribute(types.AttributeKeyClientID, clientID), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - ), - }) + // we don't set consensus state for localhost client + if header != nil && clientType != exported.Localhost { + k.SetClientConsensusState(ctx, clientID, header.GetHeight(), consensusState) + } - return nil + return clientState, nil } // CheckMisbehaviourAndUpdateState checks for client misbehaviour and freezes the diff --git a/x/ibc/02-client/keeper/client_test.go b/x/ibc/02-client/keeper/client_test.go index bea5392a3d..e06cee175d 100644 --- a/x/ibc/02-client/keeper/client_test.go +++ b/x/ibc/02-client/keeper/client_test.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" ) @@ -60,7 +61,7 @@ func (suite *KeeperTestSuite) TestCreateClient() { } } -func (suite *KeeperTestSuite) TestUpdateClient() { +func (suite *KeeperTestSuite) TestUpdateClientTendermint() { // Must create header creation functions since suite.header gets recreated on each test case createValidUpdateFn := func(s *KeeperTestSuite) ibctmtypes.Header { return ibctmtypes.CreateTestHeader(testClientID, suite.header.Height+1, suite.header.Time.Add(time.Minute), @@ -137,7 +138,7 @@ func (suite *KeeperTestSuite) TestUpdateClient() { suite.ctx = suite.ctx.WithBlockTime(updateHeader.Time.Add(time.Minute)) - err = suite.keeper.UpdateClient(suite.ctx, testClientID, updateHeader) + updatedClientState, err := suite.keeper.UpdateClient(suite.ctx, testClientID, updateHeader) if tc.expPass { suite.Require().NoError(err, err) @@ -159,9 +160,16 @@ func (suite *KeeperTestSuite) TestUpdateClient() { // recalculate cached totalVotingPower field for equality check tmConsState.ValidatorSet.TotalVotingPower() + tmClientState, ok := updatedClientState.(ibctmtypes.ClientState) + suite.Require().True(ok, "client state is not a tendermint client state") + + // recalculate cached totalVotingPower field for equality check + tmClientState.LastHeader.ValidatorSet.TotalVotingPower() + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) suite.Require().Equal(updateHeader.GetHeight(), clientState.GetLatestHeight(), "client state height not updated correctly on case %s", tc.name) suite.Require().Equal(expConsensusState, consensusState, "consensus state should have been updated on case %s", tc.name) + suite.Require().Equal(updatedClientState, tmClientState, "client states don't match") } else { suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) } @@ -169,6 +177,24 @@ func (suite *KeeperTestSuite) TestUpdateClient() { } } +func (suite *KeeperTestSuite) TestUpdateClientLocalhost() { + var localhostClient exported.ClientState = localhosttypes.NewClientState( + suite.keeper.ClientStore(suite.ctx, exported.ClientTypeLocalHost), + suite.header.ChainID, + suite.ctx.BlockHeight(), + ) + + localhostClient, err := suite.keeper.CreateClient(suite.ctx, localhostClient, nil) + suite.Require().NoError(err, err) + + suite.ctx = suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 1) + + updatedClientState, err := suite.keeper.UpdateClient(suite.ctx, exported.ClientTypeLocalHost, nil) + suite.Require().NoError(err, err) + suite.Require().Equal(localhostClient.GetID(), updatedClientState.GetID()) + suite.Require().Equal(localhostClient.GetLatestHeight()+1, updatedClientState.GetLatestHeight()) +} + func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { altPrivVal := tmtypes.NewMockPV() altVal := tmtypes.NewValidator(altPrivVal.GetPubKey(), 4) diff --git a/x/ibc/02-client/keeper/keeper.go b/x/ibc/02-client/keeper/keeper.go index 1f678c532d..9578d15209 100644 --- a/x/ibc/02-client/keeper/keeper.go +++ b/x/ibc/02-client/keeper/keeper.go @@ -40,33 +40,29 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s/%s", ibctypes.ModuleName, types.SubModuleName)) } -func (k Keeper) GetStoreKey() sdk.StoreKey { - return k.storeKey -} - // GetClientState gets a particular client from the store func (k Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) { - store := k.clientStore(ctx, clientID) + store := k.ClientStore(ctx, clientID) bz := store.Get(ibctypes.KeyClientState()) if bz == nil { return nil, false } var clientState exported.ClientState - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &clientState) + k.cdc.MustUnmarshalBinaryBare(bz, &clientState) return clientState, true } // SetClientState sets a particular Client to the store func (k Keeper) SetClientState(ctx sdk.Context, clientState exported.ClientState) { - store := k.clientStore(ctx, clientState.GetID()) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(clientState) + store := k.ClientStore(ctx, clientState.GetID()) + bz := k.cdc.MustMarshalBinaryBare(clientState) store.Set(ibctypes.KeyClientState(), bz) } // GetClientType gets the consensus type for a specific client func (k Keeper) GetClientType(ctx sdk.Context, clientID string) (exported.ClientType, bool) { - store := k.clientStore(ctx, clientID) + store := k.ClientStore(ctx, clientID) bz := store.Get(ibctypes.KeyClientType()) if bz == nil { return 0, false @@ -77,35 +73,35 @@ func (k Keeper) GetClientType(ctx sdk.Context, clientID string) (exported.Client // SetClientType sets the specific client consensus type to the provable store func (k Keeper) SetClientType(ctx sdk.Context, clientID string, clientType exported.ClientType) { - store := k.clientStore(ctx, clientID) + store := k.ClientStore(ctx, clientID) store.Set(ibctypes.KeyClientType(), []byte{byte(clientType)}) } // GetClientConsensusState gets the stored consensus state from a client at a given height. func (k Keeper) GetClientConsensusState(ctx sdk.Context, clientID string, height uint64) (exported.ConsensusState, bool) { - store := k.clientStore(ctx, clientID) + store := k.ClientStore(ctx, clientID) bz := store.Get(ibctypes.KeyConsensusState(height)) if bz == nil { return nil, false } var consensusState exported.ConsensusState - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &consensusState) + k.cdc.MustUnmarshalBinaryBare(bz, &consensusState) return consensusState, true } // SetClientConsensusState sets a ConsensusState to a particular client at the given // height func (k Keeper) SetClientConsensusState(ctx sdk.Context, clientID string, height uint64, consensusState exported.ConsensusState) { - store := k.clientStore(ctx, clientID) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(consensusState) + store := k.ClientStore(ctx, clientID) + bz := k.cdc.MustMarshalBinaryBare(consensusState) store.Set(ibctypes.KeyConsensusState(height), bz) } // HasClientConsensusState returns if keeper has a ConsensusState for a particular // client at the given height func (k Keeper) HasClientConsensusState(ctx sdk.Context, clientID string, height uint64) bool { - store := k.clientStore(ctx, clientID) + store := k.ClientStore(ctx, clientID) return store.Has(ibctypes.KeyConsensusState(height)) } @@ -118,7 +114,7 @@ func (k Keeper) GetLatestClientConsensusState(ctx sdk.Context, clientID string) return k.GetClientConsensusState(ctx, clientID, clientState.GetLatestHeight()) } -// GetClientConsensusStatelTE will get the latest ConsensusState of a particular client at the latest height +// GetClientConsensusStateLTE will get the latest ConsensusState of a particular client at the latest height // less than or equal to the given height func (k Keeper) GetClientConsensusStateLTE(ctx sdk.Context, clientID string, maxHeight uint64) (exported.ConsensusState, bool) { for i := maxHeight; i > 0; i-- { @@ -163,7 +159,7 @@ func (k Keeper) IterateClients(ctx sdk.Context, cb func(exported.ClientState) bo continue } var clientState exported.ClientState - k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &clientState) + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &clientState) if cb(clientState) { break @@ -180,9 +176,9 @@ func (k Keeper) GetAllClients(ctx sdk.Context) (states []exported.ClientState) { return states } -// Returns isolated prefix store for each client so they can read/write in separate +// ClientStore returns isolated prefix store for each client so they can read/write in separate // namespace without being able to read/write other client's data -func (k Keeper) clientStore(ctx sdk.Context, clientID string) sdk.KVStore { +func (k Keeper) ClientStore(ctx sdk.Context, clientID string) sdk.KVStore { // append here is safe, appends within a function won't cause // weird side effects when its singlethreaded clientPrefix := append([]byte("clients/"+clientID), '/') diff --git a/x/ibc/03-connection/client/utils/utils.go b/x/ibc/03-connection/client/utils/utils.go index 0334ad0b53..4ede59e5dd 100644 --- a/x/ibc/03-connection/client/utils/utils.go +++ b/x/ibc/03-connection/client/utils/utils.go @@ -55,7 +55,7 @@ func QueryConnection( } var connection types.ConnectionEnd - if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &connection); err != nil { + if err := cliCtx.Codec.UnmarshalBinaryBare(res.Value, &connection); err != nil { return types.ConnectionResponse{}, err } @@ -81,7 +81,7 @@ func QueryClientConnections( } var paths []string - if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &paths); err != nil { + if err := cliCtx.Codec.UnmarshalBinaryBare(res.Value, &paths); err != nil { return types.ClientConnectionsResponse{}, err } diff --git a/x/ibc/03-connection/keeper/keeper.go b/x/ibc/03-connection/keeper/keeper.go index 9b2d5d7d48..79a1b8b69d 100644 --- a/x/ibc/03-connection/keeper/keeper.go +++ b/x/ibc/03-connection/keeper/keeper.go @@ -52,14 +52,14 @@ func (k Keeper) GetConnection(ctx sdk.Context, connectionID string) (types.Conne } var connection types.ConnectionEnd - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &connection) + k.cdc.MustUnmarshalBinaryBare(bz, &connection) return connection, true } // SetConnection sets a connection to the store func (k Keeper) SetConnection(ctx sdk.Context, connectionID string, connection types.ConnectionEnd) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(connection) + bz := k.cdc.MustMarshalBinaryBare(connection) store.Set(ibctypes.KeyConnection(connectionID), bz) } @@ -73,14 +73,14 @@ func (k Keeper) GetClientConnectionPaths(ctx sdk.Context, clientID string) ([]st } var paths []string - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &paths) + k.cdc.MustUnmarshalBinaryBare(bz, &paths) return paths, true } // SetClientConnectionPaths sets the connections paths for client func (k Keeper) SetClientConnectionPaths(ctx sdk.Context, clientID string, paths []string) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(paths) + bz := k.cdc.MustMarshalBinaryBare(paths) store.Set(ibctypes.KeyClientConnections(clientID), bz) } @@ -94,7 +94,7 @@ func (k Keeper) IterateConnections(ctx sdk.Context, cb func(types.IdentifiedConn defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var connection types.ConnectionEnd - k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &connection) + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &connection) identifier := string(iterator.Key()[len(ibctypes.KeyConnectionPrefix)+1:]) conn := types.IdentifiedConnectionEnd{ diff --git a/x/ibc/04-channel/client/utils/utils.go b/x/ibc/04-channel/client/utils/utils.go index 729fad9d1d..669c47cab2 100644 --- a/x/ibc/04-channel/client/utils/utils.go +++ b/x/ibc/04-channel/client/utils/utils.go @@ -62,7 +62,7 @@ func QueryChannel( } var channel types.Channel - if err := ctx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &channel); err != nil { + if err := ctx.Codec.UnmarshalBinaryBare(res.Value, &channel); err != nil { return types.ChannelResponse{}, err } return types.NewChannelResponse(portID, channelID, channel, res.Proof, res.Height), nil diff --git a/x/ibc/04-channel/keeper/keeper.go b/x/ibc/04-channel/keeper/keeper.go index 2623f538fb..f7b97804e5 100644 --- a/x/ibc/04-channel/keeper/keeper.go +++ b/x/ibc/04-channel/keeper/keeper.go @@ -53,14 +53,14 @@ func (k Keeper) GetChannel(ctx sdk.Context, portID, channelID string) (types.Cha } var channel types.Channel - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &channel) + k.cdc.MustUnmarshalBinaryBare(bz, &channel) return channel, true } // SetChannel sets a channel to the store func (k Keeper) SetChannel(ctx sdk.Context, portID, channelID string, channel types.Channel) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(channel) + bz := k.cdc.MustMarshalBinaryBare(channel) store.Set(ibctypes.KeyChannel(portID, channelID), bz) } @@ -144,7 +144,7 @@ func (k Keeper) IterateChannels(ctx sdk.Context, cb func(types.IdentifiedChannel defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var channel types.Channel - k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &channel) + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &channel) portID, channelID := ibctypes.MustParseChannelPath(string(iterator.Key())) if cb(types.IdentifiedChannel{Channel: channel, PortIdentifier: portID, ChannelIdentifier: channelID}) { diff --git a/x/ibc/07-tendermint/client/rest/tx.go b/x/ibc/07-tendermint/client/rest/tx.go index 855fce80c6..942325fe85 100644 --- a/x/ibc/07-tendermint/client/rest/tx.go +++ b/x/ibc/07-tendermint/client/rest/tx.go @@ -15,7 +15,7 @@ import ( // RegisterRoutes - Central function to define routes that get registered by the main application func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { - r.HandleFunc("/ibc/clients", createClientHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc("/ibc/clients/tendermint", createClientHandlerFn(cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/ibc/clients/{%s}/update", RestClientID), updateClientHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/ibc/clients/{%s}/misbehaviour", submitMisbehaviourHandlerFn(cliCtx)).Methods("POST") } @@ -29,7 +29,7 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { // @Param body body rest.CreateClientReq true "Create client request body" // @Success 200 {object} PostCreateClient "OK" // @Failure 500 {object} rest.ErrorResponse "Internal Server Error" -// @Router /ibc/clients [post] +// @Router /ibc/clients/tendermint [post] func createClientHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req CreateClientReq diff --git a/x/ibc/07-tendermint/types/client_state.go b/x/ibc/07-tendermint/types/client_state.go index 57470717ab..b5f1f9ec1c 100644 --- a/x/ibc/07-tendermint/types/client_state.go +++ b/x/ibc/07-tendermint/types/client_state.go @@ -32,7 +32,7 @@ type ClientState struct { // Block height when the client was frozen due to a misbehaviour FrozenHeight uint64 `json:"frozen_height" yaml:"frozen_height"` // Last Header that was stored by client - LastHeader Header + LastHeader Header `json:"last_header" yaml:"last_header"` } // InitializeFromMsg creates a tendermint client state from a CreateClientMsg @@ -124,7 +124,7 @@ func (cs ClientState) VerifyClientConsensusState( return err } - bz, err := cdc.MarshalBinaryLengthPrefixed(consensusState) + bz, err := cdc.MarshalBinaryBare(consensusState) if err != nil { return err } @@ -156,7 +156,7 @@ func (cs ClientState) VerifyConnectionState( return err } - bz, err := cdc.MarshalBinaryLengthPrefixed(connectionEnd) + bz, err := cdc.MarshalBinaryBare(connectionEnd) if err != nil { return err } @@ -189,7 +189,7 @@ func (cs ClientState) VerifyChannelState( return err } - bz, err := cdc.MarshalBinaryLengthPrefixed(channel) + bz, err := cdc.MarshalBinaryBare(channel) if err != nil { return err } diff --git a/x/ibc/09-localhost/client/cli/cli.go b/x/ibc/09-localhost/client/cli/cli.go new file mode 100644 index 0000000000..c06b2f1644 --- /dev/null +++ b/x/ibc/09-localhost/client/cli/cli.go @@ -0,0 +1,25 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" +) + +// GetTxCmd returns the transaction commands for IBC +func GetTxCmd(cdc *codec.Codec, storeKey string) *cobra.Command { + ics09LocalhostTxCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "Localhost transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + ics09LocalhostTxCmd.AddCommand(flags.PostCommands( + GetCmdCreateClient(cdc), + )...) + + return ics09LocalhostTxCmd +} diff --git a/x/ibc/09-localhost/client/cli/tx.go b/x/ibc/09-localhost/client/cli/tx.go new file mode 100644 index 0000000000..83f776a98f --- /dev/null +++ b/x/ibc/09-localhost/client/cli/tx.go @@ -0,0 +1,47 @@ +package cli + +import ( + "bufio" + "fmt" + "strings" + + "github.com/spf13/cobra" + + "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" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" +) + +// 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", + Short: "create new localhost client", + Long: strings.TrimSpace(fmt.Sprintf(`create new localhost (loopback) client: + +Example: +$ %s tx ibc client localhost create --from node0 --home ../node0/cli --chain-id $CID +`, version.ClientName), + ), + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) + txBldr := authtypes.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc).WithBroadcastMode(flags.BroadcastBlock) + + msg := types.NewMsgCreateClient(cliCtx.GetFromAddress()) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + return cmd +} diff --git a/x/ibc/09-localhost/client/rest/rest.go b/x/ibc/09-localhost/client/rest/rest.go new file mode 100644 index 0000000000..4c598895bb --- /dev/null +++ b/x/ibc/09-localhost/client/rest/rest.go @@ -0,0 +1,18 @@ +package rest + +import ( + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/types/rest" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, queryRoute string) { + registerTxRoutes(cliCtx, r) +} + +// CreateClientReq defines the properties of a create client request's body. +type CreateClientReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` +} diff --git a/x/ibc/09-localhost/client/rest/tx.go b/x/ibc/09-localhost/client/rest/tx.go new file mode 100644 index 0000000000..a0d616cb01 --- /dev/null +++ b/x/ibc/09-localhost/client/rest/tx.go @@ -0,0 +1,56 @@ +package rest + +import ( + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/ibc/clients/localhost", createClientHandlerFn(cliCtx)).Methods("POST") +} + +// createClientHandlerFn implements a create client handler +// +// @Summary Create client +// @Tags IBC +// @Accept json +// @Produce json +// @Param body body rest.CreateClientReq true "Create client request body" +// @Success 200 {object} PostCreateClient "OK" +// @Failure 500 {object} rest.ErrorResponse "Internal Server Error" +// @Router /ibc/clients/localhost [post] +func createClientHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req CreateClientReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := types.NewMsgCreateClient(fromAddr) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + authclient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} diff --git a/x/ibc/09-localhost/client_state_test.go b/x/ibc/09-localhost/client_state_test.go deleted file mode 100644 index 8a2c7cc11e..0000000000 --- a/x/ibc/09-localhost/client_state_test.go +++ /dev/null @@ -1,410 +0,0 @@ -package localhost_test - -import ( - connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" - connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" - channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" - channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" - localhost "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost" - commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" -) - -const ( - testConnectionID = "connectionid" - testPortID = "testportid" - testChannelID = "testchannelid" - testSequence = 1 -) - -func (suite *LocalhostTestSuite) TestVerifyClientConsensusState() { - testCases := []struct { - name string - clientState localhost.ClientState - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - { - name: "proof verification failed", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - proof: commitmenttypes.MerkleProof{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyClientConsensusState( - suite.cdc, tc.consensusState.Root, height, "chainA", tc.consensusState.GetHeight(), tc.prefix, tc.proof, tc.consensusState, - - // suite.cdc, height, tc.prefix, tc.proof, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} - -func (suite *LocalhostTestSuite) TestVerifyConnectionState() { - counterparty := connection.NewCounterparty("clientB", testConnectionID, commitmenttypes.NewMerklePrefix([]byte("ibc"))) - conn := connection.NewConnectionEnd(connectionexported.OPEN, "clientA", counterparty, []string{"1.0.0"}) - - testCases := []struct { - name string - clientState localhost.ClientState - connection connection.ConnectionEnd - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - connection: conn, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - { - name: "proof verification failed", - clientState: localhost.NewClientState(suite.store), - connection: conn, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - proof: commitmenttypes.MerkleProof{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyConnectionState( - suite.cdc, height, tc.prefix, tc.proof, testConnectionID, tc.connection, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} - -func (suite *LocalhostTestSuite) TestVerifyChannelState() { - counterparty := channel.NewCounterparty(testPortID, testChannelID) - ch := channel.NewChannel(channelexported.OPEN, channelexported.ORDERED, counterparty, []string{testConnectionID}, "1.0.0") - - testCases := []struct { - name string - clientState localhost.ClientState - channel channel.Channel - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - channel: ch, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - { - name: "latest client height < height", - clientState: localhost.NewClientState(suite.store), - channel: ch, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "proof verification failed", - clientState: localhost.NewClientState(suite.store), - channel: ch, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - proof: commitmenttypes.MerkleProof{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyChannelState( - suite.cdc, height, tc.prefix, tc.proof, testPortID, testChannelID, tc.channel, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} - -func (suite *LocalhostTestSuite) TestVerifyPacketCommitment() { - testCases := []struct { - name string - clientState localhost.ClientState - commitment []byte - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - commitment: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - { - name: "latest client height < height", - clientState: localhost.NewClientState(suite.store), - commitment: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "client is frozen", - clientState: localhost.NewClientState(suite.store), - commitment: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "proof verification failed", - clientState: localhost.NewClientState(suite.store), - commitment: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - proof: commitmenttypes.MerkleProof{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyPacketCommitment( - height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.commitment, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} - -func (suite *LocalhostTestSuite) TestVerifyPacketAcknowledgement() { - testCases := []struct { - name string - clientState localhost.ClientState - ack []byte - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - ack: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - { - name: "latest client height < height", - clientState: localhost.NewClientState(suite.store), - ack: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "client is frozen", - clientState: localhost.NewClientState(suite.store), - ack: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "proof verification failed", - clientState: localhost.NewClientState(suite.store), - ack: []byte{}, - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - proof: commitmenttypes.MerkleProof{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyPacketAcknowledgement( - height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.ack, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} - -func (suite *LocalhostTestSuite) TestVerifyPacketAcknowledgementAbsence() { - testCases := []struct { - name string - clientState localhost.ClientState - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyPacketAcknowledgementAbsence( - height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} - -func (suite *LocalhostTestSuite) TestVerifyNextSeqRecv() { - testCases := []struct { - name string - clientState localhost.ClientState - consensusState localhost.ConsensusState - prefix commitmenttypes.MerklePrefix - proof commitmenttypes.MerkleProof - expPass bool - }{ - { - name: "ApplyPrefix failed", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.MerklePrefix{}, - expPass: false, - }, - { - name: "latest client height < height", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "client is frozen", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - expPass: false, - }, - { - name: "proof verification failed", - clientState: localhost.NewClientState(suite.store), - consensusState: localhost.ConsensusState{ - Root: commitmenttypes.NewMerkleRoot([]byte{}), - }, - prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), - proof: commitmenttypes.MerkleProof{}, - expPass: false, - }, - } - - for i, tc := range testCases { - tc := tc - - err := tc.clientState.VerifyNextSequenceRecv( - height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.consensusState, - ) - - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - } -} diff --git a/x/ibc/09-localhost/consensus_state.go b/x/ibc/09-localhost/consensus_state.go deleted file mode 100644 index bc6c8a2d23..0000000000 --- a/x/ibc/09-localhost/consensus_state.go +++ /dev/null @@ -1,31 +0,0 @@ -package localhost - -import ( - clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" - commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported" -) - -// ConsensusState defines a Localhost consensus state -type ConsensusState struct { - Root commitmentexported.Root `json:"root" yaml:"root"` -} - -// ClientType returns Localhost -func (ConsensusState) ClientType() clientexported.ClientType { - return clientexported.Localhost -} - -// GetRoot returns the commitment Root for the specific -func (cs ConsensusState) GetRoot() commitmentexported.Root { - return cs.Root -} - -// GetHeight returns the height for the specific consensus state -func (cs ConsensusState) GetHeight() uint64 { - return 0 -} - -// ValidateBasic defines a basic validation for the localhost consensus state. -func (cs ConsensusState) ValidateBasic() error { - return nil -} diff --git a/x/ibc/09-localhost/evidence.go b/x/ibc/09-localhost/evidence.go deleted file mode 100644 index d56883ee51..0000000000 --- a/x/ibc/09-localhost/evidence.go +++ /dev/null @@ -1,67 +0,0 @@ -package localhost - -import ( - yaml "gopkg.in/yaml.v2" - - "github.com/tendermint/tendermint/crypto/tmhash" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - - evidenceexported "github.com/cosmos/cosmos-sdk/x/evidence/exported" - clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" - clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" -) - -var ( - _ evidenceexported.Evidence = Evidence{} - _ clientexported.Misbehaviour = Evidence{} -) - -// Evidence is not required for a loop-back client -type Evidence struct { -} - -// ClientType is Localhost light client -func (ev Evidence) ClientType() clientexported.ClientType { - return clientexported.Localhost -} - -// GetClientID returns the ID of the client that committed a misbehaviour. -func (ev Evidence) GetClientID() string { - return clientexported.Localhost.String() -} - -// Route implements Evidence interface -func (ev Evidence) Route() string { - return clienttypes.SubModuleName -} - -// Type implements Evidence interface -func (ev Evidence) Type() string { - return "client_misbehaviour" -} - -// String implements Evidence interface -func (ev Evidence) String() string { - // FIXME: implement custom marshaller - bz, err := yaml.Marshal(ev) - if err != nil { - panic(err) - } - return string(bz) -} - -// Hash implements Evidence interface. -func (ev Evidence) Hash() tmbytes.HexBytes { - bz := SubModuleCdc.MustMarshalBinaryBare(ev) - return tmhash.Sum(bz) -} - -// GetHeight returns 0. -func (ev Evidence) GetHeight() int64 { - return 0 -} - -// ValidateBasic implements Evidence interface. -func (ev Evidence) ValidateBasic() error { - return nil -} diff --git a/x/ibc/09-localhost/header.go b/x/ibc/09-localhost/header.go deleted file mode 100644 index af3b7f1ac2..0000000000 --- a/x/ibc/09-localhost/header.go +++ /dev/null @@ -1,26 +0,0 @@ -package localhost - -import ( - clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" -) - -var _ clientexported.Header = Header{} - -// Header defines the Localhost consensus Header -type Header struct { -} - -// ClientType defines that the Header is in loop-back mode. -func (h Header) ClientType() clientexported.ClientType { - return clientexported.Localhost -} - -// ConsensusState returns an empty consensus state. -func (h Header) ConsensusState() ConsensusState { - return ConsensusState{} -} - -// GetHeight returns 0. -func (h Header) GetHeight() uint64 { - return 0 -} diff --git a/x/ibc/09-localhost/module.go b/x/ibc/09-localhost/module.go new file mode 100644 index 0000000000..c9a9d14171 --- /dev/null +++ b/x/ibc/09-localhost/module.go @@ -0,0 +1,29 @@ +package localhost + +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/09-localhost/client/cli" + "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/client/rest" + "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" +) + +// Name returns the IBC client name +func Name() string { + return types.SubModuleName +} + +// RegisterRESTRoutes registers the REST routes for the IBC localhost client +func RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router, queryRoute string) { + rest.RegisterRoutes(ctx, rtr, fmt.Sprintf("%s/%s", queryRoute, types.SubModuleName)) +} + +// GetTxCmd returns the root tx command for the IBC localhost client +func GetTxCmd(cdc *codec.Codec, storeKey string) *cobra.Command { + return cli.GetTxCmd(cdc, fmt.Sprintf("%s/%s", storeKey, types.SubModuleName)) +} diff --git a/x/ibc/09-localhost/client_state.go b/x/ibc/09-localhost/types/client_state.go similarity index 71% rename from x/ibc/09-localhost/client_state.go rename to x/ibc/09-localhost/types/client_state.go index e723eb8ca4..fa49b4c329 100644 --- a/x/ibc/09-localhost/client_state.go +++ b/x/ibc/09-localhost/types/client_state.go @@ -1,4 +1,4 @@ -package localhost +package types import ( "bytes" @@ -6,7 +6,6 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/types" 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" @@ -23,30 +22,30 @@ var _ clientexported.ClientState = ClientState{} // ClientState requires (read-only) access to keys outside the client prefix. type ClientState struct { - ctx sdk.Context - store types.KVStore + store sdk.KVStore + ID string `json:"id" yaml:"id"` + ChainID string `json:"chain_id" yaml:"chain_id"` + Height int64 `json:"height" yaml:"height"` } // NewClientState creates a new ClientState instance -func NewClientState(store types.KVStore) ClientState { +func NewClientState(store sdk.KVStore, chainID string, height int64) ClientState { return ClientState{ - store: store, + store: store, + ID: clientexported.Localhost.String(), + ChainID: chainID, + Height: height, } } -// WithContext updates the client state context to provide the chain ID and latest height -func (cs *ClientState) WithContext(ctx sdk.Context) { - cs.ctx = ctx -} - // GetID returns the loop-back client state identifier. func (cs ClientState) GetID() string { - return clientexported.Localhost.String() + return cs.ID } // GetChainID returns an empty string func (cs ClientState) GetChainID() string { - return cs.ctx.ChainID() + return cs.ChainID } // ClientType is localhost. @@ -54,9 +53,9 @@ func (cs ClientState) ClientType() clientexported.ClientType { return clientexported.Localhost } -// GetLatestHeight returns the block height from the stored context. +// GetLatestHeight returns the latest height stored. func (cs ClientState) GetLatestHeight() uint64 { - return uint64(cs.ctx.BlockHeight()) + return uint64(cs.Height) } // IsFrozen returns false. @@ -85,13 +84,19 @@ func (cs ClientState) VerifyClientConsensusState( data := cs.store.Get([]byte(path.String())) if len(data) == 0 { - return sdkerrors.Wrap(clienttypes.ErrFailedClientConsensusStateVerification, "not found") + return sdkerrors.Wrapf(clienttypes.ErrFailedClientConsensusStateVerification, "not found for path %s", path) } var prevConsensusState exported.ConsensusState - cdc.MustUnmarshalBinaryBare(data, &prevConsensusState) + if err := cdc.UnmarshalBinaryBare(data, &prevConsensusState); err != nil { + return err + } + if consensusState != prevConsensusState { - return sdkerrors.Wrap(clienttypes.ErrFailedClientConsensusStateVerification, "not equal") + return sdkerrors.Wrapf( + clienttypes.ErrFailedClientConsensusStateVerification, + "consensus state ≠ previous stored consensus state: \n%v\n≠\n%v", consensusState, prevConsensusState, + ) } return nil @@ -115,13 +120,19 @@ func (cs ClientState) VerifyConnectionState( bz := cs.store.Get([]byte(path.String())) if bz == nil { - return sdkerrors.Wrap(clienttypes.ErrFailedConnectionStateVerification, "not found") + return sdkerrors.Wrapf(clienttypes.ErrFailedConnectionStateVerification, "not found for path %s", path) } - var prevConnectionState connectionexported.ConnectionI - cdc.MustUnmarshalBinaryBare(bz, &prevConnectionState) - if connectionEnd != prevConnectionState { - return sdkerrors.Wrap(clienttypes.ErrFailedConnectionStateVerification, "not equal") + var prevConnection connectionexported.ConnectionI + if err := cdc.UnmarshalBinaryBare(bz, &prevConnection); err != nil { + return err + } + + if connectionEnd != prevConnection { + return sdkerrors.Wrapf( + clienttypes.ErrFailedConnectionStateVerification, + "connection end ≠ previous stored connection: \n%v\n≠\n%v", connectionEnd, prevConnection, + ) } return nil @@ -146,13 +157,18 @@ func (cs ClientState) VerifyChannelState( bz := cs.store.Get([]byte(path.String())) if bz == nil { - return sdkerrors.Wrap(clienttypes.ErrFailedChannelStateVerification, "not found") + return sdkerrors.Wrapf(clienttypes.ErrFailedChannelStateVerification, "not found for path %s", path) } - var prevChannelState channelexported.ChannelI - cdc.MustUnmarshalBinaryBare(bz, &prevChannelState) - if channel != prevChannelState { - return sdkerrors.Wrap(clienttypes.ErrFailedChannelStateVerification, "not equal") + var prevChannel channelexported.ChannelI + if err := cdc.UnmarshalBinaryBare(bz, &prevChannel); err != nil { + return err + } + if channel != prevChannel { + return sdkerrors.Wrapf( + clienttypes.ErrFailedChannelStateVerification, + "channel end ≠ previous stored channel: \n%v\n≠\n%v", channel, prevChannel, + ) } return nil @@ -177,11 +193,14 @@ func (cs ClientState) VerifyPacketCommitment( data := cs.store.Get([]byte(path.String())) if len(data) == 0 { - return sdkerrors.Wrap(clienttypes.ErrFailedPacketCommitmentVerification, "not found") + return sdkerrors.Wrapf(clienttypes.ErrFailedPacketCommitmentVerification, "not found for path %s", path) } if !bytes.Equal(data, commitmentBytes) { - return sdkerrors.Wrap(clienttypes.ErrFailedPacketCommitmentVerification, "not equal") + return sdkerrors.Wrapf( + clienttypes.ErrFailedPacketCommitmentVerification, + "commitment ≠ previous commitment: \n%X\n≠\n%X", commitmentBytes, data, + ) } return nil @@ -206,11 +225,14 @@ func (cs ClientState) VerifyPacketAcknowledgement( data := cs.store.Get([]byte(path.String())) if len(data) == 0 { - return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckVerification, "not found") + return sdkerrors.Wrapf(clienttypes.ErrFailedPacketAckVerification, "not found for path %s", path) } if !bytes.Equal(data, acknowledgement) { - return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckVerification, "not equal") + return sdkerrors.Wrapf( + clienttypes.ErrFailedPacketAckVerification, + "ak bytes ≠ previous ack: \n%X\n≠\n%X", acknowledgement, data, + ) } return nil @@ -259,12 +281,15 @@ func (cs ClientState) VerifyNextSequenceRecv( data := cs.store.Get([]byte(path.String())) if len(data) == 0 { - return sdkerrors.Wrap(clienttypes.ErrFailedNextSeqRecvVerification, "not found") + return sdkerrors.Wrapf(clienttypes.ErrFailedNextSeqRecvVerification, "not found for path %s", path) } prevSequenceRecv := binary.BigEndian.Uint64(data) if prevSequenceRecv != nextSequenceRecv { - return sdkerrors.Wrap(clienttypes.ErrFailedNextSeqRecvVerification, "not equal") + return sdkerrors.Wrapf( + clienttypes.ErrFailedNextSeqRecvVerification, + "next sequence receive ≠ previous stored sequence (%d ≠ %d)", nextSequenceRecv, prevSequenceRecv, + ) } return nil diff --git a/x/ibc/09-localhost/types/client_state_test.go b/x/ibc/09-localhost/types/client_state_test.go new file mode 100644 index 0000000000..dddc244ee7 --- /dev/null +++ b/x/ibc/09-localhost/types/client_state_test.go @@ -0,0 +1,343 @@ +package types_test + +import ( + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" + commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" +) + +const ( + testConnectionID = "connectionid" + testPortID = "testportid" + testChannelID = "testchannelid" + testSequence = 1 +) + +func (suite *LocalhostTestSuite) TestVerifyClientConsensusState() { + testCases := []struct { + name string + clientState types.ClientState + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: commitmenttypes.MerkleProof{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyClientConsensusState( + suite.cdc, nil, height, "chainA", 0, tc.prefix, tc.proof, nil, + + // suite.cdc, height, tc.prefix, tc.proof, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *LocalhostTestSuite) TestVerifyConnectionState() { + counterparty := connection.NewCounterparty("clientB", testConnectionID, commitmenttypes.NewMerklePrefix([]byte("ibc"))) + conn := connection.NewConnectionEnd(connectionexported.OPEN, "clientA", counterparty, []string{"1.0.0"}) + + testCases := []struct { + name string + clientState types.ClientState + connection connection.ConnectionEnd + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + connection: conn, + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + connection: conn, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: commitmenttypes.MerkleProof{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyConnectionState( + suite.cdc, height, tc.prefix, tc.proof, testConnectionID, tc.connection, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *LocalhostTestSuite) TestVerifyChannelState() { + counterparty := channel.NewCounterparty(testPortID, testChannelID) + ch := channel.NewChannel(channelexported.OPEN, channelexported.ORDERED, counterparty, []string{testConnectionID}, "1.0.0") + + testCases := []struct { + name string + clientState types.ClientState + channel channel.Channel + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + channel: ch, + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "latest client height < height", + clientState: types.NewClientState(suite.store, "chainID", 10), + channel: ch, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + channel: ch, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: commitmenttypes.MerkleProof{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyChannelState( + suite.cdc, height, tc.prefix, tc.proof, testPortID, testChannelID, tc.channel, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *LocalhostTestSuite) TestVerifyPacketCommitment() { + testCases := []struct { + name string + clientState types.ClientState + commitment []byte + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + commitment: []byte{}, + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "latest client height < height", + clientState: types.NewClientState(suite.store, "chainID", 10), + commitment: []byte{}, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "client is frozen", + clientState: types.NewClientState(suite.store, "chainID", 10), + commitment: []byte{}, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + commitment: []byte{}, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: commitmenttypes.MerkleProof{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyPacketCommitment( + height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.commitment, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *LocalhostTestSuite) TestVerifyPacketAcknowledgement() { + testCases := []struct { + name string + clientState types.ClientState + ack []byte + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + ack: []byte{}, + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "latest client height < height", + clientState: types.NewClientState(suite.store, "chainID", 10), + ack: []byte{}, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "client is frozen", + clientState: types.NewClientState(suite.store, "chainID", 10), + ack: []byte{}, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + ack: []byte{}, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: commitmenttypes.MerkleProof{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyPacketAcknowledgement( + height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, tc.ack, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *LocalhostTestSuite) TestVerifyPacketAcknowledgementAbsence() { + testCases := []struct { + name string + clientState types.ClientState + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyPacketAcknowledgementAbsence( + height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *LocalhostTestSuite) TestVerifyNextSeqRecv() { + testCases := []struct { + name string + clientState types.ClientState + prefix commitmenttypes.MerklePrefix + proof commitmenttypes.MerkleProof + expPass bool + }{ + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "latest client height < height", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "client is frozen", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(suite.store, "chainID", 10), + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: commitmenttypes.MerkleProof{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyNextSequenceRecv( + height, tc.prefix, tc.proof, testPortID, testChannelID, testSequence, nil, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + } +} diff --git a/x/ibc/09-localhost/codec.go b/x/ibc/09-localhost/types/codec.go similarity index 65% rename from x/ibc/09-localhost/codec.go rename to x/ibc/09-localhost/types/codec.go index a187144839..3022deb06b 100644 --- a/x/ibc/09-localhost/codec.go +++ b/x/ibc/09-localhost/types/codec.go @@ -1,19 +1,20 @@ -package localhost +package types import ( "github.com/cosmos/cosmos-sdk/codec" ) +const ( + // SubModuleName for the localhost (loopback) client + SubModuleName = "localhost" +) + // SubModuleCdc defines the IBC localhost client codec. var SubModuleCdc *codec.Codec // RegisterCodec registers the localhost types func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(ClientState{}, "ibc/client/localhost/ClientState", nil) - cdc.RegisterConcrete(ConsensusState{}, "ibc/client/localhost/ConsensusState", nil) - cdc.RegisterConcrete(Header{}, "ibc/client/localhost/Header", nil) - cdc.RegisterConcrete(Evidence{}, "ibc/client/localhost/Evidence", nil) - SetSubModuleCodec(cdc) } diff --git a/x/ibc/09-localhost/localhost_test.go b/x/ibc/09-localhost/types/localhost_test.go similarity index 96% rename from x/ibc/09-localhost/localhost_test.go rename to x/ibc/09-localhost/types/localhost_test.go index 5c74475c5a..621a64992d 100644 --- a/x/ibc/09-localhost/localhost_test.go +++ b/x/ibc/09-localhost/types/localhost_test.go @@ -1,4 +1,4 @@ -package localhost_test +package types_test import ( "testing" diff --git a/x/ibc/09-localhost/types/msgs.go b/x/ibc/09-localhost/types/msgs.go new file mode 100644 index 0000000000..508751a93e --- /dev/null +++ b/x/ibc/09-localhost/types/msgs.go @@ -0,0 +1,72 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// Message types for the IBC client +const ( + TypeMsgCreateClient string = "create_client" +) + +var ( + _ clientexported.MsgCreateClient = MsgCreateClient{} +) + +// MsgCreateClient defines a message to create an IBC client +type MsgCreateClient struct { + Signer sdk.AccAddress `json:"address" yaml:"address"` +} + +// NewMsgCreateClient creates a new MsgCreateClient instance +func NewMsgCreateClient(signer sdk.AccAddress) MsgCreateClient { + return MsgCreateClient{ + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgCreateClient) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgCreateClient) Type() string { + return TypeMsgCreateClient +} + +// ValidateBasic implements sdk.Msg +func (msg MsgCreateClient) ValidateBasic() error { + if msg.Signer.Empty() { + return sdkerrors.ErrInvalidAddress + } + 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} +} + +// GetClientID implements clientexported.MsgCreateClient +func (msg MsgCreateClient) GetClientID() string { + return clientexported.ClientTypeLocalHost +} + +// GetClientType implements clientexported.MsgCreateClient +func (msg MsgCreateClient) GetClientType() string { + return clientexported.ClientTypeLocalHost +} + +// GetConsensusState implements clientexported.MsgCreateClient +func (msg MsgCreateClient) GetConsensusState() clientexported.ConsensusState { + return nil +} diff --git a/x/ibc/20-transfer/module.go b/x/ibc/20-transfer/module.go index b08785bf1a..dd0758a640 100644 --- a/x/ibc/20-transfer/module.go +++ b/x/ibc/20-transfer/module.go @@ -115,7 +115,7 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, data j var genesisState types.GenesisState cdc.MustUnmarshalJSON(data, &genesisState) - // check if the IBC transfer module account is set + // TODO: check if the IBC transfer module account is set InitGenesis(ctx, am.keeper, genesisState) return []abci.ValidatorUpdate{} } diff --git a/x/ibc/ante/ante.go b/x/ibc/ante/ante.go index 4301e95c5a..fa6da73ac6 100644 --- a/x/ibc/ante/ante.go +++ b/x/ibc/ante/ante.go @@ -30,7 +30,7 @@ func (pvr ProofVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim var err error switch msg := msg.(type) { case clientexported.MsgUpdateClient: - err = pvr.clientKeeper.UpdateClient(ctx, msg.GetClientID(), msg.GetHeader()) + _, err = pvr.clientKeeper.UpdateClient(ctx, msg.GetClientID(), msg.GetHeader()) case channel.MsgPacket: _, err = pvr.channelKeeper.RecvPacket(ctx, msg.Packet, msg.Proof, msg.ProofHeight) case channel.MsgAcknowledgement: diff --git a/x/ibc/client/cli/cli.go b/x/ibc/client/cli/cli.go index c42fbc8aa9..0ba304e8fe 100644 --- a/x/ibc/client/cli/cli.go +++ b/x/ibc/client/cli/cli.go @@ -10,6 +10,7 @@ import ( connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" tmclient "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/client/cli" + localhostclient "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/client/cli" "github.com/cosmos/cosmos-sdk/x/ibc/types" ) @@ -25,6 +26,7 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { ibcTxCmd.AddCommand(flags.PostCommands( tmclient.GetTxCmd(cdc, storeKey), + localhostclient.GetTxCmd(cdc, storeKey), connection.GetTxCmd(cdc, storeKey), channel.GetTxCmd(cdc, storeKey), )...) diff --git a/x/ibc/client/rest/rest.go b/x/ibc/client/rest/rest.go index 2e0d96e0ec..6f470ee05d 100644 --- a/x/ibc/client/rest/rest.go +++ b/x/ibc/client/rest/rest.go @@ -7,11 +7,15 @@ import ( client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + tendermint "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint" + localhost "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost" ) // RegisterRoutes - Central function to define routes that get registered by the main application func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, queryRoute string) { client.RegisterRESTRoutes(cliCtx, r, queryRoute) + tendermint.RegisterRESTRoutes(cliCtx, r, queryRoute) + localhost.RegisterRESTRoutes(cliCtx, r, queryRoute) connection.RegisterRESTRoutes(cliCtx, r, queryRoute) channel.RegisterRESTRoutes(cliCtx, r, queryRoute) } diff --git a/x/ibc/module.go b/x/ibc/module.go index a6168fba80..f1db03875e 100644 --- a/x/ibc/module.go +++ b/x/ibc/module.go @@ -16,6 +16,7 @@ import ( connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" @@ -44,6 +45,7 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { connection.RegisterCodec(cdc) channel.RegisterCodec(cdc) ibctmtypes.RegisterCodec(cdc) + localhosttypes.RegisterCodec(cdc) commitmenttypes.RegisterCodec(cdc) } @@ -119,7 +121,6 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { // InitGenesis performs genesis initialization for the ibc module. It returns // no validator updates. func (am AppModule) InitGenesis(_ sdk.Context, _ codec.JSONMarshaler, _ json.RawMessage) []abci.ValidatorUpdate { - // check if the IBC transfer module account is set return []abci.ValidatorUpdate{} } @@ -131,6 +132,7 @@ func (am AppModule) ExportGenesis(_ sdk.Context, _ codec.JSONMarshaler) json.Raw // BeginBlock returns the begin blocker for the ibc module. func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + client.BeginBlocker(ctx, am.keeper.ClientKeeper) } // EndBlock returns the end blocker for the ibc module. It returns no validator