Files
openyield/x/services/keeper/keeper.go
T
cloudinit-bot 6c34650a0d
docs-build / go test ./... (lexicon firewall + all x/* tests) (push) Has been cancelled
docs-build / mkdocs build (docs site artifact) (push) Has been cancelled
Merge milestone/v0.5-bearers-runtime into main (v0.5 Bearers Runtime feature milestone release)
v0.5 Bearers Runtime — 7 runtime REQs (REQ-033..039) shipped as feature.
8 modules promoted to runtime (MsgServer + simtest). cosmos-sdk v0.50.8 +
ibc-go v8.2.1 added (G-006 controlled exception). G-003 + locked-const
firewalls intact. 8 keeper packages ≥80% coverage. 5 GRILL decisions
ratified; 8 binding fixes landed; 5 P1+ flagged for v0.6+.

---ci---
project: oy
phase: 8
milestone: v0.5
status: complete
requirements:
  covered: [REQ-033, REQ-034, REQ-035, REQ-036, REQ-037, REQ-038, REQ-039]
  partial: []
---/ci---
2026-08-18 03:42:01 +00:00

268 lines
9.4 KiB
Go

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
}