package keeper 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/partner/types" ) // keeper.go holds the store-backed Keeper for the partner module's // Anchor credential runtime (P3-02-01, REQ-035). // // The Keeper wraps an sdk.KVStore via a storeKey. It holds the // AnchorCredential records (by anchor-id). The v0.3 in-memory registry // Keeper stub (types.Keeper, x/partner/types/types.go) is RETAINED for // the Partner registry (non-Anchor partners — the v0.3 skeleton); the // v0.5 runtime promotes ONLY the Anchor credential lifecycle to a // store-backed keeper (D-054 simtest grade). The Partner registry stays // on the v0.3 in-memory stub (non-Anchor partner tiers are not promoted // in v0.5 — out of scope; only the Anchor credential lifecycle is). // // The Keeper also holds the two expected-keeper shims (WatcherKeeper for // 6-of-9 quorum authz on issue/revoke; HubKeeper for custody-provider-id // validity on onboard — the P3→P4 hub dep edge broken by the interface // shim per G-003 / ARCHITECTURE.md v0.5). The shims are interfaces // (G-003 — no struct import of x/watcher/types or x/hub/types); the // concrete keepers satisfy them structurally. // // State-machine ordering (vision §7, enforced in every handler): // ValidateBasic → keeper authz → state mutation → ctx.EventManager().EmitEvent // Keeper is the store-backed partner Anchor-credential keeper. type Keeper struct { cdc codec.Codec storeKey storetypes.StoreKey watcherKeeper types.WatcherKeeper hubKeeper types.HubKeeper } // NewKeeper constructs a new store-backed partner Anchor-credential // Keeper. The WatcherKeeper and HubKeeper expected-keeper shims are // injected (nil-able for partial tests; the handlers guard nil shims // and skip the corresponding authz/validity check, still mutating state // — the simtest wiring document this). The HubKeeper shim is the P3→P4 // hub dep edge: in P3 simtest it is wired to a stub (G-003 test // exemption); the real hub keeper is wired in P4. func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, wk types.WatcherKeeper, hk types.HubKeeper) Keeper { return Keeper{ cdc: cdc, storeKey: storeKey, watcherKeeper: wk, hubKeeper: hk, } } // SetWatcherKeeper sets the WatcherKeeper expected-keeper shim (for // post-construction wiring, e.g., app wiring or test setup). func (k *Keeper) SetWatcherKeeper(wk types.WatcherKeeper) { k.watcherKeeper = wk } // SetHubKeeper sets the HubKeeper expected-keeper shim (for // post-construction wiring, e.g., app wiring or test setup). This is // the P3→P4 hub dep edge: P4 wires the real hub keeper via this setter // or via NewKeeper. func (k *Keeper) SetHubKeeper(hk types.HubKeeper) { k.hubKeeper = hk } // --- Anchor credential store ------------------------------------------------- var anchorKeyPrefix = []byte("anchor/") func anchorKey(anchorID string) []byte { return append(anchorKeyPrefix, []byte(anchorID)...) } // GetAnchorCredential loads an AnchorCredential by anchor-id. Returns the // credential and true if found, or zero value + false if not. func (k Keeper) GetAnchorCredential(ctx sdk.Context, anchorID string) (types.AnchorCredential, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(anchorKey(anchorID)) if bz == nil { return types.AnchorCredential{}, false } var c types.AnchorCredential if err := json.Unmarshal(bz, &c); err != nil { return types.AnchorCredential{}, false } return c, true } // SetAnchorCredential persists an AnchorCredential by anchor-id. func (k Keeper) SetAnchorCredential(ctx sdk.Context, c types.AnchorCredential) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(c) if err != nil { panic(fmt.Sprintf("partner: marshal anchor credential %q: %v", c.AnchorID, err)) } store.Set(anchorKey(c.AnchorID), bz) } // AllAnchorCredentials returns all persisted AnchorCredential records // (iteration helper). func (k Keeper) AllAnchorCredentials(ctx sdk.Context) []types.AnchorCredential { store := ctx.KVStore(k.storeKey) iterator := store.Iterator(anchorKeyPrefix, prefixEnd(anchorKeyPrefix)) defer iterator.Close() out := []types.AnchorCredential{} for ; iterator.Valid(); iterator.Next() { var c types.AnchorCredential if err := json.Unmarshal(iterator.Value(), &c); err == nil { out = append(out, c) } } return out } // 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. 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 }