mukan-ibc/modules/core/02-client/keeper/keeper.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

517 lines
19 KiB
Go

package keeper
import (
"errors"
"fmt"
"strings"
corestore "cosmossdk.io/core/store"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store/prefix"
storetypes "cosmossdk.io/store/types"
upgradetypes "cosmossdk.io/x/upgrade/types"
"git.cw.tr/mukan-network/mukan-sdk/codec"
"git.cw.tr/mukan-network/mukan-sdk/runtime"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
host "git.cw.tr/mukan-network/mukan-ibc/modules/core/24-host"
"git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
ibctm "git.cw.tr/mukan-network/mukan-ibc/modules/light-clients/07-tendermint"
localhost "git.cw.tr/mukan-network/mukan-ibc/modules/light-clients/09-localhost"
)
// Keeper represents a type that grants read and write permissions to any client
// state information
type Keeper struct {
storeService corestore.KVStoreService
cdc codec.BinaryCodec
router *types.Router
legacySubspace types.ParamSubspace
upgradeKeeper types.UpgradeKeeper
}
// NewKeeper creates a new NewKeeper instance
func NewKeeper(cdc codec.BinaryCodec, storeService corestore.KVStoreService, legacySubspace types.ParamSubspace, uk types.UpgradeKeeper) *Keeper {
router := types.NewRouter()
localhostModule := localhost.NewLightClientModule(cdc, storeService)
router.AddRoute(exported.Localhost, localhostModule)
return &Keeper{
storeService: storeService,
cdc: cdc,
router: router,
legacySubspace: legacySubspace,
upgradeKeeper: uk,
}
}
// Codec returns the IBC Client module codec.
func (k *Keeper) Codec() codec.BinaryCodec {
return k.cdc
}
// Logger returns a module-specific logger.
func (Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+exported.ModuleName+"/"+types.SubModuleName)
}
// AddRoute adds a new route to the underlying router.
func (k *Keeper) AddRoute(clientType string, module exported.LightClientModule) {
k.router.AddRoute(clientType, module)
}
// GetStoreProvider returns the light client store provider.
func (k *Keeper) GetStoreProvider() types.StoreProvider {
return types.NewStoreProvider(k.storeService)
}
// Route returns the light client module for the given client identifier.
func (k *Keeper) Route(ctx sdk.Context, clientID string) (exported.LightClientModule, error) {
clientType, _, err := types.ParseClientIdentifier(clientID)
if err != nil {
return nil, errorsmod.Wrapf(err, "unable to parse client identifier %s", clientID)
}
if !k.GetParams(ctx).IsAllowedClient(clientType) {
return nil, errorsmod.Wrapf(
types.ErrInvalidClientType,
"client (%s) type %s is not in the allowed client list", clientID, clientType,
)
}
clientModule, found := k.router.GetRoute(clientType)
if !found {
return nil, errorsmod.Wrap(types.ErrRouteNotFound, clientID)
}
return clientModule, nil
}
// GenerateClientIdentifier returns the next client identifier.
func (k *Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) string {
nextClientSeq := k.GetNextClientSequence(ctx)
clientID := types.FormatClientIdentifier(clientType, nextClientSeq)
nextClientSeq++
k.SetNextClientSequence(ctx, nextClientSeq)
return clientID
}
// GetClientState gets a particular client from the store
func (k *Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) {
store := k.ClientStore(ctx, clientID)
bz := store.Get(host.ClientStateKey())
if len(bz) == 0 {
return nil, false
}
clientState := types.MustUnmarshalClientState(k.cdc, bz)
return clientState, true
}
// SetClientState sets a particular Client to the store
func (k *Keeper) SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) {
store := k.ClientStore(ctx, clientID)
store.Set(host.ClientStateKey(), types.MustMarshalClientState(k.cdc, clientState))
}
// GetClientCreator returns the creator of a client
func (k *Keeper) GetClientCreator(ctx sdk.Context, clientID string) sdk.AccAddress {
store := k.ClientStore(ctx, clientID)
bz := store.Get(types.CreatorKey())
if len(bz) == 0 {
return nil
}
return sdk.AccAddress(bz)
}
// SetClientCreator sets the creator of a client
func (k *Keeper) SetClientCreator(ctx sdk.Context, clientID string, creator sdk.AccAddress) {
store := k.ClientStore(ctx, clientID)
store.Set(types.CreatorKey(), creator.Bytes())
}
// DeleteClientCreator deletes the creator of a client
func (k *Keeper) DeleteClientCreator(ctx sdk.Context, clientID string) {
store := k.ClientStore(ctx, clientID)
store.Delete(types.CreatorKey())
}
// GetClientConsensusState gets the stored consensus state from a client at a given height.
func (k *Keeper) GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) {
store := k.ClientStore(ctx, clientID)
bz := store.Get(host.ConsensusStateKey(height))
if len(bz) == 0 {
return nil, false
}
consensusState := types.MustUnmarshalConsensusState(k.cdc, bz)
return consensusState, true
}
// SetClientConsensusState sets a ConsensusState to a particular client at the given
// height
func (k *Keeper) SetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height, consensusState exported.ConsensusState) {
store := k.ClientStore(ctx, clientID)
store.Set(host.ConsensusStateKey(height), types.MustMarshalConsensusState(k.cdc, consensusState))
}
// GetNextClientSequence gets the next client sequence from the store.
func (k *Keeper) GetNextClientSequence(ctx sdk.Context) uint64 {
store := k.storeService.OpenKVStore(ctx)
bz, err := store.Get([]byte(types.KeyNextClientSequence))
if err != nil {
panic(err)
}
if len(bz) == 0 {
panic(errors.New("next client sequence is nil"))
}
return sdk.BigEndianToUint64(bz)
}
// SetNextClientSequence sets the next client sequence to the store.
func (k *Keeper) SetNextClientSequence(ctx sdk.Context, sequence uint64) {
store := k.storeService.OpenKVStore(ctx)
bz := sdk.Uint64ToBigEndian(sequence)
if err := store.Set([]byte(types.KeyNextClientSequence), bz); err != nil {
panic(err)
}
}
// IterateConsensusStates provides an iterator over all stored consensus states.
// objects. For each State object, cb will be called. If the cb returns true,
// the iterator will close and stop.
func (k *Keeper) IterateConsensusStates(ctx sdk.Context, cb func(clientID string, cs types.ConsensusStateWithHeight) bool) {
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
iterator := storetypes.KVStorePrefixIterator(store, host.KeyClientStorePrefix)
defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() })
for ; iterator.Valid(); iterator.Next() {
keySplit := strings.Split(string(iterator.Key()), "/")
// consensus key is in the format "clients/<clientID>/consensusStates/<height>"
if len(keySplit) != 4 || keySplit[2] != string(host.KeyConsensusStatePrefix) {
continue
}
clientID := keySplit[1]
height := types.MustParseHeight(keySplit[3])
consensusState := types.MustUnmarshalConsensusState(k.cdc, iterator.Value())
consensusStateWithHeight := types.NewConsensusStateWithHeight(height, consensusState)
if cb(clientID, consensusStateWithHeight) {
break
}
}
}
// iterateMetadata provides an iterator over all stored metadata keys in the client store.
// For each metadata object, it will perform a callback.
func (k *Keeper) iterateMetadata(ctx sdk.Context, cb func(clientID string, key, value []byte) bool) {
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
iterator := storetypes.KVStorePrefixIterator(store, host.KeyClientStorePrefix)
defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() })
for ; iterator.Valid(); iterator.Next() {
split := strings.Split(string(iterator.Key()), "/")
if len(split) == 3 && split[2] == string(host.KeyClientState) {
// skip client state keys
continue
}
if len(split) == 4 && split[2] == string(host.KeyConsensusStatePrefix) {
// skip consensus state keys
continue
}
if split[0] != string(host.KeyClientStorePrefix) {
panic(errorsmod.Wrapf(host.ErrInvalidPath, "path does not begin with client store prefix: expected %s, got %s", host.KeyClientStorePrefix, split[0]))
}
if strings.TrimSpace(split[1]) == "" {
panic(errorsmod.Wrap(host.ErrInvalidPath, "clientID is empty"))
}
clientID := split[1]
key := []byte(strings.Join(split[2:], "/"))
if cb(clientID, key, iterator.Value()) {
break
}
}
}
// GetAllGenesisClients returns all the clients in state with their client ids returned as IdentifiedClientState
func (k *Keeper) GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates {
var genClients types.IdentifiedClientStates
k.IterateClientStates(ctx, nil, func(clientID string, cs exported.ClientState) bool {
genClients = append(genClients, types.NewIdentifiedClientState(clientID, cs))
return false
})
return genClients.Sort()
}
// GetAllClientMetadata will take a list of IdentifiedClientState and return a list
// of IdentifiedGenesisMetadata necessary for exporting and importing client metadata
// into the client store.
func (k *Keeper) GetAllClientMetadata(ctx sdk.Context, genClients []types.IdentifiedClientState) ([]types.IdentifiedGenesisMetadata, error) {
metadataMap := make(map[string][]types.GenesisMetadata)
k.iterateMetadata(ctx, func(clientID string, key, value []byte) bool {
metadataMap[clientID] = append(metadataMap[clientID], types.NewGenesisMetadata(key, value))
return false
})
genMetadata := make([]types.IdentifiedGenesisMetadata, 0)
for _, ic := range genClients {
metadata := metadataMap[ic.ClientId]
if len(metadata) != 0 {
genMetadata = append(genMetadata, types.NewIdentifiedGenesisMetadata(
ic.ClientId,
metadata,
))
}
}
return genMetadata, nil
}
// SetAllClientMetadata takes a list of IdentifiedGenesisMetadata and stores all of the metadata in the client store at the appropriate paths.
func (k *Keeper) SetAllClientMetadata(ctx sdk.Context, genMetadata []types.IdentifiedGenesisMetadata) {
for _, igm := range genMetadata {
// create client store
store := k.ClientStore(ctx, igm.ClientId)
// set all metadata kv pairs in client store
for _, md := range igm.ClientMetadata {
store.Set(md.GetKey(), md.GetValue())
}
}
}
// GetAllConsensusStates returns all stored client consensus states.
func (k *Keeper) GetAllConsensusStates(ctx sdk.Context) types.ClientsConsensusStates {
clientConsStates := make(types.ClientsConsensusStates, 0)
mapClientIDToConsStateIdx := make(map[string]int)
k.IterateConsensusStates(ctx, func(clientID string, cs types.ConsensusStateWithHeight) bool {
idx, ok := mapClientIDToConsStateIdx[clientID]
if ok {
clientConsStates[idx].ConsensusStates = append(clientConsStates[idx].ConsensusStates, cs)
return false
}
clientConsState := types.ClientConsensusStates{
ClientId: clientID,
ConsensusStates: []types.ConsensusStateWithHeight{cs},
}
clientConsStates = append(clientConsStates, clientConsState)
mapClientIDToConsStateIdx[clientID] = len(clientConsStates) - 1
return false
})
return clientConsStates.Sort()
}
// HasClientConsensusState returns if keeper has a ConsensusState for a particular
// client at the given height
func (k *Keeper) HasClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) bool {
store := k.ClientStore(ctx, clientID)
return store.Has(host.ConsensusStateKey(height))
}
// GetLatestClientConsensusState gets the latest ConsensusState stored for a given client
func (k *Keeper) GetLatestClientConsensusState(ctx sdk.Context, clientID string) (exported.ConsensusState, bool) {
clientModule, err := k.Route(ctx, clientID)
if err != nil {
return nil, false
}
return k.GetClientConsensusState(ctx, clientID, clientModule.LatestHeight(ctx, clientID))
}
// VerifyMembership retrieves the light client module for the clientID and verifies the proof of the existence of a key-value pair at a specified height.
func (k *Keeper) VerifyMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path, value []byte) error {
clientModule, err := k.Route(ctx, clientID)
if err != nil {
return err
}
if status := clientModule.Status(ctx, clientID); status != exported.Active {
return errorsmod.Wrapf(types.ErrClientNotActive, "cannot call verify membership on client (%s) with status %s", clientID, status)
}
return clientModule.VerifyMembership(ctx, clientID, height, delayTimePeriod, delayBlockPeriod, proof, path, value)
}
// VerifyNonMembership retrieves the light client module for the clientID and verifies the absence of a given key at a specified height.
func (k *Keeper) VerifyNonMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path) error {
clientModule, err := k.Route(ctx, clientID)
if err != nil {
return err
}
if status := clientModule.Status(ctx, clientID); status != exported.Active {
return errorsmod.Wrapf(types.ErrClientNotActive, "cannot call verify non membership on client (%s) with status %s", clientID, status)
}
return clientModule.VerifyNonMembership(ctx, clientID, height, delayTimePeriod, delayBlockPeriod, proof, path)
}
// GetUpgradePlan executes the upgrade keeper GetUpgradePlan function.
func (k *Keeper) GetUpgradePlan(ctx sdk.Context) (upgradetypes.Plan, error) {
return k.upgradeKeeper.GetUpgradePlan(ctx)
}
// GetUpgradedClient executes the upgrade keeper GetUpgradeClient function.
func (k *Keeper) GetUpgradedClient(ctx sdk.Context, planHeight int64) ([]byte, error) {
return k.upgradeKeeper.GetUpgradedClient(ctx, planHeight)
}
// GetUpgradedConsensusState returns the upgraded consensus state
func (k *Keeper) GetUpgradedConsensusState(ctx sdk.Context, planHeight int64) ([]byte, error) {
return k.upgradeKeeper.GetUpgradedConsensusState(ctx, planHeight)
}
// SetUpgradedConsensusState executes the upgrade keeper SetUpgradedConsensusState function.
func (k *Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error {
return k.upgradeKeeper.SetUpgradedConsensusState(ctx, planHeight, bz)
}
// IterateClientStates provides an iterator over all stored ibc ClientState
// objects using the provided store prefix. For each ClientState object, cb will be called. If the cb returns true,
// the iterator will close and stop.
func (k *Keeper) IterateClientStates(ctx sdk.Context, storePrefix []byte, cb func(clientID string, cs exported.ClientState) bool) {
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
iterator := storetypes.KVStorePrefixIterator(store, host.PrefixedClientStoreKey(storePrefix))
defer sdk.LogDeferred(k.Logger(ctx), func() error { return 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)
clientState := types.MustUnmarshalClientState(k.cdc, iterator.Value())
if cb(clientID, clientState) {
break
}
}
}
// GetAllClients returns all stored light client State objects.
func (k *Keeper) GetAllClients(ctx sdk.Context) []exported.ClientState {
var states []exported.ClientState
k.IterateClientStates(ctx, nil, func(_ string, state exported.ClientState) bool {
states = append(states, state)
return false
})
return states
}
// ClientStore returns isolated prefix store for each client so they can read/write in separate
// namespace without being able to read/write other client's data
func (k *Keeper) ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore {
clientPrefix := fmt.Appendf(nil, "%s/%s/", host.KeyClientStorePrefix, clientID)
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
return prefix.NewStore(store, clientPrefix)
}
// GetClientStatus returns the status for a client state given a client identifier. If the client type is not in the allowed
// clients param field, Unauthorized is returned, otherwise the client state status is returned.
func (k *Keeper) GetClientStatus(ctx sdk.Context, clientID string) exported.Status {
clientModule, err := k.Route(ctx, clientID)
if err != nil {
return exported.Unauthorized
}
return clientModule.Status(ctx, clientID)
}
// GetClientLatestHeight returns the latest height of a client state for a given client identifier. If the client type is not in the allowed
// clients param field, a zero value height is returned, otherwise the client state latest height is returned.
func (k *Keeper) GetClientLatestHeight(ctx sdk.Context, clientID string) types.Height {
clientModule, err := k.Route(ctx, clientID)
if err != nil {
return types.ZeroHeight()
}
var latestHeight types.Height
latestHeight, ok := clientModule.LatestHeight(ctx, clientID).(types.Height)
if !ok {
panic(fmt.Errorf("cannot convert %T to %T", clientModule.LatestHeight, latestHeight))
}
return latestHeight
}
// GetClientTimestampAtHeight returns the timestamp in nanoseconds of the consensus state at the given height.
func (k *Keeper) GetClientTimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) {
clientModule, err := k.Route(ctx, clientID)
if err != nil {
return 0, err
}
return clientModule.TimestampAtHeight(ctx, clientID, height)
}
// GetParams returns the total set of ibc-client parameters.
func (k *Keeper) GetParams(ctx sdk.Context) types.Params {
store := k.storeService.OpenKVStore(ctx)
bz, err := store.Get([]byte(types.ParamsKey))
if err != nil {
panic(err)
}
if bz == nil { // only panic on unset params and not on empty params
panic(errors.New("client params are not set in store"))
}
var params types.Params
k.cdc.MustUnmarshal(bz, &params)
return params
}
// SetParams sets the total set of ibc-client parameters.
func (k *Keeper) SetParams(ctx sdk.Context, params types.Params) {
store := k.storeService.OpenKVStore(ctx)
bz := k.cdc.MustMarshal(&params)
if err := store.Set([]byte(types.ParamsKey), bz); err != nil {
panic(err)
}
}
// ScheduleIBCSoftwareUpgrade schedules an upgrade for the IBC client.
func (k *Keeper) ScheduleIBCSoftwareUpgrade(ctx sdk.Context, plan upgradetypes.Plan, upgradedClientState exported.ClientState) error {
// zero out any custom fields before setting
cs, ok := upgradedClientState.(*ibctm.ClientState)
if !ok {
return errorsmod.Wrapf(types.ErrInvalidClientType, "expected: %T, got: %T", &ibctm.ClientState{}, upgradedClientState)
}
cs = cs.ZeroCustomFields()
bz, err := types.MarshalClientState(k.cdc, cs)
if err != nil {
return errorsmod.Wrap(err, "could not marshal UpgradedClientState")
}
if err := k.upgradeKeeper.ScheduleUpgrade(ctx, plan); err != nil {
return err
}
// sets the new upgraded client last height committed on this chain at plan.Height,
// since the chain will panic at plan.Height and new chain will resume at plan.Height
if err = k.upgradeKeeper.SetUpgradedClient(ctx, plan.Height, bz); err != nil {
return err
}
// emitting an event for scheduling an upgrade plan
emitScheduleIBCSoftwareUpgradeEvent(ctx, plan.Name, plan.Height)
return nil
}