cli: add command "loop stop"

* add StopDaemon RPC with permissions + REST binding
* expose `loop stop` CLI (with optional --wait) and wait logic
* pass daemon shutdown callback + unit test and regenerate docs/protos
This commit is contained in:
Boris Nagaev 2025-11-19 20:07:01 -03:00
parent c4d6c29fc0
commit a4dc59099b
No known key found for this signature in database
15 changed files with 1997 additions and 1532 deletions

File diff suppressed because it is too large Load diff

View file

@ -487,6 +487,24 @@ func local_request_SwapClient_GetInfo_0(ctx context.Context, marshaler runtime.M
}
func request_SwapClient_StopDaemon_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopDaemonRequest
var metadata runtime.ServerMetadata
msg, err := client.StopDaemon(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_SwapClient_StopDaemon_0(ctx context.Context, marshaler runtime.Marshaler, server SwapClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopDaemonRequest
var metadata runtime.ServerMetadata
msg, err := server.StopDaemon(ctx, &protoReq)
return msg, metadata, err
}
func request_SwapClient_GetLiquidityParams_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetLiquidityParamsRequest
var metadata runtime.ServerMetadata
@ -1195,6 +1213,31 @@ func RegisterSwapClientHandlerServer(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_SwapClient_StopDaemon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/looprpc.SwapClient/StopDaemon", runtime.WithHTTPPathPattern("/v1/daemon/stop"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_SwapClient_StopDaemon_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_SwapClient_StopDaemon_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_SwapClient_GetLiquidityParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -1875,6 +1918,28 @@ func RegisterSwapClientHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_SwapClient_StopDaemon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/looprpc.SwapClient/StopDaemon", runtime.WithHTTPPathPattern("/v1/daemon/stop"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_SwapClient_StopDaemon_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_SwapClient_StopDaemon_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_SwapClient_GetLiquidityParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -2233,6 +2298,8 @@ var (
pattern_SwapClient_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "loop", "info"}, ""))
pattern_SwapClient_StopDaemon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "daemon", "stop"}, ""))
pattern_SwapClient_GetLiquidityParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "liquidity", "params"}, ""))
pattern_SwapClient_SetLiquidityParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "liquidity", "params"}, ""))
@ -2289,6 +2356,8 @@ var (
forward_SwapClient_GetInfo_0 = runtime.ForwardResponseMessage
forward_SwapClient_StopDaemon_0 = runtime.ForwardResponseMessage
forward_SwapClient_GetLiquidityParams_0 = runtime.ForwardResponseMessage
forward_SwapClient_SetLiquidityParams_0 = runtime.ForwardResponseMessage

View file

@ -100,6 +100,11 @@ service SwapClient {
*/
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse);
/* loop: `stop`
StopDaemon instructs the daemon to shut down gracefully.
*/
rpc StopDaemon (StopDaemonRequest) returns (StopDaemonResponse);
/* loop: `getparams`
GetLiquidityParams gets the parameters that the daemon's liquidity manager
is currently configured with. This may be nil if nothing is configured.
@ -202,6 +207,12 @@ service SwapClient {
returns (StaticAddressLoopInResponse);
}
message StopDaemonRequest {
}
message StopDaemonResponse {
}
message LoopOutRequest {
/*
Requested swap amount in sat. This does not include the swap and miner fee.

View file

@ -39,6 +39,29 @@
]
}
},
"/v1/daemon/stop": {
"post": {
"summary": "loop: `stop`\nStopDaemon instructs the daemon to shut down gracefully.",
"operationId": "SwapClient_StopDaemon",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/looprpcStopDaemonResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"SwapClient"
]
}
},
"/v1/instantout": {
"post": {
"summary": "loop: `instantout`\nInstantOut initiates an instant out swap with the given parameters.",
@ -2532,6 +2555,9 @@
}
}
},
"looprpcStopDaemonResponse": {
"type": "object"
},
"looprpcSuggestSwapsResponse": {
"type": "object",
"properties": {

View file

@ -66,3 +66,5 @@ http:
- selector: looprpc.SwapClient.StaticAddressLoopIn
post: "/v1/staticaddr/loopin"
body: "*"
- selector: looprpc.SwapClient.StopDaemon
post: "/v1/daemon/stop"

View file

@ -75,6 +75,9 @@ type SwapClientClient interface {
// loop: `getinfo`
// GetInfo gets basic information about the loop daemon.
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
// loop: `stop`
// StopDaemon instructs the daemon to shut down gracefully.
StopDaemon(ctx context.Context, in *StopDaemonRequest, opts ...grpc.CallOption) (*StopDaemonResponse, error)
// loop: `getparams`
// GetLiquidityParams gets the parameters that the daemon's liquidity manager
// is currently configured with. This may be nil if nothing is configured.
@ -301,6 +304,15 @@ func (c *swapClientClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts
return out, nil
}
func (c *swapClientClient) StopDaemon(ctx context.Context, in *StopDaemonRequest, opts ...grpc.CallOption) (*StopDaemonResponse, error) {
out := new(StopDaemonResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/StopDaemon", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapClientClient) GetLiquidityParams(ctx context.Context, in *GetLiquidityParamsRequest, opts ...grpc.CallOption) (*LiquidityParameters, error) {
out := new(LiquidityParameters)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetLiquidityParams", in, out, opts...)
@ -497,6 +509,9 @@ type SwapClientServer interface {
// loop: `getinfo`
// GetInfo gets basic information about the loop daemon.
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
// loop: `stop`
// StopDaemon instructs the daemon to shut down gracefully.
StopDaemon(context.Context, *StopDaemonRequest) (*StopDaemonResponse, error)
// loop: `getparams`
// GetLiquidityParams gets the parameters that the daemon's liquidity manager
// is currently configured with. This may be nil if nothing is configured.
@ -607,6 +622,9 @@ func (UnimplementedSwapClientServer) FetchL402Token(context.Context, *FetchL402T
func (UnimplementedSwapClientServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedSwapClientServer) StopDaemon(context.Context, *StopDaemonRequest) (*StopDaemonResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopDaemon not implemented")
}
func (UnimplementedSwapClientServer) GetLiquidityParams(context.Context, *GetLiquidityParamsRequest) (*LiquidityParameters, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLiquidityParams not implemented")
}
@ -938,6 +956,24 @@ func _SwapClient_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _SwapClient_StopDaemon_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StopDaemonRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).StopDaemon(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapClient/StopDaemon",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).StopDaemon(ctx, req.(*StopDaemonRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapClient_GetLiquidityParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLiquidityParamsRequest)
if err := dec(in); err != nil {
@ -1271,6 +1307,10 @@ var SwapClient_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetInfo",
Handler: _SwapClient_GetInfo_Handler,
},
{
MethodName: "StopDaemon",
Handler: _SwapClient_StopDaemon_Handler,
},
{
MethodName: "GetLiquidityParams",
Handler: _SwapClient_GetLiquidityParams_Handler,

View file

@ -176,4 +176,8 @@ var RequiredPermissions = map[string][]bakery.Op{
Entity: "swap",
Action: "read",
}},
"/looprpc.SwapClient/StopDaemon": {{
Entity: "loop",
Action: "admin",
}},
}

View file

@ -413,6 +413,31 @@ func RegisterSwapClientJSONCallbacks(registry map[string]func(ctx context.Contex
callback(string(respBytes), nil)
}
registry["looprpc.SwapClient.StopDaemon"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &StopDaemonRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSwapClientClient(conn)
resp, err := client.StopDaemon(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["looprpc.SwapClient.GetLiquidityParams"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {