diff --git a/x/ccv/provider/keeper/permissionless.go b/x/ccv/provider/keeper/permissionless.go index 6647cd2882..168ca3c61c 100644 --- a/x/ccv/provider/keeper/permissionless.go +++ b/x/ccv/provider/keeper/permissionless.go @@ -1,6 +1,7 @@ package keeper import ( + storetypes "cosmossdk.io/store/types" "encoding/binary" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -198,8 +199,28 @@ func (k Keeper) DeleteClientIdToConsumerId(ctx sdk.Context, clientId string) { store.Delete(types.ClientIdToConsumerIdKey(clientId)) } -// IsConsumerLaunched returns true if the consumer chain with the provided id is launched -func (k Keeper) IsConsumerLaunched(ctx sdk.Context, consumerId string) bool { - _, found := k.GetConsumerClientId(ctx, consumerId) - return found +// GetInitializedConsumersReadyToLaunch returns the consumer ids of the pending initialized consumer chains +// that are ready to launch, i.e., consumer clients to be created. +func (k Keeper) GetInitializedConsumersReadyToLaunch(ctx sdk.Context) []string { + store := ctx.KVStore(k.storeKey) + iterator := storetypes.KVStorePrefixIterator(store, types.ConsumerIdToInitializationRecordKeyNameKeyPrefix()) + defer iterator.Close() + + var consumerIds []string + + for ; iterator.Valid(); iterator.Next() { + var record types.ConsumerInitializationRecord + err := record.Unmarshal(iterator.Value()) + if err != nil { + panic(fmt.Errorf("failed to unmarshal consumer record: %w for consumer id: %s", err, string(iterator.Value()))) + } + + if !ctx.BlockTime().Before(record.SpawnTime) { + // the `consumerId` resides in the whole key, but we skip the first byte (because it's the `ConsumerIdKey`) + // plus 8 more bytes for the `uint64` in the key that contains the length of the `consumerId` + consumerIds = append(consumerIds, string(iterator.Key()[1+8:])) + } + } + + return consumerIds } diff --git a/x/ccv/provider/keeper/permissionless_test.go b/x/ccv/provider/keeper/permissionless_test.go index aa352f6b9b..1a75cd27fa 100644 --- a/x/ccv/provider/keeper/permissionless_test.go +++ b/x/ccv/provider/keeper/permissionless_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/keeper" providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" "github.com/stretchr/testify/require" "testing" @@ -192,13 +193,54 @@ func TestConsumerIdToOwnerAddress(t *testing.T) { require.False(t, found) } -func TestIsConsumerLaunched(t *testing.T) { +// TestConsumerIdToPhase tests the getter, setter, and deletion methods of the consumer id to phase methods +func TestConsumerIdToPhase(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - require.False(t, providerKeeper.IsConsumerLaunched(ctx, "consumerId")) + _, found := providerKeeper.GetConsumerIdToPhase(ctx, "consumerId") + require.False(t, found) + + providerKeeper.SetConsumerIdToPhase(ctx, "consumerId", keeper.Registered) + phase, found := providerKeeper.GetConsumerIdToPhase(ctx, "consumerId") + require.True(t, found) + require.Equal(t, keeper.Registered, phase) + + providerKeeper.SetConsumerIdToPhase(ctx, "consumerId", keeper.Launched) + phase, found = providerKeeper.GetConsumerIdToPhase(ctx, "consumerId") + require.True(t, found) + require.Equal(t, keeper.Launched, phase) +} + +// TestGetInitializedConsumersReadyToLaunch tests that the ready to-be-launched consumer chains are returned +func TestGetInitializedConsumersReadyToLaunch(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // no chains to-be-launched exist + require.Empty(t, providerKeeper.GetInitializedConsumersReadyToLaunch(ctx)) + + // set 3 initialization records with different spawn times + providerKeeper.SetConsumerIdToInitializationRecord(ctx, "consumerId1", + providertypes.ConsumerInitializationRecord{SpawnTime: time.Unix(10, 0)}) + providerKeeper.SetConsumerIdToInitializationRecord(ctx, "consumerId2", + providertypes.ConsumerInitializationRecord{SpawnTime: time.Unix(20, 0)}) + providerKeeper.SetConsumerIdToInitializationRecord(ctx, "consumerId3", + providertypes.ConsumerInitializationRecord{SpawnTime: time.Unix(30, 0)}) + + // time has not yet reached the spawn time of "consumerId1" + ctx = ctx.WithBlockTime(time.Unix(9, 999999999)) + require.Empty(t, providerKeeper.GetInitializedConsumersReadyToLaunch(ctx)) + + // time has reached the spawn time of "consumerId1" + ctx = ctx.WithBlockTime(time.Unix(10, 0)) + require.Equal(t, []string{"consumerId1"}, providerKeeper.GetInitializedConsumersReadyToLaunch(ctx)) + + // time has reached the spawn time of "consumerId1" and "consumerId2" + ctx = ctx.WithBlockTime(time.Unix(20, 0)) + require.Equal(t, []string{"consumerId1", "consumerId2"}, providerKeeper.GetInitializedConsumersReadyToLaunch(ctx)) - // set a consumer client id which is what happens when the chain launches - providerKeeper.SetConsumerClientId(ctx, "consumerId", "clientId") - require.True(t, providerKeeper.IsConsumerLaunched(ctx, "consumerId")) + // time has reached the spawn time of all chains + ctx = ctx.WithBlockTime(time.Unix(30, 0)) + require.Equal(t, []string{"consumerId1", "consumerId2", "consumerId3"}, providerKeeper.GetInitializedConsumersReadyToLaunch(ctx)) } diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 23146d80d4..15a1cdab5f 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -384,6 +384,22 @@ func (k Keeper) GetPendingConsumerAdditionProp(ctx sdk.Context, spawnTime time.T // See: https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-bblock-init1 // Spec tag:[CCV-PCF-BBLOCK-INIT.1] func (k Keeper) BeginBlockInit(ctx sdk.Context) { + // TODO (PERMISSIONLESS) + //consumerIds := k.GetInitializedConsumersReadyToLaunch(ctx) + // + //for _, consumerId := range consumerIds { + // record, found := k.GetConsumerIdToInitializationRecord(ctx, consumerId) + // if !found { + // // something is off + // } + // // set the chain ...(have it all in one method) + // // call the method LaunchConsumer + // // cachedCtx, ... + // // k.LaunchConsumerChain(cachedTx, ...) + // // k.SetPhase(Launched) + // + // // CHANGE THEIR PHASE ... + //} propsToExecute := k.GetConsumerAdditionPropsToExecute(ctx) for i, prop := range propsToExecute { diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 71e93eb4bf..aeb7314ebb 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -766,6 +766,11 @@ func ConsumerIdToRegistrationRecordKey(consumerId string) []byte { return ConsumerIdWithLenKey(mustGetKeyPrefix(ConsumerIdToRegistrationRecordKeyName), consumerId) } +// ConsumerIdToInitializationRecordKeyNameKeyPrefix returns the key prefix for storing consumer initialization records +func ConsumerIdToInitializationRecordKeyNameKeyPrefix() []byte { + return []byte{mustGetKeyPrefix(ConsumerIdToInitializationRecordKeyName)} +} + // ConsumerIdToInitializationRecordKey returns the key used to store the initialization record that corresponds to this consumer id func ConsumerIdToInitializationRecordKey(consumerId string) []byte { return ConsumerIdWithLenKey(mustGetKeyPrefix(ConsumerIdToInitializationRecordKeyName), consumerId)