6c34650a0d
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---
268 lines
9.4 KiB
Go
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
|
|
}
|