package keeper // keeper.go holds the store-backed Keeper for the services module's // Care/SIM/Vault/Mail runtime (P5-02-01, REQ-037). // // The Keeper wraps an sdk.KVStore via a storeKey. It holds: // - the registered ServiceInfo records (service-id → ServiceInfo); // - the per-service-kind metadata records (Care/SIM/Vault/Mail). // // The Keeper also holds the two expected-keeper shims (WindowKeeper for // the window-grant-on-every-op A-552; VaultKeeper for VaultService // provisioning A-553). The shims are interfaces (G-003 — no struct // import of x/window/types or x/vault/types); the concrete keepers // satisfy them structurally. A nil WindowKeeper shim skips the // window-grant Active check (simtest wiring); a nil VaultKeeper shim // REJECTS MsgProvisionVault (the VaultService requires a real vault // keeper — a nil shim is a wiring error, not a simtest skip path; the // simtest wires a stub vault keeper, never nil). // // State-machine ordering (vision §7, enforced in every handler): // ValidateBasic → keeper authz (window-grant A-552) → state mutation → // ctx.EventManager().EmitEvent import ( "encoding/json" "fmt" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/oy/openyield/x/services/types" ) // Keeper is the store-backed services Care/SIM/Vault/Mail keeper. type Keeper struct { cdc codec.Codec storeKey storetypes.StoreKey windowKeeper types.WindowKeeper vaultKeeper types.VaultKeeper } // NewKeeper constructs a new store-backed services Keeper. The // WindowKeeper and VaultKeeper expected-keeper shims are injected // (nil-able for partial tests). A nil WindowKeeper shim skips the // window-grant Active check (simtest wiring); a nil VaultKeeper shim // REJECTS MsgProvisionVault (the VaultService requires a real vault // keeper). The shims may be re-wired post-construction via SetWindowKeeper // / SetVaultKeeper (app wiring or test setup). func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, wk types.WindowKeeper, vk types.VaultKeeper) Keeper { return Keeper{ cdc: cdc, storeKey: storeKey, windowKeeper: wk, vaultKeeper: vk, } } // SetWindowKeeper sets the WindowKeeper expected-keeper shim (for // post-construction wiring, e.g., app wiring or test setup). This is the // A-552 window-grant-on-every-op shim: the handler consults it on every // service op (RegisterService / ActivateService / SuspendService / // RevokeService / IssueCareGrant / ActivateSIM / ProvisionVault / // BindMailbox) to assert the service's window-id still references an // Active Window. func (k *Keeper) SetWindowKeeper(wk types.WindowKeeper) { k.windowKeeper = wk } // SetVaultKeeper sets the VaultKeeper expected-keeper shim (for // post-construction wiring, e.g., app wiring or test setup). This is // the A-553 VaultService provisioning shim: the MsgProvisionVault // handler delegates the storage-quota-grain provisioning to it. func (k *Keeper) SetVaultKeeper(vk types.VaultKeeper) { k.vaultKeeper = vk } // WindowKeeper returns the WindowKeeper expected-keeper shim (for test // assertion of wiring; the field is unexported to preserve the // encapsulation of the shim injection). func (k Keeper) WindowKeeper() types.WindowKeeper { return k.windowKeeper } // VaultKeeper returns the VaultKeeper expected-keeper shim (for test // assertion of wiring). func (k Keeper) VaultKeeper() types.VaultKeeper { return k.vaultKeeper } // --- ServiceInfo store ----------------------------------------------------- var serviceKeyPrefix = []byte("svc/") func serviceKey(serviceID string) []byte { return append(serviceKeyPrefix, []byte(serviceID)...) } // GetService loads a registered ServiceInfo by service-id. Returns the // ServiceInfo and true if found, or zero value + false if not. func (k Keeper) GetService(ctx sdk.Context, serviceID string) (types.ServiceInfo, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(serviceKey(serviceID)) if bz == nil { return types.ServiceInfo{}, false } var s types.ServiceInfo if err := json.Unmarshal(bz, &s); err != nil { return types.ServiceInfo{}, false } return s, true } // SetService persists a registered ServiceInfo by service-id. func (k Keeper) SetService(ctx sdk.Context, s types.ServiceInfo) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(s) if err != nil { panic(fmt.Sprintf("services: marshal service info %q: %v", s.ServiceID, err)) } store.Set(serviceKey(s.ServiceID), bz) } // AllServices returns all registered ServiceInfo records (iteration // helper, unordered). func (k Keeper) AllServices(ctx sdk.Context) []types.ServiceInfo { store := ctx.KVStore(k.storeKey) iterator := store.Iterator(serviceKeyPrefix, prefixEnd(serviceKeyPrefix)) defer iterator.Close() out := []types.ServiceInfo{} for ; iterator.Valid(); iterator.Next() { var s types.ServiceInfo if err := json.Unmarshal(iterator.Value(), &s); err == nil { out = append(out, s) } } return out } // --- Per-kind metadata stores ---------------------------------------------- // Each per-kind metadata record is stored under a kind-specific prefix // keyed by the service-id (the canonical handle). A given service-id has // AT MOST one per-kind record (the kind on its ServiceInfo picks the // kind-specific metadata set). var ( careKeyPrefix = []byte("kind/care/") simKeyPrefix = []byte("kind/sim/") vaultKeyPrefix = []byte("kind/vault/") mailKeyPrefix = []byte("kind/mail/") ) func careKey(serviceID string) []byte { return append(careKeyPrefix, []byte(serviceID)...) } func simKey(serviceID string) []byte { return append(simKeyPrefix, []byte(serviceID)...) } func vaultKey(serviceID string) []byte { return append(vaultKeyPrefix, []byte(serviceID)...) } func mailKey(serviceID string) []byte { return append(mailKeyPrefix, []byte(serviceID)...) } // GetCareService loads the CareService metadata for the named service-id. func (k Keeper) GetCareService(ctx sdk.Context, serviceID string) (types.CareService, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(careKey(serviceID)) if bz == nil { return types.CareService{}, false } var c types.CareService if err := json.Unmarshal(bz, &c); err != nil { return types.CareService{}, false } return c, true } // SetCareService persists the CareService metadata. func (k Keeper) SetCareService(ctx sdk.Context, c types.CareService) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(c) if err != nil { panic(fmt.Sprintf("services: marshal care service %q: %v", c.CareID, err)) } store.Set(careKey(c.CareID), bz) } // GetSIMService loads the SIMService metadata for the named service-id. func (k Keeper) GetSIMService(ctx sdk.Context, serviceID string) (types.SIMService, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(simKey(serviceID)) if bz == nil { return types.SIMService{}, false } var s types.SIMService if err := json.Unmarshal(bz, &s); err != nil { return types.SIMService{}, false } return s, true } // SetSIMService persists the SIMService metadata. func (k Keeper) SetSIMService(ctx sdk.Context, s types.SIMService) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(s) if err != nil { panic(fmt.Sprintf("services: marshal sim service %q: %v", s.SIMID, err)) } store.Set(simKey(s.SIMID), bz) } // GetVaultService loads the VaultService metadata for the named service-id. func (k Keeper) GetVaultService(ctx sdk.Context, serviceID string) (types.VaultService, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(vaultKey(serviceID)) if bz == nil { return types.VaultService{}, false } var v types.VaultService if err := json.Unmarshal(bz, &v); err != nil { return types.VaultService{}, false } return v, true } // SetVaultService persists the VaultService metadata. func (k Keeper) SetVaultService(ctx sdk.Context, v types.VaultService) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(v) if err != nil { panic(fmt.Sprintf("services: marshal vault service %q: %v", v.VaultID, err)) } store.Set(vaultKey(v.VaultID), bz) } // GetMailService loads the MailService metadata for the named service-id. func (k Keeper) GetMailService(ctx sdk.Context, serviceID string) (types.MailService, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(mailKey(serviceID)) if bz == nil { return types.MailService{}, false } var m types.MailService if err := json.Unmarshal(bz, &m); err != nil { return types.MailService{}, false } return m, true } // SetMailService persists the MailService metadata. func (k Keeper) SetMailService(ctx sdk.Context, m types.MailService) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(m) if err != nil { panic(fmt.Sprintf("services: marshal mail service %q: %v", m.MailID, err)) } store.Set(mailKey(m.MailID), bz) } // --- prefixEnd helper ----------------------------------------------------- // prefixEnd returns the key that sorts immediately after all keys sharing // the given prefix (the standard prefix-iteration end key: increment the // last byte, drop overflow). Used for store.Iterator(start, prefixEnd(start)) // prefix scans. Mirrors x/partner/keeper/keeper.go. func prefixEnd(prefix []byte) []byte { if len(prefix) == 0 { return nil } end := make([]byte, len(prefix)) copy(end, prefix) for i := len(end) - 1; i >= 0; i-- { end[i]++ if end[i] != 0 { return end } } // All bytes were 0xFF; return nil (iterate to end of store). return nil }