package keeper import ( "context" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/oy/openyield/x/bridge/types" ) // msg_server.go implements the bridge module's MsgServer (G-023 ownership // split: cosmos-engineer scaffolds the file structure; backend-engineer // implements the handler logic bodies). The MsgServer wraps the Keeper + // the expected-keeper shims (already on the Keeper). // // Each method returns a (*Response, error). Handler state-machine ordering // is enforced: ValidateBasic → keeper authz → state mutation → // ctx.EventManager().EmitEvent. // msgServer is the concrete MsgServer implementation wrapping the Keeper. type msgServer struct { Keeper } // NewMsgServerImpl returns the bridge MsgServer for the provided Keeper. func NewMsgServerImpl(k Keeper) types.MsgServer { return &msgServer{Keeper: k} } var _ types.MsgServer = msgServer{} // unwrapCtx extracts the sdk.Context from the interface-typed ctx (the // MsgServer interface takes interface{} to avoid coupling types/ to // sdk.Context; the keeper layer unwraps it). func unwrapCtx(ctx interface{}) sdk.Context { if c, ok := ctx.(sdk.Context); ok { return c } panic(fmt.Sprintf("bridge: expected sdk.Context, got %T", ctx)) } // --- AttestBridgeRoute (Pending → Attested) ----------------------------------- // // A Watcher 6-of-9 quorum (vision §7, REQ-004) must attest the route. The // handler consults the WatcherKeeper expected-keeper shim (by-ID-string on // the watcher-quorum-id). State-machine ordering: // ValidateBasic → load route (authz: must be Pending) → WatcherKeeper // quorum check → state mutation (status=Attested, set watcher-quorum-id) // → emit event. // AttestBridgeRoute transitions a bridge route Pending → Attested. func (s msgServer) AttestBridgeRoute(ctx interface{}, msg *types.MsgAttestBridgeRoute) (*types.MsgAttestBridgeRouteResponse, error) { if err := msg.ValidateBasic(); err != nil { return nil, err } sdkCtx := unwrapCtx(ctx) // Stateful: load route; must exist and be Pending. r, ok := s.Keeper.GetBridgeRoute(sdkCtx, msg.BridgeID) if !ok { return nil, fmt.Errorf("bridge: route %q not found", msg.BridgeID) } if r.Status != types.BridgePending { return nil, fmt.Errorf("bridge: route %q status %q, must be Pending to attest", msg.BridgeID, r.Status) } // Keeper authz: Watcher quorum check via expected-keeper shim. if s.Keeper.watcherKeeper == nil { return nil, fmt.Errorf("bridge: watcher keeper shim not wired") } // The payload is the bridge-id (the route attestation payload); a real // watcher quorum signs a canonical payload. For simtest the shim // returns true/false on the quorum-id. if !s.Keeper.watcherKeeper.IsQuorumSigned(msg.WatcherQuorumID, []byte(msg.BridgeID)) { return nil, fmt.Errorf("bridge: watcher quorum %q did not reach threshold on route %q", msg.WatcherQuorumID, msg.BridgeID) } // State mutation: status=Attested, record the watcher-quorum-id. r.Status = types.BridgeAttested r.WatcherQuorumID = msg.WatcherQuorumID s.Keeper.SetBridgeRoute(sdkCtx, r) // Emit event. sdkCtx.EventManager().EmitEvent(sdk.NewEvent( "bridge.attest", sdk.NewAttribute("bridge_id", msg.BridgeID), sdk.NewAttribute("watcher_quorum_id", msg.WatcherQuorumID), sdk.NewAttribute("status", string(types.BridgeAttested)), )) return &types.MsgAttestBridgeRouteResponse{}, nil } // --- ActivateBridge (Attested → Active) -------------------------------------- // // The route must already be Attested. State-machine ordering: // ValidateBasic → load route (authz: must be Attested) → state mutation // (status=Active) → emit event. // ActivateBridge transitions a bridge route Attested → Active. func (s msgServer) ActivateBridge(ctx interface{}, msg *types.MsgActivateBridge) (*types.MsgActivateBridgeResponse, error) { if err := msg.ValidateBasic(); err != nil { return nil, err } sdkCtx := unwrapCtx(ctx) r, ok := s.Keeper.GetBridgeRoute(sdkCtx, msg.BridgeID) if !ok { return nil, fmt.Errorf("bridge: route %q not found", msg.BridgeID) } if r.Status != types.BridgeAttested { return nil, fmt.Errorf("bridge: route %q status %q, must be Attested to activate", msg.BridgeID, r.Status) } r.Status = types.BridgeActive s.Keeper.SetBridgeRoute(sdkCtx, r) sdkCtx.EventManager().EmitEvent(sdk.NewEvent( "bridge.activate", sdk.NewAttribute("bridge_id", msg.BridgeID), sdk.NewAttribute("status", string(types.BridgeActive)), )) return &types.MsgActivateBridgeResponse{}, nil } // --- CloseBridge (Active → Closed) ------------------------------------------- // // Retire the route. State-machine ordering: // ValidateBasic → load route (authz: must be Active) → state mutation // (status=Closed) → emit event. // CloseBridge transitions a bridge route Active → Closed. func (s msgServer) CloseBridge(ctx interface{}, msg *types.MsgCloseBridge) (*types.MsgCloseBridgeResponse, error) { if err := msg.ValidateBasic(); err != nil { return nil, err } sdkCtx := unwrapCtx(ctx) r, ok := s.Keeper.GetBridgeRoute(sdkCtx, msg.BridgeID) if !ok { return nil, fmt.Errorf("bridge: route %q not found", msg.BridgeID) } if r.Status != types.BridgeActive { return nil, fmt.Errorf("bridge: route %q status %q, must be Active to close", msg.BridgeID, r.Status) } r.Status = types.BridgeClosed s.Keeper.SetBridgeRoute(sdkCtx, r) sdkCtx.EventManager().EmitEvent(sdk.NewEvent( "bridge.close", sdk.NewAttribute("bridge_id", msg.BridgeID), sdk.NewAttribute("status", string(types.BridgeClosed)), )) return &types.MsgCloseBridgeResponse{}, nil } // Compile-time assertion: msgServer implements types.MsgServer. var _ types.MsgServer = (*msgServer)(nil) // Ensure the context import is used (unwrapCtx uses context indirectly via // sdk.Context; this no-op reference keeps the import stable if handlers are // later refactored to use context.Context directly). var _ = context.Background