c97e18fc1f
---ci--- project: oy phase: 1 milestone: v0.5 status: complete requirements: covered: [REQ-033] partial: [] ---/ci---
165 lines
5.8 KiB
Go
165 lines
5.8 KiB
Go
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
|