diff --git a/docs/migrations/v6-to-v7.md b/docs/migrations/v6-to-v7.md index 9046ae679f1..64cd0d54f28 100644 --- a/docs/migrations/v6-to-v7.md +++ b/docs/migrations/v6-to-v7.md @@ -21,7 +21,7 @@ Add the following to the function call to the upgrade handler in `app/app.go`, t ```go import ( // ... - ibctm "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint" + ibctmmigrations "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint/migrations" ) // ... @@ -30,7 +30,10 @@ app.UpgradeKeeper.SetUpgradeHandler( upgradeName, func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { // prune expired tendermint consensus states to save storage space - ibctm.PruneTendermintConsensusStates(ctx, app.Codec, appCodec, keys[ibchost.StoreKey]) + _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, app.Codec, app.IBCKeeper.ClientKeeper) + if err != nil { + return nil, err + } return app.mm.RunMigrations(ctx, app.configurator, fromVM) }, diff --git a/modules/light-clients/07-tendermint/migrations.go b/modules/light-clients/07-tendermint/migrations.go deleted file mode 100644 index c369e6b6836..00000000000 --- a/modules/light-clients/07-tendermint/migrations.go +++ /dev/null @@ -1,72 +0,0 @@ -package tendermint - -import ( - "fmt" - "strings" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v6/modules/core/24-host" - "github.com/cosmos/ibc-go/v6/modules/core/exported" -) - -// PruneTendermintConsensusStates prunes all expired tendermint consensus states. This function -// may optionally be called during in-place store migrations. The ibc store key must be provided. -func PruneTendermintConsensusStates(ctx sdk.Context, cdc codec.BinaryCodec, storeKey storetypes.StoreKey) error { - store := ctx.KVStore(storeKey) - - // iterate over ibc store with prefix: clients/07-tendermint, - tendermintClientPrefix := []byte(fmt.Sprintf("%s/%s", host.KeyClientStorePrefix, exported.Tendermint)) - iterator := sdk.KVStorePrefixIterator(store, tendermintClientPrefix) - - var clientIDs []string - - // collect all clients to avoid performing store state changes during iteration - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - path := string(iterator.Key()) - if !strings.Contains(path, host.KeyClientState) { - // skip non client state keys - continue - } - - clientID := host.MustParseClientStatePath(path) - clientIDs = append(clientIDs, clientID) - } - - // keep track of the total consensus states pruned so chains can - // understand how much space is saved when the migration is run - var totalPruned int - - for _, clientID := range clientIDs { - clientPrefix := []byte(fmt.Sprintf("%s/%s/", host.KeyClientStorePrefix, clientID)) - clientStore := prefix.NewStore(ctx.KVStore(storeKey), clientPrefix) - - bz := clientStore.Get(host.ClientStateKey()) - if bz == nil { - return clienttypes.ErrClientNotFound - } - - var clientState exported.ClientState - if err := cdc.UnmarshalInterface(bz, &clientState); err != nil { - return sdkerrors.Wrap(err, "failed to unmarshal client state bytes into tendermint client state") - } - - tmClientState, ok := clientState.(*ClientState) - if !ok { - return sdkerrors.Wrap(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint") - } - - totalPruned += PruneAllExpiredConsensusStates(ctx, clientStore, cdc, tmClientState) - } - - clientLogger := ctx.Logger().With("module", "x/"+host.ModuleName+"/"+clienttypes.SubModuleName) - clientLogger.Info("pruned expired tendermint consensus states", "total", totalPruned) - - return nil -} diff --git a/modules/light-clients/07-tendermint/migrations/expected_keepers.go b/modules/light-clients/07-tendermint/migrations/expected_keepers.go new file mode 100644 index 00000000000..6a0a57c054b --- /dev/null +++ b/modules/light-clients/07-tendermint/migrations/expected_keepers.go @@ -0,0 +1,16 @@ +package migrations + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/ibc-go/v6/modules/core/exported" +) + +// ClientKeeper expected account IBC client keeper +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + IterateClientStates(ctx sdk.Context, prefix []byte, cb func(string, exported.ClientState) bool) + ClientStore(ctx sdk.Context, clientID string) sdk.KVStore + Logger(ctx sdk.Context) log.Logger +} diff --git a/modules/light-clients/07-tendermint/migrations/migrations.go b/modules/light-clients/07-tendermint/migrations/migrations.go new file mode 100644 index 00000000000..401c695a3e3 --- /dev/null +++ b/modules/light-clients/07-tendermint/migrations/migrations.go @@ -0,0 +1,46 @@ +package migrations + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v6/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint" +) + +// PruneExpiredConsensusStates prunes all expired tendermint consensus states. This function +// may optionally be called during in-place store migrations. The ibc store key must be provided. +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 { + clientIDs = append(clientIDs, clientID) + return false + }) + + // keep track of the total consensus states pruned so chains can + // understand how much space is saved when the migration is run + var totalPruned int + + for _, clientID := range clientIDs { + clientStore := clientKeeper.ClientStore(ctx, clientID) + + clientState, ok := clientKeeper.GetClientState(ctx, clientID) + if !ok { + return 0, sdkerrors.Wrapf(clienttypes.ErrClientNotFound, "clientID %s", clientID) + } + + tmClientState, ok := clientState.(*ibctm.ClientState) + if !ok { + return 0, sdkerrors.Wrap(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint") + } + + totalPruned += ibctm.PruneAllExpiredConsensusStates(ctx, clientStore, cdc, tmClientState) + } + + clientLogger := clientKeeper.Logger(ctx) + clientLogger.Info("pruned expired tendermint consensus states", "total", totalPruned) + + return totalPruned, nil +} diff --git a/modules/light-clients/07-tendermint/migrations_test.go b/modules/light-clients/07-tendermint/migrations/migrations_test.go similarity index 85% rename from modules/light-clients/07-tendermint/migrations_test.go rename to modules/light-clients/07-tendermint/migrations/migrations_test.go index c7fe56af5db..b171b844f09 100644 --- a/modules/light-clients/07-tendermint/migrations_test.go +++ b/modules/light-clients/07-tendermint/migrations/migrations_test.go @@ -1,17 +1,41 @@ -package tendermint_test +package migrations_test import ( + "testing" "time" + "github.com/stretchr/testify/suite" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v6/modules/core/24-host" "github.com/cosmos/ibc-go/v6/modules/core/exported" ibctm "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint" + ibctmmigrations "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint/migrations" ibctesting "github.com/cosmos/ibc-go/v6/testing" ) +type MigrationsTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *MigrationsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestTendermintTestSuite(t *testing.T) { + suite.Run(t, new(MigrationsTestSuite)) +} + // test pruning of multiple expired tendermint consensus states -func (suite *TendermintTestSuite) TestPruneTendermintConsensusStates() { +func (suite *MigrationsTestSuite) TestPruneExpiredConsensusStates() { // create multiple tendermint clients and a solo machine client // the solo machine is used to verify this pruning function only modifies // the tendermint store. @@ -99,8 +123,9 @@ func (suite *TendermintTestSuite) TestPruneTendermintConsensusStates() { // This will cause the consensus states created before the first time increment // to be expired suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) - err = ibctm.PruneTendermintConsensusStates(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().GetKey(host.StoreKey)) + totalPruned, err := ibctmmigrations.PruneExpiredConsensusStates(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) suite.Require().NoError(err) + suite.Require().NotZero(totalPruned) for _, path := range paths { ctx := suite.chainA.GetContext() diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 633199155e7..b1f5379a02f 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -899,7 +899,7 @@ func (app *SimApp) setupUpgradeHandlers() { app.mm, app.configurator, app.appCodec, - app.keys[ibchost.StoreKey], + app.IBCKeeper.ClientKeeper, ), ) } diff --git a/testing/simapp/upgrades/v7/upgrades.go b/testing/simapp/upgrades/v7/upgrades.go index a9072ed63a2..078ae3cb088 100644 --- a/testing/simapp/upgrades/v7/upgrades.go +++ b/testing/simapp/upgrades/v7/upgrades.go @@ -2,12 +2,12 @@ package v7 import ( "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - ibctm "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint" + clientkeeper "github.com/cosmos/ibc-go/v6/modules/core/02-client/keeper" + ibctmmigrations "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint/migrations" ) const ( @@ -20,13 +20,14 @@ func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, cdc codec.BinaryCodec, - hostStoreKey *storetypes.KVStoreKey, + clientKeeper clientkeeper.Keeper, ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { // OPTIONAL: prune expired tendermint consensus states to save storage space - if err := ibctm.PruneTendermintConsensusStates(ctx, cdc, hostStoreKey); err != nil { + if _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, cdc, clientKeeper); err != nil { return nil, err } + return mm.RunMigrations(ctx, configurator, vm) } }