From 614fb22de1efce86fe237c634c9b75546043fc4c Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 10 Jun 2024 10:02:15 +0200 Subject: [PATCH 01/17] Remove client state from localhost light client --- modules/core/02-client/abci.go | 8 ---- modules/core/02-client/keeper/grpc_query.go | 10 ++++- modules/core/02-client/keeper/keeper.go | 11 +++++ modules/core/02-client/keeper/keeper_test.go | 10 ++--- .../07-tendermint/migrations/migrations.go | 3 ++ .../09-localhost/client_state.go | 18 +++----- .../09-localhost/light_client_module.go | 44 +++---------------- 7 files changed, 39 insertions(+), 65 deletions(-) diff --git a/modules/core/02-client/abci.go b/modules/core/02-client/abci.go index c8d36b414ec..c7ebd8bcfd7 100644 --- a/modules/core/02-client/abci.go +++ b/modules/core/02-client/abci.go @@ -5,7 +5,6 @@ import ( "github.com/cosmos/ibc-go/v8/modules/core/02-client/keeper" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v8/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) @@ -33,11 +32,4 @@ func BeginBlocker(ctx sdk.Context, k *keeper.Keeper) { keeper.EmitUpgradeChainEvent(ctx, plan.Height) } } - - // update the localhost client with the latest block height if it is active. - if clientState, found := k.GetClientState(ctx, exported.Localhost); found { - if k.GetClientStatus(ctx, exported.LocalhostClientID) == exported.Active { - k.UpdateLocalhostClient(ctx, clientState) - } - } } diff --git a/modules/core/02-client/keeper/grpc_query.go b/modules/core/02-client/keeper/grpc_query.go index cd95a83588d..f25e9845d02 100644 --- a/modules/core/02-client/keeper/grpc_query.go +++ b/modules/core/02-client/keeper/grpc_query.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" "slices" "sort" "strings" @@ -63,7 +64,7 @@ func (k *Keeper) ClientStates(c context.Context, req *types.QueryClientStatesReq ctx := sdk.UnwrapSDKContext(c) - var clientStates types.IdentifiedClientStates + clientStates := types.IdentifiedClientStates{types.NewIdentifiedClientState(exported.LocalhostClientID, localhost.NewClientState(types.GetSelfHeight(ctx)))} store := prefix.NewStore(ctx.KVStore(k.storeKey), host.KeyClientStorePrefix) pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { @@ -93,9 +94,14 @@ func (k *Keeper) ClientStates(c context.Context, req *types.QueryClientStatesReq sort.Sort(clientStates) + pagination := &query.PageResponse{ + Total: pageRes.Total + 1, // To account for localhost client + NextKey: pageRes.NextKey, + } + return &types.QueryClientStatesResponse{ ClientStates: clientStates, - Pagination: pageRes, + Pagination: pagination, }, nil } diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index d30d50554e4..c5acc28baf4 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -109,6 +109,10 @@ func (k *Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) st // GetClientState gets a particular client from the store func (k *Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) { + if clientID == exported.LocalhostClientID { + return localhost.NewClientState(types.GetSelfHeight(ctx)), true + } + store := k.ClientStore(ctx, clientID) bz := store.Get(host.ClientStateKey()) if len(bz) == 0 { @@ -228,6 +232,9 @@ func (k *Keeper) iterateMetadata(ctx sdk.Context, cb func(clientID string, key, func (k *Keeper) GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates { var genClients types.IdentifiedClientStates k.IterateClientStates(ctx, nil, func(clientID string, cs exported.ClientState) bool { + if clientID == exported.LocalhostClientID { + return false + } genClients = append(genClients, types.NewIdentifiedClientState(clientID, cs)) return false }) @@ -355,6 +362,10 @@ func (k *Keeper) IterateClientStates(ctx sdk.Context, storePrefix []byte, cb fun store := ctx.KVStore(k.storeKey) iterator := storetypes.KVStorePrefixIterator(store, host.PrefixedClientStoreKey(storePrefix)) + if cb(exported.LocalhostClientID, localhost.NewClientState(types.GetSelfHeight(ctx))) { + return + } + defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() }) for ; iterator.Valid(); iterator.Next() { path := string(iterator.Key()) diff --git a/modules/core/02-client/keeper/keeper_test.go b/modules/core/02-client/keeper/keeper_test.go index 739d735ea54..0abf1fa0b66 100644 --- a/modules/core/02-client/keeper/keeper_test.go +++ b/modules/core/02-client/keeper/keeper_test.go @@ -26,7 +26,6 @@ import ( host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/cosmos/ibc-go/v8/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ibctesting "github.com/cosmos/ibc-go/v8/testing" "github.com/cosmos/ibc-go/v8/testing/simapp" ) @@ -143,10 +142,9 @@ func (suite *KeeperTestSuite) TestSetClientConsensusState() { func (suite *KeeperTestSuite) TestGetAllGenesisClients() { clientIDs := []string{ - exported.LocalhostClientID, testClientID2, testClientID3, testClientID, + testClientID2, testClientID3, testClientID, } expClients := []exported.ClientState{ - localhost.NewClientState(types.GetSelfHeight(suite.chainA.GetContext())), ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), @@ -315,14 +313,16 @@ func (suite *KeeperTestSuite) TestIterateClientStates() { "tendermint clientIDs", []byte(exported.Tendermint), func() []string { - return expTMClientIDs + clientIDs := []string{exported.LocalhostClientID} + return append(clientIDs, expTMClientIDs...) }, }, { "solo machine clientIDs", []byte(exported.Solomachine), func() []string { - return expSMClientIDs + clientIDs := []string{exported.LocalhostClientID} + return append(clientIDs, expSMClientIDs...) }, }, } diff --git a/modules/light-clients/07-tendermint/migrations/migrations.go b/modules/light-clients/07-tendermint/migrations/migrations.go index eb80c602a46..d68e2807a14 100644 --- a/modules/light-clients/07-tendermint/migrations/migrations.go +++ b/modules/light-clients/07-tendermint/migrations/migrations.go @@ -16,6 +16,9 @@ import ( func PruneExpiredConsensusStates(ctx sdk.Context, cdc codec.BinaryCodec, clientKeeper ClientKeeper) (int, error) { var clientIDs []string clientKeeper.IterateClientStates(ctx, []byte(exported.Tendermint), func(clientID string, _ exported.ClientState) bool { + if clientID == exported.Localhost { + return false + } clientIDs = append(clientIDs, clientID) return false }) diff --git a/modules/light-clients/09-localhost/client_state.go b/modules/light-clients/09-localhost/client_state.go index 114a6837cc4..f4bbfb1369a 100644 --- a/modules/light-clients/09-localhost/client_state.go +++ b/modules/light-clients/09-localhost/client_state.go @@ -45,12 +45,6 @@ func (ClientState) Initialize(ctx sdk.Context, cdc codec.BinaryCodec, clientStor return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "initial consensus state for localhost must be nil.") } - clientState := ClientState{ - LatestHeight: clienttypes.GetSelfHeight(ctx), - } - - clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &clientState)) - return nil } @@ -88,6 +82,11 @@ func (ClientState) VerifyMembership( return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) } + // The localhost client is stateless, so if we need to verify the localhost client state, we can just return nil to verify + if merklePath.KeyPath[1] == host.FullClientStatePath(ModuleName) { + return nil + } + // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store bz := store.Get([]byte(merklePath.KeyPath[1])) if bz == nil { @@ -153,10 +152,5 @@ func (ClientState) UpdateStateOnMisbehaviour(_ sdk.Context, _ codec.BinaryCodec, // UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState. // Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified. func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, _ exported.ClientMessage) []exported.Height { - height := clienttypes.GetSelfHeight(ctx) - cs.LatestHeight = height - - clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs)) - - return []exported.Height{height} + return []exported.Height{clienttypes.GetSelfHeight(ctx)} } diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go index 3c4570514b2..47f251c02ea 100644 --- a/modules/light-clients/09-localhost/light_client_module.go +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/cosmos/ibc-go/v8/modules/core/exported" ) @@ -44,12 +43,6 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, _, conse return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "initial consensus state for localhost must be nil.") } - clientState := ClientState{ - LatestHeight: clienttypes.GetSelfHeight(ctx), - } - - clientStore := l.storeProvider.ClientStore(ctx, exported.LocalhostClientID) - clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(l.cdc, &clientState)) return nil } @@ -74,15 +67,7 @@ func (LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID str // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { - clientStore := l.storeProvider.ClientStore(ctx, clientID) - cdc := l.cdc - - clientState, found := getClientState(clientStore, cdc) - if !found { - panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) - } - - return clientState.UpdateState(ctx, cdc, clientStore, clientMsg) + return []exported.Height{clienttypes.GetSelfHeight(ctx)} } // VerifyMembership obtains the localhost client state and calls into the clientState.VerifyMembership method. @@ -98,16 +83,11 @@ func (l LightClientModule) VerifyMembership( path exported.Path, value []byte, ) error { - clientStore := l.storeProvider.ClientStore(ctx, clientID) ibcStore := ctx.KVStore(l.key) - cdc := l.cdc - clientState, found := getClientState(clientStore, cdc) - if !found { - return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) - } + clientState := NewClientState(clienttypes.GetSelfHeight(ctx)) - return clientState.VerifyMembership(ctx, ibcStore, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, value) + return clientState.VerifyMembership(ctx, ibcStore, l.cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, value) } // VerifyNonMembership obtains the localhost client state and calls into the clientState.VerifyNonMembership method. @@ -122,16 +102,11 @@ func (l LightClientModule) VerifyNonMembership( proof []byte, path exported.Path, ) error { - clientStore := l.storeProvider.ClientStore(ctx, clientID) ibcStore := ctx.KVStore(l.key) - cdc := l.cdc - clientState, found := getClientState(clientStore, cdc) - if !found { - return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) - } + clientState := NewClientState(clienttypes.GetSelfHeight(ctx)) - return clientState.VerifyNonMembership(ctx, ibcStore, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path) + return clientState.VerifyNonMembership(ctx, ibcStore, l.cdc, height, delayTimePeriod, delayBlockPeriod, proof, path) } // Status always returns Active. The 09-localhost status cannot be changed. @@ -144,14 +119,7 @@ func (LightClientModule) Status(ctx sdk.Context, clientID string) exported.Statu // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. func (l LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { - clientStore := l.storeProvider.ClientStore(ctx, clientID) - - clientState, found := getClientState(clientStore, l.cdc) - if !found { - return clienttypes.ZeroHeight() - } - - return clientState.LatestHeight + return clienttypes.GetSelfHeight(ctx) } // TimestampAtHeight returns the current block time retrieved from the application context. The localhost client does not store consensus states and thus From 952423fcb9bf403edea687aea3b8afeb8228bab9 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 10 Jun 2024 12:17:41 +0200 Subject: [PATCH 02/17] Initial store upgrade for v9 localhost statelessness --- modules/core/02-client/keeper/grpc_query.go | 2 +- .../migrations/v9/expected_keepers.go | 11 +++ modules/core/02-client/migrations/v9/store.go | 57 +++++++++++++++ .../02-client/migrations/v9/store_test.go | 71 +++++++++++++++++++ .../09-localhost/client_state.go | 2 +- .../09-localhost/light_client_module.go | 6 +- modules/light-clients/09-localhost/store.go | 30 -------- 7 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 modules/core/02-client/migrations/v9/expected_keepers.go create mode 100644 modules/core/02-client/migrations/v9/store.go create mode 100644 modules/core/02-client/migrations/v9/store_test.go delete mode 100644 modules/light-clients/09-localhost/store.go diff --git a/modules/core/02-client/keeper/grpc_query.go b/modules/core/02-client/keeper/grpc_query.go index f25e9845d02..1707d80b004 100644 --- a/modules/core/02-client/keeper/grpc_query.go +++ b/modules/core/02-client/keeper/grpc_query.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" "slices" "sort" "strings" @@ -21,6 +20,7 @@ import ( "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/cosmos/ibc-go/v8/modules/core/exported" + localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ) var _ types.QueryServer = (*Keeper)(nil) diff --git a/modules/core/02-client/migrations/v9/expected_keepers.go b/modules/core/02-client/migrations/v9/expected_keepers.go new file mode 100644 index 00000000000..a901309935c --- /dev/null +++ b/modules/core/02-client/migrations/v9/expected_keepers.go @@ -0,0 +1,11 @@ +package v9 + +import ( + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type ClientKeeper interface { + ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore +} diff --git a/modules/core/02-client/migrations/v9/store.go b/modules/core/02-client/migrations/v9/store.go new file mode 100644 index 00000000000..6532032c430 --- /dev/null +++ b/modules/core/02-client/migrations/v9/store.go @@ -0,0 +1,57 @@ +package v9 + +import ( + "strings" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// MigrateStore performs in-place store migrations from ibc-go v8 to ibc-go v9. +// The migration includes: +// +// - Removing the localhost client state as it is now stateless +func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec, clientKeeper ClientKeeper) error { + handleLocalhostMigration(ctx, clientKeeper) + + return nil +} + +func handleLocalhostMigration(ctx sdk.Context, clientKeeper ClientKeeper) { + clientStore := clientKeeper.ClientStore(ctx, exported.LocalhostClientID) + + // delete the client state + clientStore.Delete(host.ClientStateKey()) + + removeAllClientConsensusStates(clientStore) +} + +// removeAllClientConsensusStates removes all client consensus states from the associated +// client store. +func removeAllClientConsensusStates(clientStore storetypes.KVStore) { + iterator := storetypes.KVStorePrefixIterator(clientStore, []byte(host.KeyConsensusStatePrefix)) + var heights []exported.Height + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + // key is in the format "consensusStates/" + if len(keySplit) != 2 || keySplit[0] != string(host.KeyConsensusStatePrefix) { + continue + } + + // collect consensus states to be pruned + heights = append(heights, clienttypes.MustParseHeight(keySplit[1])) + } + + // delete all consensus states + for _, height := range heights { + clientStore.Delete(host.ConsensusStateKey(height)) + } +} diff --git a/modules/core/02-client/migrations/v9/store_test.go b/modules/core/02-client/migrations/v9/store_test.go new file mode 100644 index 00000000000..fcd1c31bd14 --- /dev/null +++ b/modules/core/02-client/migrations/v9/store_test.go @@ -0,0 +1,71 @@ +package v9_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9" + "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +// numCreations is the number of clients/consensus states created for light clients clients +const numCreations = 10 + +type MigrationsV9TestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + chain *ibctesting.TestChain +} + +func (suite *MigrationsV9TestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) + + suite.chain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func TestIBCTestSuite(t *testing.T) { + testifysuite.Run(t, new(MigrationsV9TestSuite)) +} + +func (suite *MigrationsV9TestSuite) TestMigrateStore() { + suite.createLocalhostClient() + + err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().GetKey(exported.StoreKey), suite.chain.App.AppCodec(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + + suite.assertNoLocalhostClients() +} + +func (suite *MigrationsV9TestSuite) TestMigrateStoreNoLocalhost() { + err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().GetKey(exported.StoreKey), suite.chain.App.AppCodec(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + + suite.assertNoLocalhostClients() +} + +func (suite *MigrationsV9TestSuite) createLocalhostClient() { + clientStore := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chain.GetContext(), exported.LocalhostClientID) + + clientStore.Set(host.ClientStateKey(), []byte("clientState")) + + for i := 0; i < numCreations; i++ { + clientStore.Set(host.ConsensusStateKey(types.NewHeight(1, uint64(i))), []byte("consensusState")) + } +} + +func (suite *MigrationsV9TestSuite) assertNoLocalhostClients() { + for numClients := uint64(0); numClients < numCreations; numClients++ { + clientStore := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chain.GetContext(), exported.LocalhostClientID) + + suite.Require().False(clientStore.Has(host.ClientStateKey())) + + for i := uint64(0); i < numCreations; i++ { + suite.Require().False(clientStore.Has(host.ConsensusStateKey(types.NewHeight(1, i)))) + } + } +} diff --git a/modules/light-clients/09-localhost/client_state.go b/modules/light-clients/09-localhost/client_state.go index f4bbfb1369a..1e459d0deb6 100644 --- a/modules/light-clients/09-localhost/client_state.go +++ b/modules/light-clients/09-localhost/client_state.go @@ -151,6 +151,6 @@ func (ClientState) UpdateStateOnMisbehaviour(_ sdk.Context, _ codec.BinaryCodec, // UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState. // Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified. -func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, _ exported.ClientMessage) []exported.Height { +func (ClientState) UpdateState(ctx sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) []exported.Height { return []exported.Height{clienttypes.GetSelfHeight(ctx)} } diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go index 47f251c02ea..e8a520eea45 100644 --- a/modules/light-clients/09-localhost/light_client_module.go +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -38,7 +38,7 @@ func (l *LightClientModule) RegisterStoreProvider(storeProvider exported.ClientS // Initialize ensures that initial consensus state for localhost is nil. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, _, consensusStateBz []byte) error { +func (LightClientModule) Initialize(ctx sdk.Context, clientID string, _, consensusStateBz []byte) error { if len(consensusStateBz) != 0 { return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "initial consensus state for localhost must be nil.") } @@ -66,7 +66,7 @@ func (LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID str // UpdateState obtains the localhost client state and calls into the clientState.UpdateState method. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { +func (LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { return []exported.Height{clienttypes.GetSelfHeight(ctx)} } @@ -118,7 +118,7 @@ func (LightClientModule) Status(ctx sdk.Context, clientID string) exported.Statu // If no client is present for the provided client identifier a zero value height is returned. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (l LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { +func (LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { return clienttypes.GetSelfHeight(ctx) } diff --git a/modules/light-clients/09-localhost/store.go b/modules/light-clients/09-localhost/store.go deleted file mode 100644 index c80b371bc22..00000000000 --- a/modules/light-clients/09-localhost/store.go +++ /dev/null @@ -1,30 +0,0 @@ -package localhost - -import ( - "fmt" - - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" -) - -// getClientState retrieves the client state from the store using the provided KVStore and codec. -// It returns the unmarshaled ClientState and a boolean indicating if the state was found. -func getClientState(store storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, bool) { - bz := store.Get(host.ClientStateKey()) - if len(bz) == 0 { - return nil, false - } - - clientStateI := clienttypes.MustUnmarshalClientState(cdc, bz) - var clientState *ClientState - clientState, ok := clientStateI.(*ClientState) - if !ok { - panic(fmt.Errorf("cannot convert %T into %T", clientStateI, clientState)) - } - - return clientState, true -} From fe6519fd6b2500cd181d5e4a6631bea43c0e408f Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 21 Jun 2024 16:44:07 +0200 Subject: [PATCH 03/17] Remove localhost creation and self state proof --- .../02-localhost/03-client-state.md | 17 +++------ modules/core/02-client/genesis.go | 6 ---- modules/core/02-client/keeper/keeper.go | 10 ------ .../migrations/v7/expected_keepers.go | 1 - .../core/02-client/migrations/v7/localhost.go | 2 +- .../02-client/migrations/v7/localhost_test.go | 10 +++--- .../09-localhost/client_state.go | 6 ---- .../09-localhost/light_client_module.go | 10 ++---- .../09-localhost/light_client_module_test.go | 36 +++++++++---------- 9 files changed, 32 insertions(+), 66 deletions(-) diff --git a/docs/docs/03-light-clients/02-localhost/03-client-state.md b/docs/docs/03-light-clients/02-localhost/03-client-state.md index a242a2b649c..684b76b033d 100644 --- a/docs/docs/03-light-clients/02-localhost/03-client-state.md +++ b/docs/docs/03-light-clients/02-localhost/03-client-state.md @@ -8,6 +8,9 @@ slug: /ibc/light-clients/localhost/client-state # `ClientState` +Even though the 09-localhost light client is a stateless client, it still has the concept of a `ClientState` that follows the +blockchains own latest height automatically. + The 09-localhost `ClientState` maintains a single field used to track the latest sequence of the state machine i.e. the height of the blockchain. ```go @@ -17,19 +20,7 @@ type ClientState struct { } ``` -The 09-localhost `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC. -It calls `CreateLocalhostClient`, declaring a new `ClientState` and initializing it with its own client prefixed store. - -```go -func (k Keeper) CreateLocalhostClient(ctx sdk.Context) error { - clientModule, found := k.router.GetRoute(exported.LocalhostClientID) - if !found { - return errorsmod.Wrap(types.ErrRouteNotFound, exported.LocalhostClientID) - } - - return clientModule.Initialize(ctx, exported.LocalhostClientID, nil, nil) -} -``` +The 09-localhost `ClientState` is always available from the 02-client submodule in core IBC, and does not need to be initialized. It is possible to disable the localhost client by removing the `09-localhost` entry from the `allowed_clients` list through governance. diff --git a/modules/core/02-client/genesis.go b/modules/core/02-client/genesis.go index d89cc5073ed..1e72b363554 100644 --- a/modules/core/02-client/genesis.go +++ b/modules/core/02-client/genesis.go @@ -50,12 +50,6 @@ func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) { } k.SetNextClientSequence(ctx, gs.NextClientSequence) - - // if the localhost already exists in state (included in the genesis file), - // it must be overwritten to ensure its stored height equals the context block height - if err := k.CreateLocalhostClient(ctx); err != nil { - panic(fmt.Errorf("failed to initialise localhost client: %s", err.Error())) - } } // ExportGenesis returns the ibc client submodule's exported genesis. diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index c5acc28baf4..7658b67e590 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -68,16 +68,6 @@ func (k *Keeper) Route(clientID string) (exported.LightClientModule, bool) { return k.router.GetRoute(clientID) } -// CreateLocalhostClient initialises the 09-localhost client state and sets it in state. -func (k *Keeper) CreateLocalhostClient(ctx sdk.Context) error { - clientModule, found := k.router.GetRoute(exported.LocalhostClientID) - if !found { - return errorsmod.Wrap(types.ErrRouteNotFound, exported.LocalhostClientID) - } - - return clientModule.Initialize(ctx, exported.LocalhostClientID, nil, nil) -} - // UpdateLocalhostClient updates the 09-localhost client to the latest block height and chain ID. func (k *Keeper) UpdateLocalhostClient(ctx sdk.Context, clientState exported.ClientState) []exported.Height { clientModule, found := k.router.GetRoute(exported.LocalhostClientID) diff --git a/modules/core/02-client/migrations/v7/expected_keepers.go b/modules/core/02-client/migrations/v7/expected_keepers.go index 4a73d42d20f..0485b120558 100644 --- a/modules/core/02-client/migrations/v7/expected_keepers.go +++ b/modules/core/02-client/migrations/v7/expected_keepers.go @@ -13,5 +13,4 @@ type ClientKeeper interface { GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore - CreateLocalhostClient(ctx sdk.Context) error } diff --git a/modules/core/02-client/migrations/v7/localhost.go b/modules/core/02-client/migrations/v7/localhost.go index 49709b9db9f..781686a6c69 100644 --- a/modules/core/02-client/migrations/v7/localhost.go +++ b/modules/core/02-client/migrations/v7/localhost.go @@ -6,5 +6,5 @@ import ( // MigrateLocalhostClient initialises the 09-localhost client state and sets it in state. func MigrateLocalhostClient(ctx sdk.Context, clientKeeper ClientKeeper) error { - return clientKeeper.CreateLocalhostClient(ctx) + return nil } diff --git a/modules/core/02-client/migrations/v7/localhost_test.go b/modules/core/02-client/migrations/v7/localhost_test.go index 1a6d657477c..22fa9bcbf0e 100644 --- a/modules/core/02-client/migrations/v7/localhost_test.go +++ b/modules/core/02-client/migrations/v7/localhost_test.go @@ -13,14 +13,16 @@ func (suite *MigrationsV7TestSuite) TestMigrateLocalhostClient() { clientStore := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.LocalhostClientID) clientStore.Delete(host.ClientStateKey()) + // Deleting the client state should not have any effect as the localhost client is stateless. clientState, found := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), exported.LocalhostClientID) - suite.Require().False(found) - suite.Require().Nil(clientState) + suite.Require().True(found) + suite.Require().NotNil(clientState) err := v7.MigrateLocalhostClient(suite.chainA.GetContext(), suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper) suite.Require().NoError(err) + // After the migration, it should still be there. No change is expected. clientState, found = suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), exported.LocalhostClientID) - suite.Require().True(found) - suite.Require().NotNil(clientState) + suite.Require().False(found) + suite.Require().Nil(clientState) } diff --git a/modules/light-clients/09-localhost/client_state.go b/modules/light-clients/09-localhost/client_state.go index 1e459d0deb6..dc4572dbdfc 100644 --- a/modules/light-clients/09-localhost/client_state.go +++ b/modules/light-clients/09-localhost/client_state.go @@ -82,12 +82,6 @@ func (ClientState) VerifyMembership( return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) } - // The localhost client is stateless, so if we need to verify the localhost client state, we can just return nil to verify - if merklePath.KeyPath[1] == host.FullClientStatePath(ModuleName) { - return nil - } - - // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store bz := store.Get([]byte(merklePath.KeyPath[1])) if bz == nil { return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value not found for path %s", path) diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go index e8a520eea45..1467ae64d63 100644 --- a/modules/light-clients/09-localhost/light_client_module.go +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -35,15 +35,11 @@ func (l *LightClientModule) RegisterStoreProvider(storeProvider exported.ClientS l.storeProvider = storeProvider } -// Initialize ensures that initial consensus state for localhost is nil. +// Initialize returns an error because it is stateless // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (LightClientModule) Initialize(ctx sdk.Context, clientID string, _, consensusStateBz []byte) error { - if len(consensusStateBz) != 0 { - return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "initial consensus state for localhost must be nil.") - } - - return nil +func (LightClientModule) Initialize(ctx sdk.Context, clientID string, clientState, consensusStateBz []byte) error { + return errorsmod.Wrap(clienttypes.ErrClientExists, "localhost is stateless and cannot be initialized") } // VerifyClientMessage is unsupported by the 09-localhost client type and returns an error. diff --git a/modules/light-clients/09-localhost/light_client_module_test.go b/modules/light-clients/09-localhost/light_client_module_test.go index 8ea737a1cd9..d924a53befb 100644 --- a/modules/light-clients/09-localhost/light_client_module_test.go +++ b/modules/light-clients/09-localhost/light_client_module_test.go @@ -31,20 +31,6 @@ func (suite *LocalhostTestSuite) TestVerifyMembership() { malleate func() expPass bool }{ - { - "success: client state verification", - func() { - clientState := suite.chain.GetClientState(exported.LocalhostClientID) - - merklePath := commitmenttypes.NewMerklePath(host.FullClientStatePath(exported.LocalhostClientID)) - merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) - suite.Require().NoError(err) - - path = merklePath - value = clienttypes.MustMarshalClientState(suite.chain.Codec, clientState) - }, - true, - }, { "success: connection state verification", func() { @@ -144,21 +130,35 @@ func (suite *LocalhostTestSuite) TestVerifyMembership() { true, }, { - "invalid type for key path", + "failure: stateless client state verification", + func() { + clientState := suite.chain.GetClientState(exported.LocalhostClientID) + + merklePath := commitmenttypes.NewMerklePath(host.FullClientStatePath(exported.LocalhostClientID)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = clienttypes.MustMarshalClientState(suite.chain.Codec, clientState) + }, + false, + }, + { + "failure: invalid type for key path", func() { path = mock.KeyPath{} }, false, }, { - "key path has too many elements", + "failure: key path has too many elements", func() { path = commitmenttypes.NewMerklePath("ibc", "test", "key") }, false, }, { - "no value found at provided key path", + "failure: no value found at provided key path", func() { merklePath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(mock.PortID, ibctesting.FirstChannelID, 100)) merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) @@ -170,7 +170,7 @@ func (suite *LocalhostTestSuite) TestVerifyMembership() { false, }, { - "invalid value, bytes are not equal", + "failure: invalid value, bytes are not equal", func() { channel := channeltypes.NewChannel( channeltypes.OPEN, From 249518b079d3d01c511ed4beb3e13cb35c1c9ad6 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 21 Jun 2024 18:02:26 +0200 Subject: [PATCH 04/17] Wire up migrations --- modules/core/02-client/keeper/migrations.go | 5 +++++ modules/core/02-client/migrations/v7/localhost_test.go | 6 +++--- modules/core/02-client/migrations/v9/store.go | 3 +-- modules/core/02-client/migrations/v9/store_test.go | 4 ++-- modules/core/module.go | 9 ++++++--- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index cefdd5da0fc..40d27e1d825 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + v9 "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9" "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -47,3 +48,7 @@ func (m Migrator) MigrateParams(ctx sdk.Context) error { m.keeper.Logger(ctx).Info("successfully migrated client to self-manage params") return nil } + +func (m Migrator) Migrate6to7(ctx sdk.Context) error { + return v9.MigrateStore(ctx, m.keeper) +} diff --git a/modules/core/02-client/migrations/v7/localhost_test.go b/modules/core/02-client/migrations/v7/localhost_test.go index 22fa9bcbf0e..29ca8de35dc 100644 --- a/modules/core/02-client/migrations/v7/localhost_test.go +++ b/modules/core/02-client/migrations/v7/localhost_test.go @@ -21,8 +21,8 @@ func (suite *MigrationsV7TestSuite) TestMigrateLocalhostClient() { err := v7.MigrateLocalhostClient(suite.chainA.GetContext(), suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper) suite.Require().NoError(err) - // After the migration, it should still be there. No change is expected. + // After the migration, it should still return a value. No change is expected. clientState, found = suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), exported.LocalhostClientID) - suite.Require().False(found) - suite.Require().Nil(clientState) + suite.Require().True(found) + suite.Require().NotNil(clientState) } diff --git a/modules/core/02-client/migrations/v9/store.go b/modules/core/02-client/migrations/v9/store.go index 6532032c430..6c3346fc34a 100644 --- a/modules/core/02-client/migrations/v9/store.go +++ b/modules/core/02-client/migrations/v9/store.go @@ -5,7 +5,6 @@ import ( storetypes "cosmossdk.io/store/types" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -17,7 +16,7 @@ import ( // The migration includes: // // - Removing the localhost client state as it is now stateless -func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec, clientKeeper ClientKeeper) error { +func MigrateStore(ctx sdk.Context, clientKeeper ClientKeeper) error { handleLocalhostMigration(ctx, clientKeeper) return nil diff --git a/modules/core/02-client/migrations/v9/store_test.go b/modules/core/02-client/migrations/v9/store_test.go index fcd1c31bd14..76261afc8cb 100644 --- a/modules/core/02-client/migrations/v9/store_test.go +++ b/modules/core/02-client/migrations/v9/store_test.go @@ -35,14 +35,14 @@ func TestIBCTestSuite(t *testing.T) { func (suite *MigrationsV9TestSuite) TestMigrateStore() { suite.createLocalhostClient() - err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().GetKey(exported.StoreKey), suite.chain.App.AppCodec(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) + err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) suite.Require().NoError(err) suite.assertNoLocalhostClients() } func (suite *MigrationsV9TestSuite) TestMigrateStoreNoLocalhost() { - err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().GetKey(exported.StoreKey), suite.chain.App.AppCodec(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) + err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) suite.Require().NoError(err) suite.assertNoLocalhostClients() diff --git a/modules/core/module.go b/modules/core/module.go index e7f6ebd4a94..6a34b9cd559 100644 --- a/modules/core/module.go +++ b/modules/core/module.go @@ -160,8 +160,11 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { } channelMigrator := channelkeeper.NewMigrator(am.keeper.ChannelKeeper) - err := cfg.RegisterMigration(exported.ModuleName, 5, channelMigrator.MigrateParams) - if err != nil { + if err := cfg.RegisterMigration(exported.ModuleName, 5, channelMigrator.MigrateParams); err != nil { + panic(err) + } + + if err := cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.Migrate6to7); err != nil { panic(err) } } @@ -184,7 +187,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements AppModule/ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 6 } +func (AppModule) ConsensusVersion() uint64 { return 7 } // BeginBlock returns the begin blocker for the ibc module. func (am AppModule) BeginBlock(ctx context.Context) error { From 251825d0adeeac7e87d0294fd33bbb47338e436e Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 24 Jun 2024 10:24:39 +0200 Subject: [PATCH 05/17] Update docs for 09-localhost for 09-localhost --- .../02-localhost/01-overview.md | 44 ++++++++++++------- .../02-localhost/03-client-state.md | 36 ++------------- .../02-localhost/05-state-verification.md | 4 ++ docs/docs/05-migrations/13-v8-to-v9.md | 7 +++ e2e/tests/core/02-client/client_test.go | 4 ++ 5 files changed, 46 insertions(+), 49 deletions(-) diff --git a/docs/docs/03-light-clients/02-localhost/01-overview.md b/docs/docs/03-light-clients/02-localhost/01-overview.md index d32ccb01684..708bf043798 100644 --- a/docs/docs/03-light-clients/02-localhost/01-overview.md +++ b/docs/docs/03-light-clients/02-localhost/01-overview.md @@ -5,7 +5,6 @@ sidebar_position: 1 slug: /ibc/light-clients/localhost/overview --- - # `09-localhost` ## Overview @@ -14,33 +13,44 @@ slug: /ibc/light-clients/localhost/overview Learn about the 09-localhost light client module. ::: -The 09-localhost light client module implements a localhost loopback client with the ability to send and receive IBC packets to and from the same state machine. +The 09-localhost light client module implements a stateless localhost loopback client with the ability to send and +receive IBC packets to and from the same state machine. ### Context -In a multichain environment, application developers will be used to developing cross-chain applications through IBC. From their point of view, whether or not they are interacting with multiple modules on the same chain or on different chains should not matter. The localhost client module enables a unified interface to interact with different applications on a single chain, using the familiar IBC application layer semantics. +In a multichain environment, application developers will be used to developing cross-chain applications through IBC. +From their point of view, whether or not they are interacting with multiple modules on the same chain or on different +chains should not matter. The localhost client module enables a unified interface to interact with different +applications on a single chain, using the familiar IBC application layer semantics. ### Implementation -There exists a [single sentinel `ClientState`](03-client-state.md) instance with the client identifier `09-localhost`. +There exists a [single sentinel `ClientState`](03-client-state.md) with the client identifier `09-localhost`. The light +client is stateless, so the `ClientState` is constructed on demand when required. -To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel connection which leverage the 09-localhost loopback functionality. +To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection +identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel +connection which leverage the 09-localhost loopback functionality. -[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. +[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in +complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. ### Localhost vs *regular* client -The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). +The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC +application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of +differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). The table below lists some important differences: -| | Regular client | Localhost | -| -------------------------------------------- | --------------------------------------------------------------------------- | --------- | -| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost`| -| Client creation | Relayer (permissionless) | `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC | -| Client updates | Relayer submits headers using `MsgUpdateClient` | Latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC | -| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | -| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | -| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | -| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | -| Integration | Expected to register codec types using the `AppModuleBasic` interface | Registers codec types within the core IBC module | +| | Regular client | Localhost | +|----------------------------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost` | +| Client creation | Relayer (permissionless) | Implicitly made available by the 02-client submodule in core IBC | +| Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the `ClientState` is constructed with the latest height when queried | +| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | +| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | +| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | +| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | +| Integration | Expected to register codec types using the `AppModuleBasic` interface | Registers codec types within the core IBC module | +| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` | \ No newline at end of file diff --git a/docs/docs/03-light-clients/02-localhost/03-client-state.md b/docs/docs/03-light-clients/02-localhost/03-client-state.md index 684b76b033d..c68553bba84 100644 --- a/docs/docs/03-light-clients/02-localhost/03-client-state.md +++ b/docs/docs/03-light-clients/02-localhost/03-client-state.md @@ -9,7 +9,7 @@ slug: /ibc/light-clients/localhost/client-state # `ClientState` Even though the 09-localhost light client is a stateless client, it still has the concept of a `ClientState` that follows the -blockchains own latest height automatically. +blockchains own latest height automatically. The `ClientState` is constructed on demand when required. The 09-localhost `ClientState` maintains a single field used to track the latest sequence of the state machine i.e. the height of the blockchain. @@ -20,40 +20,12 @@ type ClientState struct { } ``` -The 09-localhost `ClientState` is always available from the 02-client submodule in core IBC, and does not need to be initialized. +The 09-localhost `ClientState` is available from the 02-client submodule in core IBC, and does not need to be initialized. It is possible to disable the localhost client by removing the `09-localhost` entry from the `allowed_clients` list through governance. ## Client updates -The latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC. +The 09-localhost `ClientState` is stateless, so no client updates are required. The `ClientState` is constructed with the latest height when queried. +It will always follow latest height of the blockchain. -[See `BeginBlocker` in abci.go.](https://github.com/cosmos/ibc-go/blob/v8.1.1/modules/core/02-client/abci.go#L12) - -```go -func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { - // ... - - if clientState, found := k.GetClientState(ctx, exported.Localhost); found { - if k.GetClientStatus(ctx, clientState, exported.Localhost) == exported.Active { - k.UpdateLocalhostClient(ctx, clientState) - } - } -} -``` - -The above calls into the 09-localhost `UpdateState` method of the `ClientState` . -It retrieves the current block height from the application context and sets the `LatestHeight` of the 09-localhost client. - -```go -func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) []exported.Height { - height := clienttypes.GetSelfHeight(ctx) - cs.LatestHeight = height - - clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs)) - - return []exported.Height{height} -} -``` - -Note that the 09-localhost `ClientState` is not updated through the 02-client interface leveraged by conventional IBC light clients. diff --git a/docs/docs/03-light-clients/02-localhost/05-state-verification.md b/docs/docs/03-light-clients/02-localhost/05-state-verification.md index eb92f2899e9..19d3f1f177a 100644 --- a/docs/docs/03-light-clients/02-localhost/05-state-verification.md +++ b/docs/docs/03-light-clients/02-localhost/05-state-verification.md @@ -20,3 +20,7 @@ The 09-localhost light client module defines a `SentinelProof` as a single byte. ```go var SentinelProof = []byte{0x01} ``` + +The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`. +Instead, the `ClientState` is constructed on demand when required. + diff --git a/docs/docs/05-migrations/13-v8-to-v9.md b/docs/docs/05-migrations/13-v8-to-v9.md index ebeb98b477b..5314b5b8122 100644 --- a/docs/docs/05-migrations/13-v8-to-v9.md +++ b/docs/docs/05-migrations/13-v8-to-v9.md @@ -141,3 +141,10 @@ The `IterateConsensusMetadata` function has been removed. The `ExportMetadataMsg` struct has been removed and is no longer required for contracts to implement. Core IBC will handle exporting all key/value's written to the store by a light client contract. The `ZeroCustomFields` interface function has been removed from the `ClientState` interface. Core IBC only used this function to set tendermint client states when scheduling an IBC software upgrade. The interface function has been replaced by a type assertion. + +### 09-localhost + +The `09-localhost` light client has been made stateless and will no longer updating the client on every block. The `ClientState` is constructed on demand when required. +The `ClientState` itself is therefore no longer provable direcly with `VerifyMembership` or `VerifyNonMembership`. + +Previously stored client state date is pruned automatically on IBC module store migration from `ConsensusVersion` 6 to 7. \ No newline at end of file diff --git a/e2e/tests/core/02-client/client_test.go b/e2e/tests/core/02-client/client_test.go index cb1da13cc7c..ae041a3b4cb 100644 --- a/e2e/tests/core/02-client/client_test.go +++ b/e2e/tests/core/02-client/client_test.go @@ -487,6 +487,10 @@ func (s *ClientTestSuite) TestAllowedClientsParam() { status, err := query.ClientStatus(ctx, chainA, ibctesting.FirstClientID) s.Require().NoError(err) s.Require().Equal(ibcexported.Unauthorized.String(), status) + + status, err = query.ClientStatus(ctx, chainA, ibcexported.Localhost) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Unauthorized.String(), status) }) } From bb92f2244b248e1cbd9f9fa235e7140460839b1b Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 24 Jun 2024 12:06:47 +0200 Subject: [PATCH 06/17] lint --- docs/docs/03-light-clients/02-localhost/01-overview.md | 1 + modules/core/02-client/keeper/migrations.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/03-light-clients/02-localhost/01-overview.md b/docs/docs/03-light-clients/02-localhost/01-overview.md index 708bf043798..6cddc5a5803 100644 --- a/docs/docs/03-light-clients/02-localhost/01-overview.md +++ b/docs/docs/03-light-clients/02-localhost/01-overview.md @@ -5,6 +5,7 @@ sidebar_position: 1 slug: /ibc/light-clients/localhost/overview --- + # `09-localhost` ## Overview diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index 40d27e1d825..2b10ffbec04 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -2,9 +2,9 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - v9 "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9" "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" + "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" ) From 84c32360f37abac28cec68400563b0927969b2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:51:29 +0200 Subject: [PATCH 07/17] refactor: simplify migrations, remove unnecessary iteration over client state --- modules/core/02-client/keeper/grpc_query.go | 10 +-- .../core/02-client/keeper/grpc_query_test.go | 20 +----- modules/core/02-client/keeper/keeper.go | 7 -- modules/core/02-client/keeper/keeper_test.go | 11 +-- modules/core/02-client/keeper/migrations.go | 13 ++-- .../core/02-client/keeper/migrations_test.go | 18 +++++ .../migrations/v9/expected_keepers.go | 11 --- modules/core/02-client/migrations/v9/store.go | 56 --------------- .../02-client/migrations/v9/store_test.go | 71 ------------------- modules/core/module.go | 4 +- .../07-tendermint/migrations/migrations.go | 5 +- .../09-localhost/client_state.go | 1 + 12 files changed, 37 insertions(+), 190 deletions(-) delete mode 100644 modules/core/02-client/migrations/v9/expected_keepers.go delete mode 100644 modules/core/02-client/migrations/v9/store.go delete mode 100644 modules/core/02-client/migrations/v9/store_test.go diff --git a/modules/core/02-client/keeper/grpc_query.go b/modules/core/02-client/keeper/grpc_query.go index 1707d80b004..cd95a83588d 100644 --- a/modules/core/02-client/keeper/grpc_query.go +++ b/modules/core/02-client/keeper/grpc_query.go @@ -20,7 +20,6 @@ import ( "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/cosmos/ibc-go/v8/modules/core/exported" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ) var _ types.QueryServer = (*Keeper)(nil) @@ -64,7 +63,7 @@ func (k *Keeper) ClientStates(c context.Context, req *types.QueryClientStatesReq ctx := sdk.UnwrapSDKContext(c) - clientStates := types.IdentifiedClientStates{types.NewIdentifiedClientState(exported.LocalhostClientID, localhost.NewClientState(types.GetSelfHeight(ctx)))} + var clientStates types.IdentifiedClientStates store := prefix.NewStore(ctx.KVStore(k.storeKey), host.KeyClientStorePrefix) pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { @@ -94,14 +93,9 @@ func (k *Keeper) ClientStates(c context.Context, req *types.QueryClientStatesReq sort.Sort(clientStates) - pagination := &query.PageResponse{ - Total: pageRes.Total + 1, // To account for localhost client - NextKey: pageRes.NextKey, - } - return &types.QueryClientStatesResponse{ ClientStates: clientStates, - Pagination: pagination, + Pagination: pageRes, }, nil } diff --git a/modules/core/02-client/keeper/grpc_query_test.go b/modules/core/02-client/keeper/grpc_query_test.go index 2a916d89c8b..3d70e1b312d 100644 --- a/modules/core/02-client/keeper/grpc_query_test.go +++ b/modules/core/02-client/keeper/grpc_query_test.go @@ -119,26 +119,11 @@ func (suite *KeeperTestSuite) TestQueryClientStates() { { "empty pagination", func() { - localhost := types.NewIdentifiedClientState(exported.LocalhostClientID, suite.chainA.GetClientState(exported.LocalhostClientID)) - expClientStates = types.IdentifiedClientStates{localhost} + expClientStates = nil req = &types.QueryClientStatesRequest{} }, true, }, - { - "success, only localhost", - func() { - localhost := types.NewIdentifiedClientState(exported.LocalhostClientID, suite.chainA.GetClientState(exported.LocalhostClientID)) - expClientStates = types.IdentifiedClientStates{localhost} - req = &types.QueryClientStatesRequest{ - Pagination: &query.PageRequest{ - Limit: 3, - CountTotal: true, - }, - } - }, - true, - }, { "success", func() { @@ -151,12 +136,11 @@ func (suite *KeeperTestSuite) TestQueryClientStates() { clientStateA1 := path1.EndpointA.GetClientState() clientStateA2 := path2.EndpointA.GetClientState() - localhost := types.NewIdentifiedClientState(exported.LocalhostClientID, suite.chainA.GetClientState(exported.LocalhostClientID)) idcs := types.NewIdentifiedClientState(path1.EndpointA.ClientID, clientStateA1) idcs2 := types.NewIdentifiedClientState(path2.EndpointA.ClientID, clientStateA2) // order is sorted by client id - expClientStates = types.IdentifiedClientStates{localhost, idcs, idcs2}.Sort() + expClientStates = types.IdentifiedClientStates{idcs, idcs2}.Sort() req = &types.QueryClientStatesRequest{ Pagination: &query.PageRequest{ Limit: 20, diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 7658b67e590..ecdfd75e82e 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -222,9 +222,6 @@ func (k *Keeper) iterateMetadata(ctx sdk.Context, cb func(clientID string, key, func (k *Keeper) GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates { var genClients types.IdentifiedClientStates k.IterateClientStates(ctx, nil, func(clientID string, cs exported.ClientState) bool { - if clientID == exported.LocalhostClientID { - return false - } genClients = append(genClients, types.NewIdentifiedClientState(clientID, cs)) return false }) @@ -352,10 +349,6 @@ func (k *Keeper) IterateClientStates(ctx sdk.Context, storePrefix []byte, cb fun store := ctx.KVStore(k.storeKey) iterator := storetypes.KVStorePrefixIterator(store, host.PrefixedClientStoreKey(storePrefix)) - if cb(exported.LocalhostClientID, localhost.NewClientState(types.GetSelfHeight(ctx))) { - return - } - defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() }) for ; iterator.Valid(); iterator.Next() { path := string(iterator.Key()) diff --git a/modules/core/02-client/keeper/keeper_test.go b/modules/core/02-client/keeper/keeper_test.go index 0abf1fa0b66..3d8eb4c3a90 100644 --- a/modules/core/02-client/keeper/keeper_test.go +++ b/modules/core/02-client/keeper/keeper_test.go @@ -303,26 +303,21 @@ func (suite *KeeperTestSuite) TestIterateClientStates() { "all clientIDs", nil, func() []string { - allClientIDs := []string{exported.LocalhostClientID} - allClientIDs = append(allClientIDs, expSMClientIDs...) - allClientIDs = append(allClientIDs, expTMClientIDs...) - return allClientIDs + return append(expSMClientIDs, expTMClientIDs...) }, }, { "tendermint clientIDs", []byte(exported.Tendermint), func() []string { - clientIDs := []string{exported.LocalhostClientID} - return append(clientIDs, expTMClientIDs...) + return expTMClientIDs }, }, { "solo machine clientIDs", []byte(exported.Solomachine), func() []string { - clientIDs := []string{exported.LocalhostClientID} - return append(clientIDs, expSMClientIDs...) + return expSMClientIDs }, }, } diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index 2b10ffbec04..3c08534960f 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -3,9 +3,10 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" - "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9" + v7 "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" ) // Migrator is a struct for handling in-place store migrations. @@ -49,6 +50,10 @@ func (m Migrator) MigrateParams(ctx sdk.Context) error { return nil } -func (m Migrator) Migrate6to7(ctx sdk.Context) error { - return v9.MigrateStore(ctx, m.keeper) +func (m Migrator) MigrateStatelessLocalhost(ctx sdk.Context) error { + clientStore := m.keeper.ClientStore(ctx, exported.LocalhostClientID) + + // delete the client state + clientStore.Delete(host.ClientStateKey()) + return nil } diff --git a/modules/core/02-client/keeper/migrations_test.go b/modules/core/02-client/keeper/migrations_test.go index 7bdd26b8a12..739363b774d 100644 --- a/modules/core/02-client/keeper/migrations_test.go +++ b/modules/core/02-client/keeper/migrations_test.go @@ -3,6 +3,8 @@ package keeper_test import ( "github.com/cosmos/ibc-go/v8/modules/core/02-client/keeper" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ) @@ -41,3 +43,19 @@ func (suite *KeeperTestSuite) TestMigrateParams() { }) } } + +func (suite *KeeperTestSuite) TestMigrateStatelessLocalhost() { + // set localhost in state + clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.LocalhostClientID) + clientStore.Set(host.ClientStateKey(), []byte("clientState")) + + m := keeper.NewMigrator(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + err := m.MigrateStatelessLocalhost(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().False(clientStore.Has(host.ClientStateKey())) + + // rerun migration on no localhost set + err = m.MigrateStatelessLocalhost(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().False(clientStore.Has(host.ClientStateKey())) +} diff --git a/modules/core/02-client/migrations/v9/expected_keepers.go b/modules/core/02-client/migrations/v9/expected_keepers.go deleted file mode 100644 index a901309935c..00000000000 --- a/modules/core/02-client/migrations/v9/expected_keepers.go +++ /dev/null @@ -1,11 +0,0 @@ -package v9 - -import ( - storetypes "cosmossdk.io/store/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type ClientKeeper interface { - ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore -} diff --git a/modules/core/02-client/migrations/v9/store.go b/modules/core/02-client/migrations/v9/store.go deleted file mode 100644 index 6c3346fc34a..00000000000 --- a/modules/core/02-client/migrations/v9/store.go +++ /dev/null @@ -1,56 +0,0 @@ -package v9 - -import ( - "strings" - - storetypes "cosmossdk.io/store/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -// MigrateStore performs in-place store migrations from ibc-go v8 to ibc-go v9. -// The migration includes: -// -// - Removing the localhost client state as it is now stateless -func MigrateStore(ctx sdk.Context, clientKeeper ClientKeeper) error { - handleLocalhostMigration(ctx, clientKeeper) - - return nil -} - -func handleLocalhostMigration(ctx sdk.Context, clientKeeper ClientKeeper) { - clientStore := clientKeeper.ClientStore(ctx, exported.LocalhostClientID) - - // delete the client state - clientStore.Delete(host.ClientStateKey()) - - removeAllClientConsensusStates(clientStore) -} - -// removeAllClientConsensusStates removes all client consensus states from the associated -// client store. -func removeAllClientConsensusStates(clientStore storetypes.KVStore) { - iterator := storetypes.KVStorePrefixIterator(clientStore, []byte(host.KeyConsensusStatePrefix)) - var heights []exported.Height - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - keySplit := strings.Split(string(iterator.Key()), "/") - // key is in the format "consensusStates/" - if len(keySplit) != 2 || keySplit[0] != string(host.KeyConsensusStatePrefix) { - continue - } - - // collect consensus states to be pruned - heights = append(heights, clienttypes.MustParseHeight(keySplit[1])) - } - - // delete all consensus states - for _, height := range heights { - clientStore.Delete(host.ConsensusStateKey(height)) - } -} diff --git a/modules/core/02-client/migrations/v9/store_test.go b/modules/core/02-client/migrations/v9/store_test.go deleted file mode 100644 index 76261afc8cb..00000000000 --- a/modules/core/02-client/migrations/v9/store_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package v9_test - -import ( - "testing" - - testifysuite "github.com/stretchr/testify/suite" - - "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9" - "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" - ibctesting "github.com/cosmos/ibc-go/v8/testing" -) - -// numCreations is the number of clients/consensus states created for light clients clients -const numCreations = 10 - -type MigrationsV9TestSuite struct { - testifysuite.Suite - - coordinator *ibctesting.Coordinator - chain *ibctesting.TestChain -} - -func (suite *MigrationsV9TestSuite) SetupTest() { - suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) - - suite.chain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) -} - -func TestIBCTestSuite(t *testing.T) { - testifysuite.Run(t, new(MigrationsV9TestSuite)) -} - -func (suite *MigrationsV9TestSuite) TestMigrateStore() { - suite.createLocalhostClient() - - err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) - suite.Require().NoError(err) - - suite.assertNoLocalhostClients() -} - -func (suite *MigrationsV9TestSuite) TestMigrateStoreNoLocalhost() { - err := v9.MigrateStore(suite.chain.GetContext(), suite.chain.GetSimApp().IBCKeeper.ClientKeeper) - suite.Require().NoError(err) - - suite.assertNoLocalhostClients() -} - -func (suite *MigrationsV9TestSuite) createLocalhostClient() { - clientStore := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chain.GetContext(), exported.LocalhostClientID) - - clientStore.Set(host.ClientStateKey(), []byte("clientState")) - - for i := 0; i < numCreations; i++ { - clientStore.Set(host.ConsensusStateKey(types.NewHeight(1, uint64(i))), []byte("consensusState")) - } -} - -func (suite *MigrationsV9TestSuite) assertNoLocalhostClients() { - for numClients := uint64(0); numClients < numCreations; numClients++ { - clientStore := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chain.GetContext(), exported.LocalhostClientID) - - suite.Require().False(clientStore.Has(host.ClientStateKey())) - - for i := uint64(0); i < numCreations; i++ { - suite.Require().False(clientStore.Has(host.ConsensusStateKey(types.NewHeight(1, i)))) - } - } -} diff --git a/modules/core/module.go b/modules/core/module.go index 6a34b9cd559..10ea6d4ef06 100644 --- a/modules/core/module.go +++ b/modules/core/module.go @@ -164,9 +164,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { panic(err) } - if err := cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.Migrate6to7); err != nil { - panic(err) - } + cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.MigrateStatelessLocalhost) } // InitGenesis performs genesis initialization for the ibc module. It returns diff --git a/modules/light-clients/07-tendermint/migrations/migrations.go b/modules/light-clients/07-tendermint/migrations/migrations.go index d68e2807a14..c6c05b2c624 100644 --- a/modules/light-clients/07-tendermint/migrations/migrations.go +++ b/modules/light-clients/07-tendermint/migrations/migrations.go @@ -16,9 +16,6 @@ import ( func PruneExpiredConsensusStates(ctx sdk.Context, cdc codec.BinaryCodec, clientKeeper ClientKeeper) (int, error) { var clientIDs []string clientKeeper.IterateClientStates(ctx, []byte(exported.Tendermint), func(clientID string, _ exported.ClientState) bool { - if clientID == exported.Localhost { - return false - } clientIDs = append(clientIDs, clientID) return false }) @@ -37,7 +34,7 @@ func PruneExpiredConsensusStates(ctx sdk.Context, cdc codec.BinaryCodec, clientK tmClientState, ok := clientState.(*ibctm.ClientState) if !ok { - return 0, errorsmod.Wrap(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint") + return 0, errorsmod.Wrapf(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint, %s", clientID) } totalPruned += ibctm.PruneAllExpiredConsensusStates(ctx, clientStore, cdc, tmClientState) diff --git a/modules/light-clients/09-localhost/client_state.go b/modules/light-clients/09-localhost/client_state.go index dc4572dbdfc..c185a87b01f 100644 --- a/modules/light-clients/09-localhost/client_state.go +++ b/modules/light-clients/09-localhost/client_state.go @@ -82,6 +82,7 @@ func (ClientState) VerifyMembership( return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) } + // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store bz := store.Get([]byte(merklePath.KeyPath[1])) if bz == nil { return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value not found for path %s", path) From ec2571062c0fe0d0ad0d82743537a5dee7db8767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:06:45 +0200 Subject: [PATCH 08/17] refactor: remove localhost client state from queries --- modules/core/02-client/keeper/keeper.go | 4 --- modules/core/02-client/keeper/migrations.go | 3 +- .../core/02-client/migrations/v7/localhost.go | 10 ------- .../02-client/migrations/v7/localhost_test.go | 28 ------------------- .../07-tendermint/migrations/migrations.go | 2 +- .../09-localhost/client_state_test.go | 4 --- .../09-localhost/light_client_module_test.go | 2 +- 7 files changed, 4 insertions(+), 49 deletions(-) delete mode 100644 modules/core/02-client/migrations/v7/localhost.go delete mode 100644 modules/core/02-client/migrations/v7/localhost_test.go diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index ecdfd75e82e..cb3097dbb89 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -99,10 +99,6 @@ func (k *Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) st // GetClientState gets a particular client from the store func (k *Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) { - if clientID == exported.LocalhostClientID { - return localhost.NewClientState(types.GetSelfHeight(ctx)), true - } - store := k.ClientStore(ctx, clientID) bz := store.Get(host.ClientStateKey()) if len(bz) == 0 { diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index 3c08534960f..e1f92eba3f1 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -32,7 +32,8 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error { // Migrate3to4 migrates from consensus version 3 to 4. // This migration enables the localhost client. func (m Migrator) Migrate3to4(ctx sdk.Context) error { - return v7.MigrateLocalhostClient(ctx, m.keeper) + // localhost is now stateless, no changes necessary + return nil } // MigrateParams migrates from consensus version 4 to 5. diff --git a/modules/core/02-client/migrations/v7/localhost.go b/modules/core/02-client/migrations/v7/localhost.go deleted file mode 100644 index 781686a6c69..00000000000 --- a/modules/core/02-client/migrations/v7/localhost.go +++ /dev/null @@ -1,10 +0,0 @@ -package v7 - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// MigrateLocalhostClient initialises the 09-localhost client state and sets it in state. -func MigrateLocalhostClient(ctx sdk.Context, clientKeeper ClientKeeper) error { - return nil -} diff --git a/modules/core/02-client/migrations/v7/localhost_test.go b/modules/core/02-client/migrations/v7/localhost_test.go deleted file mode 100644 index 29ca8de35dc..00000000000 --- a/modules/core/02-client/migrations/v7/localhost_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package v7_test - -import ( - "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -func (suite *MigrationsV7TestSuite) TestMigrateLocalhostClient() { - suite.SetupTest() - - // note: explicitly remove the localhost client before running migration handler - clientStore := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.LocalhostClientID) - clientStore.Delete(host.ClientStateKey()) - - // Deleting the client state should not have any effect as the localhost client is stateless. - clientState, found := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), exported.LocalhostClientID) - suite.Require().True(found) - suite.Require().NotNil(clientState) - - err := v7.MigrateLocalhostClient(suite.chainA.GetContext(), suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper) - suite.Require().NoError(err) - - // After the migration, it should still return a value. No change is expected. - clientState, found = suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), exported.LocalhostClientID) - suite.Require().True(found) - suite.Require().NotNil(clientState) -} diff --git a/modules/light-clients/07-tendermint/migrations/migrations.go b/modules/light-clients/07-tendermint/migrations/migrations.go index c6c05b2c624..eb80c602a46 100644 --- a/modules/light-clients/07-tendermint/migrations/migrations.go +++ b/modules/light-clients/07-tendermint/migrations/migrations.go @@ -34,7 +34,7 @@ func PruneExpiredConsensusStates(ctx sdk.Context, cdc codec.BinaryCodec, clientK tmClientState, ok := clientState.(*ibctm.ClientState) if !ok { - return 0, errorsmod.Wrapf(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint, %s", clientID) + return 0, errorsmod.Wrap(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint") } totalPruned += ibctm.PruneAllExpiredConsensusStates(ctx, clientStore, cdc, tmClientState) diff --git a/modules/light-clients/09-localhost/client_state_test.go b/modules/light-clients/09-localhost/client_state_test.go index 2eac5411d97..6dab2663d28 100644 --- a/modules/light-clients/09-localhost/client_state_test.go +++ b/modules/light-clients/09-localhost/client_state_test.go @@ -116,8 +116,4 @@ func (suite *LocalhostTestSuite) TestUpdateState() { expHeight := clienttypes.NewHeight(1, uint64(suite.chain.GetContext().BlockHeight())) suite.Require().True(heights[0].EQ(expHeight)) - var ok bool - clientState, ok = suite.chain.GetClientState(exported.LocalhostClientID).(*localhost.ClientState) - suite.Require().True(ok) - suite.Require().True(heights[0].EQ(clientState.LatestHeight)) } diff --git a/modules/light-clients/09-localhost/light_client_module_test.go b/modules/light-clients/09-localhost/light_client_module_test.go index d924a53befb..e9019341864 100644 --- a/modules/light-clients/09-localhost/light_client_module_test.go +++ b/modules/light-clients/09-localhost/light_client_module_test.go @@ -132,7 +132,7 @@ func (suite *LocalhostTestSuite) TestVerifyMembership() { { "failure: stateless client state verification", func() { - clientState := suite.chain.GetClientState(exported.LocalhostClientID) + clientState := localhost.NewClientState(clienttypes.GetSelfHeight(suite.chain.GetContext())) merklePath := commitmenttypes.NewMerklePath(host.FullClientStatePath(exported.LocalhostClientID)) merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) From c207393dbf5a56eb5cc7248231c67b1d6d6086de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:46:13 +0200 Subject: [PATCH 09/17] refactor: remove localhost client state, condense localhost impl into single file --- e2e/testsuite/codec.go | 2 - modules/core/02-client/keeper/client_test.go | 6 +- modules/core/types/codec.go | 2 - .../08-wasm/keeper/contract_keeper_test.go | 22 +- .../08-wasm/keeper/msg_server_test.go | 5 +- .../09-localhost/client_state.go | 151 -------- .../09-localhost/client_state_test.go | 119 ------- modules/light-clients/09-localhost/codec.go | 16 - .../light-clients/09-localhost/codec_test.go | 49 --- modules/light-clients/09-localhost/keys.go | 12 - .../09-localhost/light_client_module.go | 63 +++- .../09-localhost/light_client_module_test.go | 81 ++++- .../09-localhost/localhost.pb.go | 322 ------------------ .../09-localhost/localhost_test.go | 25 -- .../lightclients/localhost/v2/localhost.proto | 16 - 15 files changed, 128 insertions(+), 763 deletions(-) delete mode 100644 modules/light-clients/09-localhost/client_state.go delete mode 100644 modules/light-clients/09-localhost/client_state_test.go delete mode 100644 modules/light-clients/09-localhost/codec.go delete mode 100644 modules/light-clients/09-localhost/codec_test.go delete mode 100644 modules/light-clients/09-localhost/keys.go delete mode 100644 modules/light-clients/09-localhost/localhost.pb.go delete mode 100644 modules/light-clients/09-localhost/localhost_test.go delete mode 100644 proto/ibc/lightclients/localhost/v2/localhost.proto diff --git a/e2e/testsuite/codec.go b/e2e/testsuite/codec.go index 42f790a78e6..5fabcf48671 100644 --- a/e2e/testsuite/codec.go +++ b/e2e/testsuite/codec.go @@ -34,7 +34,6 @@ import ( channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ibctesting "github.com/cosmos/ibc-go/v8/testing" simappparams "github.com/cosmos/ibc-go/v8/testing/simapp/params" ) @@ -76,7 +75,6 @@ func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) { channeltypes.RegisterInterfaces(cfg.InterfaceRegistry) connectiontypes.RegisterInterfaces(cfg.InterfaceRegistry) ibctmtypes.RegisterInterfaces(cfg.InterfaceRegistry) - localhost.RegisterInterfaces(cfg.InterfaceRegistry) wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry) // all other types diff --git a/modules/core/02-client/keeper/client_test.go b/modules/core/02-client/keeper/client_test.go index 51939164938..275567c195f 100644 --- a/modules/core/02-client/keeper/client_test.go +++ b/modules/core/02-client/keeper/client_test.go @@ -14,7 +14,6 @@ import ( "github.com/cosmos/ibc-go/v8/modules/core/exported" solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ibctesting "github.com/cosmos/ibc-go/v8/testing" ) @@ -64,10 +63,7 @@ func (suite *KeeperTestSuite) TestCreateClient() { }, { "failure: 09-localhost client type not supported", - func() { - lhClientState := localhost.NewClientState(clienttypes.GetSelfHeight(suite.chainA.GetContext())) - clientState = suite.chainA.App.AppCodec().MustMarshal(lhClientState) - }, + func() {}, exported.Localhost, false, }, diff --git a/modules/core/types/codec.go b/modules/core/types/codec.go index 57b4018adb8..1216ab22864 100644 --- a/modules/core/types/codec.go +++ b/modules/core/types/codec.go @@ -7,7 +7,6 @@ import ( connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ) // RegisterInterfaces registers ibc types against interfaces using the global InterfaceRegistry. @@ -17,5 +16,4 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { connectiontypes.RegisterInterfaces(registry) channeltypes.RegisterInterfaces(registry) commitmenttypes.RegisterInterfaces(registry) - localhost.RegisterInterfaces(registry) } diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go index b7b59bbcdec..35517fa1536 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go @@ -12,7 +12,6 @@ import ( host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/cosmos/ibc-go/v8/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ) func (suite *KeeperTestSuite) TestWasmInstantiate() { @@ -105,8 +104,7 @@ func (suite *KeeperTestSuite) TestWasmInstantiate() { "failure: change clientstate type", func() { suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + store.Set(host.ClientStateKey(), []byte("changed client state")) data, err := json.Marshal(types.EmptyResult{}) suite.Require().NoError(err) @@ -269,8 +267,7 @@ func (suite *KeeperTestSuite) TestWasmMigrate() { "failure: change clientstate type", func() { suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + store.Set(host.ClientStateKey(), []byte("changed client state")) data, err := json.Marshal(types.EmptyResult{}) suite.Require().NoError(err) @@ -486,21 +483,6 @@ func (suite *KeeperTestSuite) TestWasmSudo() { }, types.ErrWasmAttributesNotAllowed, }, - { - "failure: invalid clientstate type", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidContractModification, - }, { "failure: unmarshallable clientstate bytes", func() { diff --git a/modules/light-clients/08-wasm/keeper/msg_server_test.go b/modules/light-clients/08-wasm/keeper/msg_server_test.go index cb17191c69f..def0351b2a3 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server_test.go +++ b/modules/light-clients/08-wasm/keeper/msg_server_test.go @@ -17,7 +17,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" ibctesting "github.com/cosmos/ibc-go/v8/testing" ) @@ -249,9 +248,7 @@ func (suite *KeeperTestSuite) TestMsgMigrateContract() { suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { // the checksum written in here will be overwritten - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + store.Set(host.ClientStateKey(), []byte("changed client state")) data, err := json.Marshal(types.EmptyResult{}) suite.Require().NoError(err) diff --git a/modules/light-clients/09-localhost/client_state.go b/modules/light-clients/09-localhost/client_state.go deleted file mode 100644 index c185a87b01f..00000000000 --- a/modules/light-clients/09-localhost/client_state.go +++ /dev/null @@ -1,151 +0,0 @@ -package localhost - -import ( - "bytes" - - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -var _ exported.ClientState = (*ClientState)(nil) - -// NewClientState creates a new 09-localhost ClientState instance. -func NewClientState(height clienttypes.Height) *ClientState { - return &ClientState{ - LatestHeight: height, - } -} - -// ClientType returns the 09-localhost client type. -func (ClientState) ClientType() string { - return exported.Localhost -} - -// Validate performs a basic validation of the client state fields. -func (cs ClientState) Validate() error { - if cs.LatestHeight.RevisionHeight == 0 { - return errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "local revision height cannot be zero") - } - - return nil -} - -// Initialize ensures that initial consensus state for localhost is nil. -func (ClientState) Initialize(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, consState exported.ConsensusState) error { - if consState != nil { - return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "initial consensus state for localhost must be nil.") - } - - return nil -} - -// GetTimestampAtHeight returns the current block time retrieved from the application context. The localhost client does not store consensus states and thus -// cannot provide a timestamp for the provided height. -func (ClientState) GetTimestampAtHeight(ctx sdk.Context, _ storetypes.KVStore, _ codec.BinaryCodec, _ exported.Height) (uint64, error) { - return uint64(ctx.BlockTime().UnixNano()), nil -} - -// VerifyMembership is a generic proof verification method which verifies the existence of a given key and value within the IBC store. -// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). -// The caller must provide the full IBC store. -func (ClientState) VerifyMembership( - ctx sdk.Context, - store storetypes.KVStore, - _ codec.BinaryCodec, - _ exported.Height, - _ uint64, - _ uint64, - proof []byte, - path exported.Path, - value []byte, -) error { - // ensure the proof provided is the expected sentinel localhost client proof - if !bytes.Equal(proof, SentinelProof) { - return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "expected %s, got %s", string(SentinelProof), string(proof)) - } - - merklePath, ok := path.(commitmenttypes.MerklePath) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) - } - - if len(merklePath.GetKeyPath()) != 2 { - return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) - } - - // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store - bz := store.Get([]byte(merklePath.KeyPath[1])) - if bz == nil { - return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value not found for path %s", path) - } - - if !bytes.Equal(bz, value) { - return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value provided does not equal value stored at path: %s", path) - } - - return nil -} - -// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath within the IBC store. -// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). -// The caller must provide the full IBC store. -func (ClientState) VerifyNonMembership( - ctx sdk.Context, - store storetypes.KVStore, - _ codec.BinaryCodec, - _ exported.Height, - _ uint64, - _ uint64, - proof []byte, - path exported.Path, -) error { - // ensure the proof provided is the expected sentinel localhost client proof - if !bytes.Equal(proof, SentinelProof) { - return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "expected %s, got %s", string(SentinelProof), string(proof)) - } - - merklePath, ok := path.(commitmenttypes.MerklePath) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) - } - - if len(merklePath.GetKeyPath()) != 2 { - return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) - } - - // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store - if store.Has([]byte(merklePath.KeyPath[1])) { - return errorsmod.Wrapf(clienttypes.ErrFailedNonMembershipVerification, "value found for path %s", path) - } - - return nil -} - -// VerifyClientMessage is unsupported by the 09-localhost client type and returns an error. -func (ClientState) VerifyClientMessage(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) error { - return errorsmod.Wrap(clienttypes.ErrUpdateClientFailed, "client message verification is unsupported by the localhost client") -} - -// CheckForMisbehaviour is unsupported by the 09-localhost client type and performs a no-op, returning false. -func (ClientState) CheckForMisbehaviour(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) bool { - return false -} - -// UpdateStateOnMisbehaviour is unsupported by the 09-localhost client type and performs a no-op. -func (ClientState) UpdateStateOnMisbehaviour(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) { -} - -// UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState. -// Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified. -func (ClientState) UpdateState(ctx sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) []exported.Height { - return []exported.Height{clienttypes.GetSelfHeight(ctx)} -} diff --git a/modules/light-clients/09-localhost/client_state_test.go b/modules/light-clients/09-localhost/client_state_test.go deleted file mode 100644 index 6dab2663d28..00000000000 --- a/modules/light-clients/09-localhost/client_state_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package localhost_test - -import ( - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v8/modules/core/exported" - ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" -) - -func (suite *LocalhostTestSuite) TestClientType() { - clientState := localhost.NewClientState(clienttypes.NewHeight(3, 10)) - suite.Require().Equal(exported.Localhost, clientState.ClientType()) -} - -func (suite *LocalhostTestSuite) TestGetLatestHeight() { - expectedHeight := clienttypes.NewHeight(3, 10) - clientState := localhost.NewClientState(expectedHeight) - suite.Require().Equal(expectedHeight, clientState.LatestHeight) -} - -func (suite *LocalhostTestSuite) TestGetTimestampAtHeight() { - ctx := suite.chain.GetContext() - clientState := localhost.NewClientState(clienttypes.NewHeight(1, 10)) - - timestamp, err := clientState.GetTimestampAtHeight(ctx, nil, nil, nil) - suite.Require().NoError(err) - suite.Require().Equal(uint64(ctx.BlockTime().UnixNano()), timestamp) -} - -func (suite *LocalhostTestSuite) TestValidate() { - testCases := []struct { - name string - clientState exported.ClientState - expPass bool - }{ - { - name: "valid client", - clientState: localhost.NewClientState(clienttypes.NewHeight(3, 10)), - expPass: true, - }, - { - name: "invalid height", - clientState: localhost.NewClientState(clienttypes.ZeroHeight()), - expPass: false, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - err := tc.clientState.Validate() - if tc.expPass { - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Error(err, tc.name) - } - }) - } -} - -func (suite *LocalhostTestSuite) TestInitialize() { - testCases := []struct { - name string - consState exported.ConsensusState - expPass bool - }{ - { - "valid initialization", - nil, - true, - }, - { - "invalid consenus state", - &ibctm.ConsensusState{}, - false, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - clientState := localhost.NewClientState(clienttypes.NewHeight(3, 10)) - clientStore := suite.chain.GetSimApp().GetIBCKeeper().ClientKeeper.ClientStore(suite.chain.GetContext(), exported.LocalhostClientID) - - err := clientState.Initialize(suite.chain.GetContext(), suite.chain.Codec, clientStore, tc.consState) - - if tc.expPass { - suite.Require().NoError(err, "valid testcase: %s failed", tc.name) - } else { - suite.Require().Error(err, "invalid testcase: %s passed", tc.name) - } - }) - suite.SetupTest() - - } -} - -func (suite *LocalhostTestSuite) TestVerifyClientMessage() { - clientState := localhost.NewClientState(clienttypes.NewHeight(1, 10)) - suite.Require().Error(clientState.VerifyClientMessage(suite.chain.GetContext(), nil, nil, nil)) -} - -func (suite *LocalhostTestSuite) TestVerifyCheckForMisbehaviour() { - clientState := localhost.NewClientState(clienttypes.NewHeight(1, 10)) - suite.Require().False(clientState.CheckForMisbehaviour(suite.chain.GetContext(), nil, nil, nil)) -} - -func (suite *LocalhostTestSuite) TestUpdateState() { - clientState := localhost.NewClientState(clienttypes.NewHeight(1, uint64(suite.chain.GetContext().BlockHeight()))) - store := suite.chain.GetSimApp().GetIBCKeeper().ClientKeeper.ClientStore(suite.chain.GetContext(), exported.LocalhostClientID) - - suite.coordinator.CommitBlock(suite.chain) - - heights := clientState.UpdateState(suite.chain.GetContext(), suite.chain.Codec, store, nil) - - expHeight := clienttypes.NewHeight(1, uint64(suite.chain.GetContext().BlockHeight())) - suite.Require().True(heights[0].EQ(expHeight)) - -} diff --git a/modules/light-clients/09-localhost/codec.go b/modules/light-clients/09-localhost/codec.go deleted file mode 100644 index 89d4e6980c1..00000000000 --- a/modules/light-clients/09-localhost/codec.go +++ /dev/null @@ -1,16 +0,0 @@ -package localhost - -import ( - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -// RegisterInterfaces registers the tendermint concrete client-related -// implementations and interfaces. -func RegisterInterfaces(registry codectypes.InterfaceRegistry) { - registry.RegisterImplementations( - (*exported.ClientState)(nil), - &ClientState{}, - ) -} diff --git a/modules/light-clients/09-localhost/codec_test.go b/modules/light-clients/09-localhost/codec_test.go deleted file mode 100644 index 5b90faf2160..00000000000 --- a/modules/light-clients/09-localhost/codec_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package localhost_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - - ibc "github.com/cosmos/ibc-go/v8/modules/core" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" -) - -func TestCodecTypeRegistration(t *testing.T) { - testCases := []struct { - name string - typeURL string - expPass bool - }{ - { - "success: ClientState", - sdk.MsgTypeURL(&localhost.ClientState{}), - true, - }, - { - "type not registered on codec", - "ibc.invalid.MsgTypeURL", - false, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) - msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) - - if tc.expPass { - require.NotNil(t, msg) - require.NoError(t, err) - } else { - require.Nil(t, msg) - require.Error(t, err) - } - }) - } -} diff --git a/modules/light-clients/09-localhost/keys.go b/modules/light-clients/09-localhost/keys.go deleted file mode 100644 index bf10ed0d800..00000000000 --- a/modules/light-clients/09-localhost/keys.go +++ /dev/null @@ -1,12 +0,0 @@ -package localhost - -const ( - // ModuleName defines the 09-localhost light client module name - ModuleName = "09-localhost" -) - -// SentinelProof defines the 09-localhost sentinel proof. -// Submission of nil or empty proofs is disallowed in core IBC messaging. -// This serves as a placeholder value for relayers to leverage as the proof field in various message types. -// Localhost client state verification will fail if the sentintel proof value is not provided. -var SentinelProof = []byte{0x01} diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go index 1467ae64d63..33720fe2124 100644 --- a/modules/light-clients/09-localhost/light_client_module.go +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -1,6 +1,8 @@ package localhost import ( + "bytes" + errorsmod "cosmossdk.io/errors" storetypes "cosmossdk.io/store/types" @@ -8,9 +10,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" "github.com/cosmos/ibc-go/v8/modules/core/exported" ) +const ( + // ModuleName defines the 09-localhost light client module name + ModuleName = "09-localhost" +) + +// SentinelProof defines the 09-localhost sentinel proof. +// Submission of nil or empty proofs is disallowed in core IBC messaging. +// This serves as a placeholder value for relayers to leverage as the proof field in various message types. +// Localhost client state verification will fail if the sentintel proof value is not provided. +var SentinelProof = []byte{0x01} + var _ exported.LightClientModule = (*LightClientModule)(nil) // LightClientModule implements the core IBC api.LightClientModule interface. @@ -81,9 +97,31 @@ func (l LightClientModule) VerifyMembership( ) error { ibcStore := ctx.KVStore(l.key) - clientState := NewClientState(clienttypes.GetSelfHeight(ctx)) + // ensure the proof provided is the expected sentinel localhost client proof + if !bytes.Equal(proof, SentinelProof) { + return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "expected %s, got %s", string(SentinelProof), string(proof)) + } + + merklePath, ok := path.(commitmenttypes.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) + } + + if len(merklePath.GetKeyPath()) != 2 { + return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) + } + + // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store + bz := ibcStore.Get([]byte(merklePath.KeyPath[1])) + if bz == nil { + return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value not found for path %s", path) + } + + if !bytes.Equal(bz, value) { + return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value provided does not equal value stored at path: %s", path) + } - return clientState.VerifyMembership(ctx, ibcStore, l.cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, value) + return nil } // VerifyNonMembership obtains the localhost client state and calls into the clientState.VerifyNonMembership method. @@ -100,9 +138,26 @@ func (l LightClientModule) VerifyNonMembership( ) error { ibcStore := ctx.KVStore(l.key) - clientState := NewClientState(clienttypes.GetSelfHeight(ctx)) + // ensure the proof provided is the expected sentinel localhost client proof + if !bytes.Equal(proof, SentinelProof) { + return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "expected %s, got %s", string(SentinelProof), string(proof)) + } + + merklePath, ok := path.(commitmenttypes.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) + } + + if len(merklePath.GetKeyPath()) != 2 { + return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) + } + + // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store + if ibcStore.Has([]byte(merklePath.KeyPath[1])) { + return errorsmod.Wrapf(clienttypes.ErrFailedNonMembershipVerification, "value found for path %s", path) + } - return clientState.VerifyNonMembership(ctx, ibcStore, l.cdc, height, delayTimePeriod, delayBlockPeriod, proof, path) + return nil } // Status always returns Active. The 09-localhost status cannot be changed. diff --git a/modules/light-clients/09-localhost/light_client_module_test.go b/modules/light-clients/09-localhost/light_client_module_test.go index e9019341864..6f05759a266 100644 --- a/modules/light-clients/09-localhost/light_client_module_test.go +++ b/modules/light-clients/09-localhost/light_client_module_test.go @@ -1,6 +1,10 @@ package localhost_test import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -14,10 +18,53 @@ import ( "github.com/cosmos/ibc-go/v8/testing/mock" ) -func (suite *LocalhostTestSuite) TestStatus() { +type LocalhostTestSuite struct { + testifysuite.Suite + + coordinator ibctesting.Coordinator + chain *ibctesting.TestChain +} + +func (suite *LocalhostTestSuite) SetupTest() { + suite.coordinator = *ibctesting.NewCoordinator(suite.T(), 1) + suite.chain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func TestLocalhostTestSuite(t *testing.T) { + testifysuite.Run(t, new(LocalhostTestSuite)) +} + +func (suite *LocalhostTestSuite) TestInitialize() { lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) suite.Require().True(found) - suite.Require().Equal(exported.Active, lightClientModule.Status(suite.chain.GetContext(), exported.LocalhostClientID)) + + err := lightClientModule.Initialize(suite.chain.GetContext(), exported.LocalhostClientID, nil, nil) + suite.Require().Error(err) +} + +func (suite *LocalhostTestSuite) TestVerifyClientMessage() { + lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) + suite.Require().True(found) + + err := lightClientModule.Initialize(suite.chain.GetContext(), exported.LocalhostClientID, nil, nil) + suite.Require().Error(err) +} + +func (suite *LocalhostTestSuite) TestVerifyCheckForMisbehaviour() { + lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) + suite.Require().True(found) + + suite.Require().False(lightClientModule.CheckForMisbehaviour(suite.chain.GetContext(), exported.LocalhostClientID, nil)) +} + +func (suite *LocalhostTestSuite) TestUpdateState() { + lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) + suite.Require().True(found) + + heights := lightClientModule.UpdateState(suite.chain.GetContext(), exported.LocalhostClientID, nil) + + expHeight := clienttypes.NewHeight(1, uint64(suite.chain.GetContext().BlockHeight())) + suite.Require().True(heights[0].EQ(expHeight)) } func (suite *LocalhostTestSuite) TestVerifyMembership() { @@ -129,20 +176,6 @@ func (suite *LocalhostTestSuite) TestVerifyMembership() { }, true, }, - { - "failure: stateless client state verification", - func() { - clientState := localhost.NewClientState(clienttypes.GetSelfHeight(suite.chain.GetContext())) - - merklePath := commitmenttypes.NewMerklePath(host.FullClientStatePath(exported.LocalhostClientID)) - merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) - suite.Require().NoError(err) - - path = merklePath - value = clienttypes.MustMarshalClientState(suite.chain.Codec, clientState) - }, - false, - }, { "failure: invalid type for key path", func() { @@ -303,6 +336,22 @@ func (suite *LocalhostTestSuite) TestVerifyNonMembership() { } } +func (suite *LocalhostTestSuite) TestStatus() { + lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) + suite.Require().True(found) + suite.Require().Equal(exported.Active, lightClientModule.Status(suite.chain.GetContext(), exported.LocalhostClientID)) +} + +func (suite *LocalhostTestSuite) TestGetTimestampAtHeight() { + lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) + suite.Require().True(found) + + ctx := suite.chain.GetContext() + timestamp, err := lightClientModule.TimestampAtHeight(ctx, exported.LocalhostClientID, nil) + suite.Require().NoError(err) + suite.Require().Equal(uint64(ctx.BlockTime().UnixNano()), timestamp) +} + func (suite *LocalhostTestSuite) TestRecoverClient() { lightClientModule, found := suite.chain.GetSimApp().IBCKeeper.ClientKeeper.Route(exported.LocalhostClientID) suite.Require().True(found) diff --git a/modules/light-clients/09-localhost/localhost.pb.go b/modules/light-clients/09-localhost/localhost.pb.go deleted file mode 100644 index 61c3999c56d..00000000000 --- a/modules/light-clients/09-localhost/localhost.pb.go +++ /dev/null @@ -1,322 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ibc/lightclients/localhost/v2/localhost.proto - -package localhost - -import ( - fmt "fmt" - _ "github.com/cosmos/gogoproto/gogoproto" - proto "github.com/cosmos/gogoproto/proto" - types "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// ClientState defines the 09-localhost client state -type ClientState struct { - // the latest block height - LatestHeight types.Height `protobuf:"bytes,1,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height"` -} - -func (m *ClientState) Reset() { *m = ClientState{} } -func (m *ClientState) String() string { return proto.CompactTextString(m) } -func (*ClientState) ProtoMessage() {} -func (*ClientState) Descriptor() ([]byte, []int) { - return fileDescriptor_60e51cfed1fd7859, []int{0} -} -func (m *ClientState) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ClientState.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ClientState) XXX_Merge(src proto.Message) { - xxx_messageInfo_ClientState.Merge(m, src) -} -func (m *ClientState) XXX_Size() int { - return m.Size() -} -func (m *ClientState) XXX_DiscardUnknown() { - xxx_messageInfo_ClientState.DiscardUnknown(m) -} - -var xxx_messageInfo_ClientState proto.InternalMessageInfo - -func init() { - proto.RegisterType((*ClientState)(nil), "ibc.lightclients.localhost.v2.ClientState") -} - -func init() { - proto.RegisterFile("ibc/lightclients/localhost/v2/localhost.proto", fileDescriptor_60e51cfed1fd7859) -} - -var fileDescriptor_60e51cfed1fd7859 = []byte{ - // 257 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xcd, 0x4c, 0x4a, 0xd6, - 0xcf, 0xc9, 0x4c, 0xcf, 0x28, 0x49, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x29, 0xd6, 0xcf, 0xc9, 0x4f, - 0x4e, 0xcc, 0xc9, 0xc8, 0x2f, 0x2e, 0xd1, 0x2f, 0x33, 0x42, 0x70, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, - 0xf2, 0x85, 0x64, 0x33, 0x93, 0x92, 0xf5, 0x90, 0x95, 0xeb, 0x21, 0x54, 0x94, 0x19, 0x49, 0xc9, - 0x83, 0x4c, 0x4b, 0xce, 0x2f, 0x4a, 0xd5, 0x87, 0x48, 0xeb, 0x97, 0x19, 0x42, 0x59, 0x10, 0xfd, - 0x52, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x60, 0xa6, 0x3e, 0x88, 0x05, 0x11, 0x55, 0x8a, 0xe2, 0xe2, - 0x76, 0x06, 0xab, 0x0a, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0x72, 0xe5, 0xe2, 0xcd, 0x49, 0x2c, 0x49, - 0x2d, 0x2e, 0x89, 0xcf, 0x48, 0x05, 0x59, 0x25, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0xa5, - 0x07, 0xb2, 0x1c, 0x64, 0xba, 0x1e, 0xd4, 0xcc, 0x32, 0x43, 0x3d, 0x0f, 0xb0, 0x0a, 0x27, 0x96, - 0x13, 0xf7, 0xe4, 0x19, 0x82, 0x78, 0x20, 0xda, 0x20, 0x62, 0x56, 0x2c, 0x1d, 0x0b, 0xe4, 0x19, - 0x9c, 0x92, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, - 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x23, 0x3d, 0xb3, - 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x3f, 0x39, 0xbf, 0x38, 0x37, 0xbf, 0x58, 0x3f, - 0x33, 0x29, 0x59, 0x37, 0x3d, 0x5f, 0xbf, 0xcc, 0x42, 0x3f, 0x37, 0x3f, 0xa5, 0x34, 0x27, 0xb5, - 0x18, 0x12, 0x34, 0xba, 0xb0, 0xb0, 0x31, 0xb0, 0xd4, 0x85, 0xfb, 0xd7, 0x1a, 0xce, 0x4a, 0x62, - 0x03, 0x7b, 0xc3, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xac, 0xfc, 0x52, 0xb3, 0x4d, 0x01, 0x00, - 0x00, -} - -func (m *ClientState) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClientState) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.LatestHeight.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintLocalhost(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func encodeVarintLocalhost(dAtA []byte, offset int, v uint64) int { - offset -= sovLocalhost(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *ClientState) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.LatestHeight.Size() - n += 1 + l + sovLocalhost(uint64(l)) - return n -} - -func sovLocalhost(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozLocalhost(x uint64) (n int) { - return sovLocalhost(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *ClientState) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLocalhost - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ClientState: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ClientState: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LatestHeight", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLocalhost - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLocalhost - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthLocalhost - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.LatestHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLocalhost(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthLocalhost - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipLocalhost(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLocalhost - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLocalhost - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLocalhost - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthLocalhost - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupLocalhost - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthLocalhost - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthLocalhost = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowLocalhost = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupLocalhost = fmt.Errorf("proto: unexpected end of group") -) diff --git a/modules/light-clients/09-localhost/localhost_test.go b/modules/light-clients/09-localhost/localhost_test.go deleted file mode 100644 index c12c4546cd8..00000000000 --- a/modules/light-clients/09-localhost/localhost_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package localhost_test - -import ( - "testing" - - testifysuite "github.com/stretchr/testify/suite" - - ibctesting "github.com/cosmos/ibc-go/v8/testing" -) - -type LocalhostTestSuite struct { - testifysuite.Suite - - coordinator ibctesting.Coordinator - chain *ibctesting.TestChain -} - -func (suite *LocalhostTestSuite) SetupTest() { - suite.coordinator = *ibctesting.NewCoordinator(suite.T(), 1) - suite.chain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) -} - -func TestLocalhostTestSuite(t *testing.T) { - testifysuite.Run(t, new(LocalhostTestSuite)) -} diff --git a/proto/ibc/lightclients/localhost/v2/localhost.proto b/proto/ibc/lightclients/localhost/v2/localhost.proto deleted file mode 100644 index 635db85214e..00000000000 --- a/proto/ibc/lightclients/localhost/v2/localhost.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package ibc.lightclients.localhost.v2; - -option go_package = "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost;localhost"; - -import "ibc/core/client/v1/client.proto"; -import "gogoproto/gogo.proto"; - -// ClientState defines the 09-localhost client state -message ClientState { - option (gogoproto.goproto_getters) = false; - - // the latest block height - ibc.core.client.v1.Height latest_height = 1 [(gogoproto.nullable) = false]; -} From 8321f41e9051cf7b7e8900ed5ee56a5f4abf6e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:49:36 +0200 Subject: [PATCH 10/17] rename: MigrateToStatelessLocalhost --- modules/core/02-client/keeper/migrations.go | 4 +++- modules/core/02-client/keeper/migrations_test.go | 6 +++--- modules/core/module.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index e1f92eba3f1..0da2e08febd 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -51,7 +51,9 @@ func (m Migrator) MigrateParams(ctx sdk.Context) error { return nil } -func (m Migrator) MigrateStatelessLocalhost(ctx sdk.Context) error { +// MigrateToStatelessLocalhost deletes the localhost client state. The localhost +// implementation is now stateless. +func (m Migrator) MigrateToStatelessLocalhost(ctx sdk.Context) error { clientStore := m.keeper.ClientStore(ctx, exported.LocalhostClientID) // delete the client state diff --git a/modules/core/02-client/keeper/migrations_test.go b/modules/core/02-client/keeper/migrations_test.go index 739363b774d..6917a2a973e 100644 --- a/modules/core/02-client/keeper/migrations_test.go +++ b/modules/core/02-client/keeper/migrations_test.go @@ -44,18 +44,18 @@ func (suite *KeeperTestSuite) TestMigrateParams() { } } -func (suite *KeeperTestSuite) TestMigrateStatelessLocalhost() { +func (suite *KeeperTestSuite) TestMigrateToStatelessLocalhost() { // set localhost in state clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.LocalhostClientID) clientStore.Set(host.ClientStateKey(), []byte("clientState")) m := keeper.NewMigrator(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) - err := m.MigrateStatelessLocalhost(suite.chainA.GetContext()) + err := m.MigrateToStatelessLocalhost(suite.chainA.GetContext()) suite.Require().NoError(err) suite.Require().False(clientStore.Has(host.ClientStateKey())) // rerun migration on no localhost set - err = m.MigrateStatelessLocalhost(suite.chainA.GetContext()) + err = m.MigrateToStatelessLocalhost(suite.chainA.GetContext()) suite.Require().NoError(err) suite.Require().False(clientStore.Has(host.ClientStateKey())) } diff --git a/modules/core/module.go b/modules/core/module.go index 10ea6d4ef06..58587a21780 100644 --- a/modules/core/module.go +++ b/modules/core/module.go @@ -164,7 +164,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { panic(err) } - cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.MigrateStatelessLocalhost) + cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.MigrateToStatelessLocalhost) } // InitGenesis performs genesis initialization for the ibc module. It returns From c58f93a3182883ac07b14ca6a44a2bd894e4569c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:55:45 +0200 Subject: [PATCH 11/17] lint --- e2e/tests/transfer/localhost_test.go | 15 --------------- modules/core/02-client/keeper/migrations.go | 2 +- modules/core/02-client/keeper/migrations_test.go | 3 +-- modules/core/module.go | 4 +++- .../09-localhost/light_client_module.go | 6 +++--- 5 files changed, 8 insertions(+), 22 deletions(-) diff --git a/e2e/tests/transfer/localhost_test.go b/e2e/tests/transfer/localhost_test.go index c24a4d19425..982f05a6b68 100644 --- a/e2e/tests/transfer/localhost_test.go +++ b/e2e/tests/transfer/localhost_test.go @@ -53,21 +53,6 @@ func (s *LocalhostTransferTestSuite) TestMsgTransfer_Localhost() { s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") - t.Run("verify begin blocker was executed", func(t *testing.T) { - cs, err := query.ClientState(ctx, chainA, exported.LocalhostClientID) - s.Require().NoError(err) - - localhostClientState, ok := cs.(*localhost.ClientState) - s.Require().True(ok) - originalHeight := localhostClientState.LatestHeight - - s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") - - cs, err = query.ClientState(ctx, chainA, exported.LocalhostClientID) - s.Require().NoError(err) - s.Require().True(cs.(*localhost.ClientState).LatestHeight.GT(originalHeight), "client state height was not incremented") - }) - t.Run("channel open init localhost", func(t *testing.T) { msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( transfertypes.PortID, channelA.Version, diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index 0da2e08febd..9f64ddaf466 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -31,7 +31,7 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error { // Migrate3to4 migrates from consensus version 3 to 4. // This migration enables the localhost client. -func (m Migrator) Migrate3to4(ctx sdk.Context) error { +func (Migrator) Migrate3to4(ctx sdk.Context) error { // localhost is now stateless, no changes necessary return nil } diff --git a/modules/core/02-client/keeper/migrations_test.go b/modules/core/02-client/keeper/migrations_test.go index 6917a2a973e..7b9370307bf 100644 --- a/modules/core/02-client/keeper/migrations_test.go +++ b/modules/core/02-client/keeper/migrations_test.go @@ -4,7 +4,6 @@ import ( "github.com/cosmos/ibc-go/v8/modules/core/02-client/keeper" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ) @@ -46,7 +45,7 @@ func (suite *KeeperTestSuite) TestMigrateParams() { func (suite *KeeperTestSuite) TestMigrateToStatelessLocalhost() { // set localhost in state - clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.LocalhostClientID) + clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), ibcexported.LocalhostClientID) clientStore.Set(host.ClientStateKey(), []byte("clientState")) m := keeper.NewMigrator(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) diff --git a/modules/core/module.go b/modules/core/module.go index 58587a21780..679b01047f0 100644 --- a/modules/core/module.go +++ b/modules/core/module.go @@ -164,7 +164,9 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { panic(err) } - cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.MigrateToStatelessLocalhost) + if err := cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.MigrateToStatelessLocalhost); err != nil { + panic(err) + } } // InitGenesis performs genesis initialization for the ibc module. It returns diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go index 33720fe2124..1b0111a4db9 100644 --- a/modules/light-clients/09-localhost/light_client_module.go +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -75,7 +75,7 @@ func (LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID str // no-op } -// UpdateState obtains the localhost client state and calls into the clientState.UpdateState method. +// UpdateState performs a no-op and returns the context height in the updated heights return value. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. func (LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { @@ -112,7 +112,7 @@ func (l LightClientModule) VerifyMembership( } // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store - bz := ibcStore.Get([]byte(merklePath.KeyPath[1])) + bz := ibcStore.Get(merklePath.KeyPath[1]) if bz == nil { return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value not found for path %s", path) } @@ -153,7 +153,7 @@ func (l LightClientModule) VerifyNonMembership( } // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store - if ibcStore.Has([]byte(merklePath.KeyPath[1])) { + if ibcStore.Has(merklePath.KeyPath[1]) { return errorsmod.Wrapf(clienttypes.ErrFailedNonMembershipVerification, "value found for path %s", path) } From 58ca2d9fdf192c5927f792e0c557178d7b373a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:00:37 +0200 Subject: [PATCH 12/17] Update docs/docs/05-migrations/13-v8-to-v9.md --- docs/docs/05-migrations/13-v8-to-v9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/05-migrations/13-v8-to-v9.md b/docs/docs/05-migrations/13-v8-to-v9.md index 99a9bf83d50..7adbc764134 100644 --- a/docs/docs/05-migrations/13-v8-to-v9.md +++ b/docs/docs/05-migrations/13-v8-to-v9.md @@ -188,7 +188,7 @@ The `IterateConsensusMetadata` function has been removed. ### 09-localhost -The `09-localhost` light client has been made stateless and will no longer updating the client on every block. The `ClientState` is constructed on demand when required. +The `09-localhost` light client has been made stateless and will no longer update the client on every block. The `ClientState` is constructed on demand when required. The `ClientState` itself is therefore no longer provable direcly with `VerifyMembership` or `VerifyNonMembership`. Previously stored client state date is pruned automatically on IBC module store migration from `ConsensusVersion` 6 to 7. From ab9908425fcf66aa2033dbea39ba206bd4c9099f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:36:54 +0200 Subject: [PATCH 13/17] Update docs/docs/05-migrations/13-v8-to-v9.md Co-authored-by: Damian Nolan --- docs/docs/05-migrations/13-v8-to-v9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/05-migrations/13-v8-to-v9.md b/docs/docs/05-migrations/13-v8-to-v9.md index 7adbc764134..88c36cbf082 100644 --- a/docs/docs/05-migrations/13-v8-to-v9.md +++ b/docs/docs/05-migrations/13-v8-to-v9.md @@ -191,4 +191,4 @@ The `IterateConsensusMetadata` function has been removed. The `09-localhost` light client has been made stateless and will no longer update the client on every block. The `ClientState` is constructed on demand when required. The `ClientState` itself is therefore no longer provable direcly with `VerifyMembership` or `VerifyNonMembership`. -Previously stored client state date is pruned automatically on IBC module store migration from `ConsensusVersion` 6 to 7. +Previously stored client state data is pruned automatically on IBC module store migration from `ConsensusVersion` 6 to 7. From 61f8ea69fef2e2d44b32a540df9969181273cd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:42:44 +0200 Subject: [PATCH 14/17] review: docs + linting cleanups --- .../02-localhost/01-overview.md | 7 ++--- .../02-localhost/03-client-state.md | 21 +------------ .../02-localhost/05-state-verification.md | 1 - docs/docs/05-migrations/13-v8-to-v9.md | 1 + .../09-localhost/light_client_module.go | 31 ++++++++++--------- 5 files changed, 22 insertions(+), 39 deletions(-) diff --git a/docs/docs/03-light-clients/02-localhost/01-overview.md b/docs/docs/03-light-clients/02-localhost/01-overview.md index 6cddc5a5803..c6754004097 100644 --- a/docs/docs/03-light-clients/02-localhost/01-overview.md +++ b/docs/docs/03-light-clients/02-localhost/01-overview.md @@ -26,7 +26,7 @@ applications on a single chain, using the familiar IBC application layer semanti ### Implementation -There exists a [single sentinel `ClientState`](03-client-state.md) with the client identifier `09-localhost`. The light +There exists a localhost light client module which can be invoked with the client identifier `09-localhost`. The light client is stateless, so the `ClientState` is constructed on demand when required. To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection @@ -48,10 +48,9 @@ The table below lists some important differences: |----------------------------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| | Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost` | | Client creation | Relayer (permissionless) | Implicitly made available by the 02-client submodule in core IBC | -| Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the `ClientState` is constructed with the latest height when queried | +| Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the localhost implementation is stateless | | Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | | Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | | Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | | `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | -| Integration | Expected to register codec types using the `AppModuleBasic` interface | Registers codec types within the core IBC module | -| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` | \ No newline at end of file +| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` | diff --git a/docs/docs/03-light-clients/02-localhost/03-client-state.md b/docs/docs/03-light-clients/02-localhost/03-client-state.md index c68553bba84..c7bac364b33 100644 --- a/docs/docs/03-light-clients/02-localhost/03-client-state.md +++ b/docs/docs/03-light-clients/02-localhost/03-client-state.md @@ -8,24 +8,5 @@ slug: /ibc/light-clients/localhost/client-state # `ClientState` -Even though the 09-localhost light client is a stateless client, it still has the concept of a `ClientState` that follows the -blockchains own latest height automatically. The `ClientState` is constructed on demand when required. - -The 09-localhost `ClientState` maintains a single field used to track the latest sequence of the state machine i.e. the height of the blockchain. - -```go -type ClientState struct { - // the latest height of the blockchain - LatestHeight clienttypes.Height -} -``` - -The 09-localhost `ClientState` is available from the 02-client submodule in core IBC, and does not need to be initialized. - -It is possible to disable the localhost client by removing the `09-localhost` entry from the `allowed_clients` list through governance. - -## Client updates - -The 09-localhost `ClientState` is stateless, so no client updates are required. The `ClientState` is constructed with the latest height when queried. -It will always follow latest height of the blockchain. +The 09-localhost client is stateless and has no types. diff --git a/docs/docs/03-light-clients/02-localhost/05-state-verification.md b/docs/docs/03-light-clients/02-localhost/05-state-verification.md index 19d3f1f177a..13b7c4710e6 100644 --- a/docs/docs/03-light-clients/02-localhost/05-state-verification.md +++ b/docs/docs/03-light-clients/02-localhost/05-state-verification.md @@ -22,5 +22,4 @@ var SentinelProof = []byte{0x01} ``` The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`. -Instead, the `ClientState` is constructed on demand when required. diff --git a/docs/docs/05-migrations/13-v8-to-v9.md b/docs/docs/05-migrations/13-v8-to-v9.md index 99a9bf83d50..430d94f0be2 100644 --- a/docs/docs/05-migrations/13-v8-to-v9.md +++ b/docs/docs/05-migrations/13-v8-to-v9.md @@ -102,6 +102,7 @@ func NewMsgModuleQuerySafe( - The utility function `QueryLatestConsensusState` of `04-channel` CLI has been removed. - `UnmarshalPacketData` now takes in the context, portID, and channelID. This allows the packet data to be unmarshaled based on the channel version. - `Router` reference has been removed from IBC core keeper: [#6138](https://github.com/cosmos/ibc-go/pull/6138). Please use `PortKeeper.Router` instead. +- The function `CreateLocalhostClient` has been removed. The localhost client is now stateless. ### 02-client diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go index 1b0111a4db9..49e5536b76b 100644 --- a/modules/light-clients/09-localhost/light_client_module.go +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -51,38 +51,40 @@ func (l *LightClientModule) RegisterStoreProvider(storeProvider exported.ClientS l.storeProvider = storeProvider } -// Initialize returns an error because it is stateless +// Initialize returns an error because it is stateless. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (LightClientModule) Initialize(ctx sdk.Context, clientID string, clientState, consensusStateBz []byte) error { +func (LightClientModule) Initialize(_ sdk.Context, _ string, _, _ []byte) error { return errorsmod.Wrap(clienttypes.ErrClientExists, "localhost is stateless and cannot be initialized") } // VerifyClientMessage is unsupported by the 09-localhost client type and returns an error. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { +func (LightClientModule) VerifyClientMessage(_ sdk.Context, _ string, _ exported.ClientMessage) error { return errorsmod.Wrap(clienttypes.ErrUpdateClientFailed, "client message verification is unsupported by the localhost client") } // CheckForMisbehaviour is unsupported by the 09-localhost client type and performs a no-op, returning false. -func (LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) bool { +func (LightClientModule) CheckForMisbehaviour(_ sdk.Context, _ string, _ exported.ClientMessage) bool { return false } // UpdateStateOnMisbehaviour is unsupported by the 09-localhost client type and performs a no-op. -func (LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) { +func (LightClientModule) UpdateStateOnMisbehaviour(_ sdk.Context, _ string, _ exported.ClientMessage) { // no-op } // UpdateState performs a no-op and returns the context height in the updated heights return value. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { +func (LightClientModule) UpdateState(ctx sdk.Context, _ string, _ exported.ClientMessage) []exported.Height { return []exported.Height{clienttypes.GetSelfHeight(ctx)} } -// VerifyMembership obtains the localhost client state and calls into the clientState.VerifyMembership method. +// VerifyMembership is a generic proof verification method which verifies the existence of a given key and value within the IBC store. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// The caller must provide the full IBC store. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. func (l LightClientModule) VerifyMembership( @@ -124,7 +126,9 @@ func (l LightClientModule) VerifyMembership( return nil } -// VerifyNonMembership obtains the localhost client state and calls into the clientState.VerifyNonMembership method. +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath within the IBC store. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// The caller must provide the full IBC store. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. func (l LightClientModule) VerifyNonMembership( @@ -161,21 +165,20 @@ func (l LightClientModule) VerifyNonMembership( } // Status always returns Active. The 09-localhost status cannot be changed. -func (LightClientModule) Status(ctx sdk.Context, clientID string) exported.Status { +func (LightClientModule) Status(_ sdk.Context, _ string) exported.Status { return exported.Active } -// LatestHeight returns the latest height for the client state for the given client identifier. -// If no client is present for the provided client identifier a zero value height is returned. +// LatestHeight returns the context height. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to be 09-localhost. -func (LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { +func (LightClientModule) LatestHeight(ctx sdk.Context, _ string) exported.Height { return clienttypes.GetSelfHeight(ctx) } // TimestampAtHeight returns the current block time retrieved from the application context. The localhost client does not store consensus states and thus // cannot provide a timestamp for the provided height. -func (LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) { +func (LightClientModule) TimestampAtHeight(ctx sdk.Context, _ string, _ exported.Height) (uint64, error) { return uint64(ctx.BlockTime().UnixNano()), nil } @@ -185,6 +188,6 @@ func (LightClientModule) RecoverClient(_ sdk.Context, _, _ string) error { } // VerifyUpgradeAndUpdateState returns an error since localhost cannot be upgraded. -func (LightClientModule) VerifyUpgradeAndUpdateState(ctx sdk.Context, clientID string, newClient, newConsState, upgradeClientProof, upgradeConsensusStateProof []byte) error { +func (LightClientModule) VerifyUpgradeAndUpdateState(_ sdk.Context, _ string, _, _, _, _ []byte) error { return errorsmod.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade localhost client") } From fdd78065f3e05733192c0294b22aa92b59d377cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:50:59 +0200 Subject: [PATCH 15/17] refactor: remove unused func --- modules/core/02-client/keeper/migrations.go | 7 ------- modules/core/module.go | 6 +----- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index 9f64ddaf466..979a7fd6182 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -29,13 +29,6 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error { return v7.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc, m.keeper) } -// Migrate3to4 migrates from consensus version 3 to 4. -// This migration enables the localhost client. -func (Migrator) Migrate3to4(ctx sdk.Context) error { - // localhost is now stateless, no changes necessary - return nil -} - // MigrateParams migrates from consensus version 4 to 5. // This migration takes the parameters that are currently stored and managed by x/params // and stores them directly in the ibc module's state. diff --git a/modules/core/module.go b/modules/core/module.go index 679b01047f0..9a3fc9d9084 100644 --- a/modules/core/module.go +++ b/modules/core/module.go @@ -140,11 +140,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { connectionMigrator := connectionkeeper.NewMigrator(am.keeper.ConnectionKeeper) if err := cfg.RegisterMigration(exported.ModuleName, 3, func(ctx sdk.Context) error { - if err := connectionMigrator.Migrate3to4(ctx); err != nil { - return err - } - - return clientMigrator.Migrate3to4(ctx) + return connectionMigrator.Migrate3to4(ctx) }); err != nil { panic(err) } From a66ccff4a1f2427ef03a911b111ff87045fd6a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:51:30 +0200 Subject: [PATCH 16/17] Update modules/core/02-client/keeper/migrations.go --- modules/core/02-client/keeper/migrations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index 979a7fd6182..3dd32bbbf46 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -3,7 +3,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - v7 "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" + "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/cosmos/ibc-go/v8/modules/core/exported" From 6028aa087be1459172903bc5e5587ba3a92b8690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:54:18 +0200 Subject: [PATCH 17/17] markdown lint --- docs/docs/03-light-clients/02-localhost/03-client-state.md | 2 -- .../docs/03-light-clients/02-localhost/05-state-verification.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/docs/docs/03-light-clients/02-localhost/03-client-state.md b/docs/docs/03-light-clients/02-localhost/03-client-state.md index c7bac364b33..3cbdf05442a 100644 --- a/docs/docs/03-light-clients/02-localhost/03-client-state.md +++ b/docs/docs/03-light-clients/02-localhost/03-client-state.md @@ -5,8 +5,6 @@ sidebar_position: 3 slug: /ibc/light-clients/localhost/client-state --- - # `ClientState` The 09-localhost client is stateless and has no types. - diff --git a/docs/docs/03-light-clients/02-localhost/05-state-verification.md b/docs/docs/03-light-clients/02-localhost/05-state-verification.md index 13b7c4710e6..82d10d6ff40 100644 --- a/docs/docs/03-light-clients/02-localhost/05-state-verification.md +++ b/docs/docs/03-light-clients/02-localhost/05-state-verification.md @@ -5,7 +5,6 @@ sidebar_position: 5 slug: /ibc/light-clients/localhost/state-verification --- - # State verification The localhost client handles state verification through the `LightClientModule` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store. @@ -22,4 +21,3 @@ var SentinelProof = []byte{0x01} ``` The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`. -