From b0931f49b0f34efb14debf27e4ea34fbbc35f548 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Jul 2023 13:34:40 +0200 Subject: [PATCH 01/20] multi: let subserver manager handle disabled sub-server Modify the sub-server Manager's AddServer method to take an `enabled` boolean so that it can handle what to do with a disabled sub-server. This will come into play in a future commit which adds a status server. --- subservers/manager.go | 10 +++++++++- terminal.go | 14 +++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/subservers/manager.go b/subservers/manager.go index 9b39d29a2..577df9686 100644 --- a/subservers/manager.go +++ b/subservers/manager.go @@ -45,15 +45,23 @@ func NewManager(permsMgr *perms.Manager) *Manager { } // AddServer adds a new subServer to the manager's set. -func (s *Manager) AddServer(ss SubServer) { +func (s *Manager) AddServer(ss SubServer, enable bool) { + // If the sub-server has explicitly been disabled, then we don't add it + // to the set of servers tracked by the Manager. + if !enable { + return + } + s.mu.Lock() defer s.mu.Unlock() + // Add the enabled server to the set of servers tracked by the Manager. s.servers = append(s.servers, &subServerWrapper{ SubServer: ss, quit: make(chan struct{}), }) + // Register the sub-server's permissions with the permission manager. s.permsMgr.RegisterSubServer( ss.Name(), ss.Permissions(), ss.WhiteListedURLs(), ) diff --git a/terminal.go b/terminal.go index 66f5c35f4..732eb734f 100644 --- a/terminal.go +++ b/terminal.go @@ -1471,22 +1471,22 @@ func (g *LightningTerminal) initSubServers() { g.subServerMgr.AddServer(subservers.NewFaradaySubServer( g.cfg.Faraday, g.cfg.faradayRpcConfig, g.cfg.Remote.Faraday, g.cfg.faradayRemote, - )) + ), true) g.subServerMgr.AddServer(subservers.NewLoopSubServer( g.cfg.Loop, g.cfg.Remote.Loop, g.cfg.loopRemote, - )) + ), true) g.subServerMgr.AddServer(subservers.NewPoolSubServer( g.cfg.Pool, g.cfg.Remote.Pool, g.cfg.poolRemote, - )) + ), true) - if g.cfg.TaprootAssetsMode != ModeDisable { - g.subServerMgr.AddServer(subservers.NewTaprootAssetsSubServer( + g.subServerMgr.AddServer( + subservers.NewTaprootAssetsSubServer( g.cfg.TaprootAssets, g.cfg.Remote.TaprootAssets, g.cfg.tapRemote, - )) - } + ), g.cfg.TaprootAssetsMode != ModeDisable, + ) } // BakeSuperMacaroon uses the lnd client to bake a macaroon that can include From fb7f313c8029f332f307b16ffea94be1d8beff5c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 12:43:52 +0200 Subject: [PATCH 02/20] multi: add "disable" mode for all subservers Just like for the Taproot Assets subserver, we add the option to disable the Loop, Pool and Faraday subservers. --- config.go | 32 +++++++++++++++++++------------- terminal.go | 30 ++++++++++++++++++------------ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/config.go b/config.go index 48ff2e5a9..107e2a4f8 100644 --- a/config.go +++ b/config.go @@ -187,16 +187,16 @@ type Config struct { LndMode string `long:"lnd-mode" description:"The mode to run lnd in, either 'remote' (default) or 'integrated'. 'integrated' means lnd is started alongside the UI and everything is stored in lnd's main data directory, configure everything by using the --lnd.* flags. 'remote' means the UI connects to an existing lnd node and acts as a proxy for gRPC calls to it. In the remote node LiT creates its own directory for log and configuration files, configure everything using the --remote.* flags." choice:"integrated" choice:"remote"` Lnd *lnd.Config `group:"Integrated lnd (use when lnd-mode=integrated)" namespace:"lnd"` - FaradayMode string `long:"faraday-mode" description:"The mode to run faraday in, either 'integrated' (default) or 'remote'. 'integrated' means faraday is started alongside the UI and everything is stored in faraday's main data directory, configure everything by using the --faraday.* flags. 'remote' means the UI connects to an existing faraday node and acts as a proxy for gRPC calls to it." choice:"integrated" choice:"remote"` + FaradayMode string `long:"faraday-mode" description:"The mode to run faraday in, either 'integrated' (default), 'remote' or 'disable'. 'integrated' means faraday is started alongside the UI and everything is stored in faraday's main data directory, configure everything by using the --faraday.* flags. 'remote' means the UI connects to an existing faraday node and acts as a proxy for gRPC calls to it. 'disable' means that LiT is started without faraday." choice:"integrated" choice:"remote" choice:"disable"` Faraday *faraday.Config `group:"Integrated faraday options (use when faraday-mode=integrated)" namespace:"faraday"` - LoopMode string `long:"loop-mode" description:"The mode to run loop in, either 'integrated' (default) or 'remote'. 'integrated' means loopd is started alongside the UI and everything is stored in loop's main data directory, configure everything by using the --loop.* flags. 'remote' means the UI connects to an existing loopd node and acts as a proxy for gRPC calls to it." choice:"integrated" choice:"remote"` + LoopMode string `long:"loop-mode" description:"The mode to run loop in, either 'integrated' (default), 'remote' or 'disable'. 'integrated' means loopd is started alongside the UI and everything is stored in loop's main data directory, configure everything by using the --loop.* flags. 'remote' means the UI connects to an existing loopd node and acts as a proxy for gRPC calls to it. 'disable' means that LiT is started without loop." choice:"integrated" choice:"remote" choice:"disable"` Loop *loopd.Config `group:"Integrated loop options (use when loop-mode=integrated)" namespace:"loop"` - PoolMode string `long:"pool-mode" description:"The mode to run pool in, either 'integrated' (default) or 'remote'. 'integrated' means poold is started alongside the UI and everything is stored in pool's main data directory, configure everything by using the --pool.* flags. 'remote' means the UI connects to an existing poold node and acts as a proxy for gRPC calls to it." choice:"integrated" choice:"remote"` + PoolMode string `long:"pool-mode" description:"The mode to run pool in, either 'integrated' (default), 'remote' or 'disable'. 'integrated' means poold is started alongside the UI and everything is stored in pool's main data directory, configure everything by using the --pool.* flags. 'remote' means the UI connects to an existing poold node and acts as a proxy for gRPC calls to it. 'disable' means that LiT is started without pool." choice:"integrated" choice:"remote" choice:"disable"` Pool *pool.Config `group:"Integrated pool options (use when pool-mode=integrated)" namespace:"pool"` - TaprootAssetsMode string `long:"taproot-assets-mode" description:"The mode to run taproot assets in, either 'integrated' (default), 'remote' or 'disable'. 'integrated' means tapd is started alongside the UI and everything is stored in tap's main data directory, configure everything by using the --taproot-assets.* flags. 'remote' means the UI connects to an existing tapd node and acts as a proxy for gRPC calls to it. 'disable' means that LiT is started without a connection to tapd" choice:"integrated" choice:"disable"` + TaprootAssetsMode string `long:"taproot-assets-mode" description:"The mode to run taproot assets in, either 'integrated' (default), 'remote' or 'disable'. 'integrated' means tapd is started alongside the UI and everything is stored in tap's main data directory, configure everything by using the --taproot-assets.* flags. 'remote' means the UI connects to an existing tapd node and acts as a proxy for gRPC calls to it. 'disable' means that LiT is started without tapd" choice:"integrated" choice:"disable"` TaprootAssets *tapcfg.Config `group:"Integrated taproot assets options (use when taproot-assets=integrated)" namespace:"taproot-assets"` RPCMiddleware *mid.Config `group:"RPC middleware options" namespace:"rpcmiddleware"` @@ -458,20 +458,26 @@ func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) { // (like the log or lnd options) as they will be taken from lnd's config // struct. Others we want to force to be the same as lnd so the user // doesn't have to set them manually, like the network for example. - cfg.Faraday.Lnd.MacaroonPath = faraday.DefaultLndMacaroonPath - if err := faraday.ValidateConfig(cfg.Faraday); err != nil { - return nil, err + if cfg.FaradayMode != ModeDisable { + cfg.Faraday.Lnd.MacaroonPath = faraday.DefaultLndMacaroonPath + if err := faraday.ValidateConfig(cfg.Faraday); err != nil { + return nil, err + } } defaultLoopCfg := loopd.DefaultConfig() - cfg.Loop.Lnd.MacaroonPath = defaultLoopCfg.Lnd.MacaroonPath - if err := loopd.Validate(cfg.Loop); err != nil { - return nil, err + if cfg.LoopMode != ModeDisable { + cfg.Loop.Lnd.MacaroonPath = defaultLoopCfg.Lnd.MacaroonPath + if err := loopd.Validate(cfg.Loop); err != nil { + return nil, err + } } - cfg.Pool.Lnd.MacaroonPath = pool.DefaultLndMacaroonPath - if err := pool.Validate(cfg.Pool); err != nil { - return nil, err + if cfg.PoolMode != ModeDisable { + cfg.Pool.Lnd.MacaroonPath = pool.DefaultLndMacaroonPath + if err := pool.Validate(cfg.Pool); err != nil { + return nil, err + } } if cfg.TaprootAssetsMode != ModeDisable { diff --git a/terminal.go b/terminal.go index 732eb734f..b6f36d120 100644 --- a/terminal.go +++ b/terminal.go @@ -1468,18 +1468,24 @@ func (g *LightningTerminal) validateSuperMacaroon(ctx context.Context, // initSubServers registers the faraday and loop sub-servers with the // subServerMgr. func (g *LightningTerminal) initSubServers() { - g.subServerMgr.AddServer(subservers.NewFaradaySubServer( - g.cfg.Faraday, g.cfg.faradayRpcConfig, g.cfg.Remote.Faraday, - g.cfg.faradayRemote, - ), true) - - g.subServerMgr.AddServer(subservers.NewLoopSubServer( - g.cfg.Loop, g.cfg.Remote.Loop, g.cfg.loopRemote, - ), true) - - g.subServerMgr.AddServer(subservers.NewPoolSubServer( - g.cfg.Pool, g.cfg.Remote.Pool, g.cfg.poolRemote, - ), true) + g.subServerMgr.AddServer( + subservers.NewFaradaySubServer( + g.cfg.Faraday, g.cfg.faradayRpcConfig, + g.cfg.Remote.Faraday, g.cfg.faradayRemote, + ), g.cfg.FaradayMode != ModeDisable, + ) + + g.subServerMgr.AddServer( + subservers.NewLoopSubServer( + g.cfg.Loop, g.cfg.Remote.Loop, g.cfg.loopRemote, + ), g.cfg.LoopMode != ModeDisable, + ) + + g.subServerMgr.AddServer( + subservers.NewPoolSubServer( + g.cfg.Pool, g.cfg.Remote.Pool, g.cfg.poolRemote, + ), g.cfg.PoolMode != ModeDisable, + ) g.subServerMgr.AddServer( subservers.NewTaprootAssetsSubServer( From 20a835cf428cb1d5b8d775136329cf7dd118cd4e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 18:10:25 +0200 Subject: [PATCH 03/20] itest: test the suite when sub-servers are disabled Update the `testDisablingSubServers` test to also test the suite when the loop, pool and faraday subservers are disabled. --- itest/litd_mode_integrated_test.go | 6 +++ itest/litd_node.go | 62 ++++++++++++++++++------------ 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index f2e98a13c..3b8c8d1d0 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -259,6 +259,7 @@ var ( allowedThroughLNC: true, grpcWebURI: "/frdrpc.FaradayServer/RevenueReport", restWebURI: "/v1/faraday/revenue", + canDisable: true, }, { name: "looprpc", macaroonFn: loopMacaroonFn, @@ -267,6 +268,7 @@ var ( allowedThroughLNC: true, grpcWebURI: "/looprpc.SwapClient/ListSwaps", restWebURI: "/v1/loop/swaps", + canDisable: true, }, { name: "poolrpc", macaroonFn: poolMacaroonFn, @@ -275,6 +277,7 @@ var ( allowedThroughLNC: true, grpcWebURI: "/poolrpc.Trader/GetInfo", restWebURI: "/v1/pool/info", + canDisable: true, }, { name: "taprpc", macaroonFn: tapMacaroonFn, @@ -354,6 +357,9 @@ func testDisablingSubServers(ctx context.Context, net *NetworkHarness, err := net.RestartNode( node, nil, []LitArgOption{ WithLitArg("taproot-assets-mode", "disable"), + WithLitArg("loop-mode", "disable"), + WithLitArg("pool-mode", "disable"), + WithLitArg("faraday-mode", "disable"), }, ) require.NoError(t, err) diff --git a/itest/litd_node.go b/itest/litd_node.go index ec85a53f1..fe3f71dae 100644 --- a/itest/litd_node.go +++ b/itest/litd_node.go @@ -730,42 +730,56 @@ func (hn *HarnessNode) WaitUntilStarted(conn grpc.ClientConnInterface, return err } + faradayMode, _ := hn.Cfg.ActiveArgs.getArg("faraday-mode") + loopMode, _ := hn.Cfg.ActiveArgs.getArg("loop-mode") + poolMode, _ := hn.Cfg.ActiveArgs.getArg("pool-mode") + tapMode, _ := hn.Cfg.ActiveArgs.getArg("taproot-assets-mode") + ctxt, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() return wait.NoError(func() error { - faradayClient, err := hn.faradayClient() - if err != nil { - return err - } + if faradayMode != terminal.ModeDisable { + faradayClient, err := hn.faradayClient() + if err != nil { + return err + } - _, err = faradayClient.RevenueReport( - ctxt, &frdrpc.RevenueReportRequest{}, - ) - if err != nil { - return err + _, err = faradayClient.RevenueReport( + ctxt, &frdrpc.RevenueReportRequest{}, + ) + if err != nil { + return err + } } - loopClient, err := hn.loopClient() - if err != nil { - return err - } + if loopMode != terminal.ModeDisable { + loopClient, err := hn.loopClient() + if err != nil { + return err + } - _, err = loopClient.ListSwaps(ctxt, &looprpc.ListSwapsRequest{}) - if err != nil { - return err + _, err = loopClient.ListSwaps( + ctxt, &looprpc.ListSwapsRequest{}, + ) + if err != nil { + return err + } } - poolClient, err := hn.poolClient() - if err != nil { - return err - } + if poolMode != terminal.ModeDisable { + poolClient, err := hn.poolClient() + if err != nil { + return err + } - _, err = poolClient.GetInfo(ctxt, &poolrpc.GetInfoRequest{}) - if err != nil { - return err + _, err = poolClient.GetInfo( + ctxt, &poolrpc.GetInfoRequest{}, + ) + if err != nil { + return err + } } - tapMode, _ := hn.Cfg.ActiveArgs.getArg("taproot-assets-mode") if tapMode != terminal.ModeDisable { tapClient, err := hn.tapClient() if err != nil { From 8db75cf11c4f0fc470605f04048a03a4fe1ea8c7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 18:47:09 +0200 Subject: [PATCH 04/20] terminal: return errQueue error If an error is returned from the errQueue channel, we want that error to be returned so that LiT is shutdown with a non-0 exit code. --- terminal.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal.go b/terminal.go index b6f36d120..b90a813a6 100644 --- a/terminal.go +++ b/terminal.go @@ -622,8 +622,8 @@ func (g *LightningTerminal) start() error { select { case err := <-g.errQueue.ChanOut(): if err != nil { - log.Errorf("Received critical error from subsystem, "+ - "shutting down: %v", err) + return fmt.Errorf("received critical error from "+ + "subsystem, shutting down: %v", err) } case <-lndQuit: From dc51e884de950ce70d265c8328d5901809c1190c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 19:22:52 +0200 Subject: [PATCH 05/20] terminal: register Proxy server with Lit's grpc server We eventually want a few Lit grpc servers to continue functioning even if LND did not start up properly. This means that we need to register these servers with LiT's grpc server instead of using LND's. Because of this change, we also need to update the itest a bit so that calls to the proxy server are never expected to succeed if the call is made via the LND port. --- itest/litd_mode_integrated_test.go | 12 +++++++++--- terminal.go | 5 ++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 3b8c8d1d0..5493f7b39 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -217,6 +217,11 @@ var ( // noAuth is true if the call does not require a macaroon. noAuth bool + + // litOnly is true if the endpoint is only being served on + // Lit's grpc server and so will never be accessible via the + // LND port. + litOnly bool }{{ name: "lnrpc", macaroonFn: lndMacaroonFn, @@ -332,6 +337,7 @@ var ( allowedThroughLNC: false, grpcWebURI: "/litrpc.Proxy/GetInfo", restWebURI: "/v1/proxy/info", + litOnly: true, }} // customURIs is a map of endpoint URIs that we want to allow via a @@ -457,7 +463,7 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpoint.noAuth, endpoint.requestFn, endpoint.successPattern, - endpointDisabled, + endpointDisabled || endpoint.litOnly, "Unimplemented desc = unknown service", ) }) @@ -490,7 +496,7 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, cfg.UIPassword, endpoint.requestFn, endpoint.noAuth, true, endpoint.successPattern, - endpointDisabled, + endpointDisabled || endpoint.litOnly, "Unimplemented desc = unknown service", ) }) @@ -560,7 +566,7 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, superMacFile, endpoint.noAuth, endpoint.requestFn, endpoint.successPattern, - endpointDisabled, + endpointDisabled || endpoint.litOnly, "Unimplemented desc = unknown service", ) }) diff --git a/terminal.go b/terminal.go index b90a813a6..be784bcf4 100644 --- a/terminal.go +++ b/terminal.go @@ -242,6 +242,10 @@ func (g *LightningTerminal) Run() error { g.cfg, g, g.validateSuperMacaroon, g.permsMgr, g.subServerMgr, ) + // Register any gRPC services that should be served using LiT's + // gRPC server regardless of the LND mode being used. + litrpc.RegisterProxyServer(g.rpcProxy.grpcServer, g.rpcProxy) + // Start the main web server that dispatches requests either to the // static UI file server or the RPC proxy. This makes it possible to // unlock lnd through the UI. @@ -915,7 +919,6 @@ func (g *LightningTerminal) registerSubDaemonGrpcServers(server *grpc.Server, if withLitRPC { litrpc.RegisterSessionsServer(server, g.sessionRpcServer) litrpc.RegisterAccountsServer(server, g.accountRpcServer) - litrpc.RegisterProxyServer(server, g.rpcProxy) } litrpc.RegisterFirewallServer(server, g.sessionRpcServer) From 5f481baecb54ee456a6c961e34bebd020b275114 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 18:55:51 +0200 Subject: [PATCH 06/20] itest: remove uneccessary log subscriptions These logs were initially added for debugging LNC issues but now they just spam the itest logs so let's remove them for now. --- go.mod | 2 +- itest/litd_test.go | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 54bd64792..6b0eefe6d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 github.com/improbable-eng/grpc-web v0.12.0 github.com/jessevdk/go-flags v1.4.0 - github.com/lightninglabs/aperture v0.1.20-beta github.com/lightninglabs/faraday v0.2.11-alpha github.com/lightninglabs/lightning-node-connect v0.1.12-alpha github.com/lightninglabs/lightning-terminal/autopilotserverrpc v0.0.1 @@ -125,6 +124,7 @@ require ( github.com/klauspost/pgzip v1.2.5 // indirect github.com/lib/pq v1.10.3 // indirect github.com/libdns/libdns v0.2.1 // indirect + github.com/lightninglabs/aperture v0.1.20-beta // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 // indirect github.com/lightninglabs/neutrino v0.15.0 // indirect diff --git a/itest/litd_test.go b/itest/litd_test.go index 37c5da8dd..de907722d 100644 --- a/itest/litd_test.go +++ b/itest/litd_test.go @@ -6,10 +6,6 @@ import ( "testing" "time" - "github.com/lightninglabs/aperture" - "github.com/lightninglabs/lightning-node-connect/gbn" - "github.com/lightninglabs/lightning-node-connect/mailbox" - "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/signal" @@ -134,12 +130,6 @@ func (h *harnessTest) setupLogging() { require.NoError(h.t, err) interceptor = &ic - aperture.SetupLoggers(logWriter, *interceptor) - lnd.AddSubLogger( - logWriter, mailbox.Subsystem, *interceptor, mailbox.UseLogger, - ) - lnd.AddSubLogger(logWriter, gbn.Subsystem, *interceptor, gbn.UseLogger) - err = build.ParseAndSetDebugLevels("debug", logWriter) require.NoError(h.t, err) } From 4006622df6e1d6604a06aefc42ee37fa7779d824 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 09:14:58 +0200 Subject: [PATCH 07/20] litrpc: add Status server protos Add the protos for a new Status server that can be used to query the status of a number of LiT's subservers. --- litrpc/lit-status.pb.go | 305 +++++++++++++++++++++++++++++++++ litrpc/lit-status.pb.gw.go | 151 ++++++++++++++++ litrpc/lit-status.proto | 31 ++++ litrpc/lit-status.swagger.json | 103 +++++++++++ litrpc/lit-status.yaml | 9 + litrpc/lit-status_grpc.pb.go | 101 +++++++++++ litrpc/status.pb.json.go | 48 ++++++ 7 files changed, 748 insertions(+) create mode 100644 litrpc/lit-status.pb.go create mode 100644 litrpc/lit-status.pb.gw.go create mode 100644 litrpc/lit-status.proto create mode 100644 litrpc/lit-status.swagger.json create mode 100644 litrpc/lit-status.yaml create mode 100644 litrpc/lit-status_grpc.pb.go create mode 100644 litrpc/status.pb.json.go diff --git a/litrpc/lit-status.pb.go b/litrpc/lit-status.pb.go new file mode 100644 index 000000000..fee2cd92a --- /dev/null +++ b/litrpc/lit-status.pb.go @@ -0,0 +1,305 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.6.1 +// source: lit-status.proto + +package litrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SubServerStatusReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SubServerStatusReq) Reset() { + *x = SubServerStatusReq{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_status_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubServerStatusReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubServerStatusReq) ProtoMessage() {} + +func (x *SubServerStatusReq) ProtoReflect() protoreflect.Message { + mi := &file_lit_status_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubServerStatusReq.ProtoReflect.Descriptor instead. +func (*SubServerStatusReq) Descriptor() ([]byte, []int) { + return file_lit_status_proto_rawDescGZIP(), []int{0} +} + +type SubServerStatusResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A map of sub-server names to their status. + SubServers map[string]*SubServerStatus `protobuf:"bytes,1,rep,name=sub_servers,json=subServers,proto3" json:"sub_servers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *SubServerStatusResp) Reset() { + *x = SubServerStatusResp{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_status_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubServerStatusResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubServerStatusResp) ProtoMessage() {} + +func (x *SubServerStatusResp) ProtoReflect() protoreflect.Message { + mi := &file_lit_status_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubServerStatusResp.ProtoReflect.Descriptor instead. +func (*SubServerStatusResp) Descriptor() ([]byte, []int) { + return file_lit_status_proto_rawDescGZIP(), []int{1} +} + +func (x *SubServerStatusResp) GetSubServers() map[string]*SubServerStatus { + if x != nil { + return x.SubServers + } + return nil +} + +type SubServerStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // disabled is true if the sub-server is available in the LiT package but + // has explicitly been disabled. + Disabled bool `protobuf:"varint,1,opt,name=disabled,proto3" json:"disabled,omitempty"` + // running is true if the sub-server is currently running. + Running bool `protobuf:"varint,2,opt,name=running,proto3" json:"running,omitempty"` + // error describes an error that might have resulted in the sub-server not + // starting up properly. + Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *SubServerStatus) Reset() { + *x = SubServerStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_status_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubServerStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubServerStatus) ProtoMessage() {} + +func (x *SubServerStatus) ProtoReflect() protoreflect.Message { + mi := &file_lit_status_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubServerStatus.ProtoReflect.Descriptor instead. +func (*SubServerStatus) Descriptor() ([]byte, []int) { + return file_lit_status_proto_rawDescGZIP(), []int{2} +} + +func (x *SubServerStatus) GetDisabled() bool { + if x != nil { + return x.Disabled + } + return false +} + +func (x *SubServerStatus) GetRunning() bool { + if x != nil { + return x.Running + } + return false +} + +func (x *SubServerStatus) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +var File_lit_status_proto protoreflect.FileDescriptor + +var file_lit_status_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x6c, 0x69, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x75, + 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x22, 0xbb, 0x01, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4c, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x1a, 0x56, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5d, + 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0x54, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4a, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, + 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_lit_status_proto_rawDescOnce sync.Once + file_lit_status_proto_rawDescData = file_lit_status_proto_rawDesc +) + +func file_lit_status_proto_rawDescGZIP() []byte { + file_lit_status_proto_rawDescOnce.Do(func() { + file_lit_status_proto_rawDescData = protoimpl.X.CompressGZIP(file_lit_status_proto_rawDescData) + }) + return file_lit_status_proto_rawDescData +} + +var file_lit_status_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_lit_status_proto_goTypes = []interface{}{ + (*SubServerStatusReq)(nil), // 0: litrpc.SubServerStatusReq + (*SubServerStatusResp)(nil), // 1: litrpc.SubServerStatusResp + (*SubServerStatus)(nil), // 2: litrpc.SubServerStatus + nil, // 3: litrpc.SubServerStatusResp.SubServersEntry +} +var file_lit_status_proto_depIdxs = []int32{ + 3, // 0: litrpc.SubServerStatusResp.sub_servers:type_name -> litrpc.SubServerStatusResp.SubServersEntry + 2, // 1: litrpc.SubServerStatusResp.SubServersEntry.value:type_name -> litrpc.SubServerStatus + 0, // 2: litrpc.Status.SubServerStatus:input_type -> litrpc.SubServerStatusReq + 1, // 3: litrpc.Status.SubServerStatus:output_type -> litrpc.SubServerStatusResp + 3, // [3:4] is the sub-list for method output_type + 2, // [2:3] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_lit_status_proto_init() } +func file_lit_status_proto_init() { + if File_lit_status_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_lit_status_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubServerStatusReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_status_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubServerStatusResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_status_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubServerStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_lit_status_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_lit_status_proto_goTypes, + DependencyIndexes: file_lit_status_proto_depIdxs, + MessageInfos: file_lit_status_proto_msgTypes, + }.Build() + File_lit_status_proto = out.File + file_lit_status_proto_rawDesc = nil + file_lit_status_proto_goTypes = nil + file_lit_status_proto_depIdxs = nil +} diff --git a/litrpc/lit-status.pb.gw.go b/litrpc/lit-status.pb.gw.go new file mode 100644 index 000000000..564e3b54a --- /dev/null +++ b/litrpc/lit-status.pb.gw.go @@ -0,0 +1,151 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: lit-status.proto + +/* +Package litrpc is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package litrpc + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_Status_SubServerStatus_0(ctx context.Context, marshaler runtime.Marshaler, client StatusClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubServerStatusReq + var metadata runtime.ServerMetadata + + msg, err := client.SubServerStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Status_SubServerStatus_0(ctx context.Context, marshaler runtime.Marshaler, server StatusServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubServerStatusReq + var metadata runtime.ServerMetadata + + msg, err := server.SubServerStatus(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterStatusHandlerServer registers the http handlers for service Status to "mux". +// UnaryRPC :call StatusServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterStatusHandlerFromEndpoint instead. +func RegisterStatusHandlerServer(ctx context.Context, mux *runtime.ServeMux, server StatusServer) error { + + mux.Handle("GET", pattern_Status_SubServerStatus_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/litrpc.Status/SubServerStatus", runtime.WithHTTPPathPattern("/v1/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Status_SubServerStatus_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Status_SubServerStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterStatusHandlerFromEndpoint is same as RegisterStatusHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterStatusHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterStatusHandler(ctx, mux, conn) +} + +// RegisterStatusHandler registers the http handlers for service Status to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterStatusHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterStatusHandlerClient(ctx, mux, NewStatusClient(conn)) +} + +// RegisterStatusHandlerClient registers the http handlers for service Status +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "StatusClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "StatusClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "StatusClient" to call the correct interceptors. +func RegisterStatusHandlerClient(ctx context.Context, mux *runtime.ServeMux, client StatusClient) error { + + mux.Handle("GET", pattern_Status_SubServerStatus_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/litrpc.Status/SubServerStatus", runtime.WithHTTPPathPattern("/v1/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Status_SubServerStatus_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Status_SubServerStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Status_SubServerStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "status"}, "")) +) + +var ( + forward_Status_SubServerStatus_0 = runtime.ForwardResponseMessage +) diff --git a/litrpc/lit-status.proto b/litrpc/lit-status.proto new file mode 100644 index 000000000..c7ec55838 --- /dev/null +++ b/litrpc/lit-status.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package litrpc; + +option go_package = "github.com/lightninglabs/lightning-terminal/litrpc"; + +// The Status server can be used to query the state of various LiT sub-servers. +service Status { + rpc SubServerStatus (SubServerStatusReq) returns (SubServerStatusResp); +} + +message SubServerStatusReq { +} + +message SubServerStatusResp { + // A map of sub-server names to their status. + map sub_servers = 1; +} + +message SubServerStatus { + // disabled is true if the sub-server is available in the LiT package but + // has explicitly been disabled. + bool disabled = 1; + + // running is true if the sub-server is currently running. + bool running = 2; + + // error describes an error that might have resulted in the sub-server not + // starting up properly. + string error = 3; +} diff --git a/litrpc/lit-status.swagger.json b/litrpc/lit-status.swagger.json new file mode 100644 index 000000000..9a7ef8f9d --- /dev/null +++ b/litrpc/lit-status.swagger.json @@ -0,0 +1,103 @@ +{ + "swagger": "2.0", + "info": { + "title": "lit-status.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "Status" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/status": { + "get": { + "operationId": "Status_SubServerStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/litrpcSubServerStatusResp" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "Status" + ] + } + } + }, + "definitions": { + "litrpcSubServerStatus": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean", + "description": "disabled is true if the sub-server is available in the LiT package but\nhas explicitly been disabled." + }, + "running": { + "type": "boolean", + "description": "running is true if the sub-server is currently running." + }, + "error": { + "type": "string", + "description": "error describes an error that might have resulted in the sub-server not\nstarting up properly." + } + } + }, + "litrpcSubServerStatusResp": { + "type": "object", + "properties": { + "sub_servers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/litrpcSubServerStatus" + }, + "description": "A map of sub-server names to their status." + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/litrpc/lit-status.yaml b/litrpc/lit-status.yaml new file mode 100644 index 000000000..52fc33e4c --- /dev/null +++ b/litrpc/lit-status.yaml @@ -0,0 +1,9 @@ +type: google.api.Service +config_version: 3 + +http: + rules: + + # lit-status.proto + - selector: litrpc.Status.SubServerStatus + get: "/v1/status" diff --git a/litrpc/lit-status_grpc.pb.go b/litrpc/lit-status_grpc.pb.go new file mode 100644 index 000000000..238155a75 --- /dev/null +++ b/litrpc/lit-status_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package litrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// StatusClient is the client API for Status service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StatusClient interface { + SubServerStatus(ctx context.Context, in *SubServerStatusReq, opts ...grpc.CallOption) (*SubServerStatusResp, error) +} + +type statusClient struct { + cc grpc.ClientConnInterface +} + +func NewStatusClient(cc grpc.ClientConnInterface) StatusClient { + return &statusClient{cc} +} + +func (c *statusClient) SubServerStatus(ctx context.Context, in *SubServerStatusReq, opts ...grpc.CallOption) (*SubServerStatusResp, error) { + out := new(SubServerStatusResp) + err := c.cc.Invoke(ctx, "/litrpc.Status/SubServerStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StatusServer is the server API for Status service. +// All implementations must embed UnimplementedStatusServer +// for forward compatibility +type StatusServer interface { + SubServerStatus(context.Context, *SubServerStatusReq) (*SubServerStatusResp, error) + mustEmbedUnimplementedStatusServer() +} + +// UnimplementedStatusServer must be embedded to have forward compatible implementations. +type UnimplementedStatusServer struct { +} + +func (UnimplementedStatusServer) SubServerStatus(context.Context, *SubServerStatusReq) (*SubServerStatusResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubServerStatus not implemented") +} +func (UnimplementedStatusServer) mustEmbedUnimplementedStatusServer() {} + +// UnsafeStatusServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StatusServer will +// result in compilation errors. +type UnsafeStatusServer interface { + mustEmbedUnimplementedStatusServer() +} + +func RegisterStatusServer(s grpc.ServiceRegistrar, srv StatusServer) { + s.RegisterService(&Status_ServiceDesc, srv) +} + +func _Status_SubServerStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubServerStatusReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatusServer).SubServerStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Status/SubServerStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatusServer).SubServerStatus(ctx, req.(*SubServerStatusReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Status_ServiceDesc is the grpc.ServiceDesc for Status service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Status_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "litrpc.Status", + HandlerType: (*StatusServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SubServerStatus", + Handler: _Status_SubServerStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "lit-status.proto", +} diff --git a/litrpc/status.pb.json.go b/litrpc/status.pb.json.go new file mode 100644 index 000000000..539f1c6fd --- /dev/null +++ b/litrpc/status.pb.json.go @@ -0,0 +1,48 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: lit-status.proto + +package litrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterStatusJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["litrpc.Status.SubServerStatus"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SubServerStatusReq{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewStatusClient(conn) + resp, err := client.SubServerStatus(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} From 21525a5d5331ad66bdfb30ef410b642f338a8fd7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 09:21:29 +0200 Subject: [PATCH 08/20] status: add status server implementation --- status/log.go | 24 ++++++ status/manager.go | 191 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 status/log.go create mode 100644 status/manager.go diff --git a/status/log.go b/status/log.go new file mode 100644 index 000000000..e7651d227 --- /dev/null +++ b/status/log.go @@ -0,0 +1,24 @@ +package status + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "STAT" + +// log is a logger that is initialized with no output filters. This means the +// package will not perform any logging by default until the caller requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/status/manager.go b/status/manager.go new file mode 100644 index 000000000..c70f0d4ce --- /dev/null +++ b/status/manager.go @@ -0,0 +1,191 @@ +package status + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/lightninglabs/lightning-terminal/litrpc" +) + +// SubServerStatus represents the status of a sub-server. +type SubServerStatus struct { + // Disabled is true if the sub-server is available in the LiT bundle but + // has explicitly been disabled by the user. + Disabled bool + + // Running is true if the sub-server is enabled and has successfully + // been started. + Running bool + + // Err will be a non-empty string if the sub-server failed to start. + Err string +} + +// newSubServerStatus constructs a new SubServerStatus. +func newSubServerStatus() *SubServerStatus { + return &SubServerStatus{ + Disabled: true, + } +} + +// Manager manages the status of any sub-server registered to it. It is also an +// implementation of the litrpc.StatusServer which can be queried for the status +// of various LiT sub-servers. +type Manager struct { + litrpc.UnimplementedStatusServer + + subServers map[string]*SubServerStatus + mu sync.RWMutex +} + +// NewStatusManager constructs a new Manager. +func NewStatusManager() *Manager { + return &Manager{ + subServers: make(map[string]*SubServerStatus), + } +} + +// SubServerStatus queries the current status of a given sub-server. +// +// NOTE: this is part of the litrpc.StatusServer interface. +func (s *Manager) SubServerStatus(_ context.Context, + _ *litrpc.SubServerStatusReq) (*litrpc.SubServerStatusResp, + error) { + + s.mu.RLock() + defer s.mu.RUnlock() + + resp := make(map[string]*litrpc.SubServerStatus, len(s.subServers)) + for server, status := range s.subServers { + resp[server] = &litrpc.SubServerStatus{ + Disabled: status.Disabled, + Running: status.Running, + Error: status.Err, + } + } + + return &litrpc.SubServerStatusResp{ + SubServers: resp, + }, nil +} + +// RegisterSubServer will create a new sub-server entry for the Manager to +// keep track of. +func (s *Manager) RegisterSubServer(name string) { + s.mu.RLock() + defer s.mu.RUnlock() + + s.subServers[name] = newSubServerStatus() +} + +// RegisterAndEnableSubServer will create a new sub-server entry for the +// Manager to keep track of and will set it as enabled. +func (s *Manager) RegisterAndEnableSubServer(name string) { + s.mu.RLock() + defer s.mu.RUnlock() + + ss := newSubServerStatus() + ss.Disabled = false + + s.subServers[name] = ss + +} + +// GetStatus returns the current status of a given sub-server. This will +// silently fail if the referenced sub-server has not yet been registered. +func (s *Manager) GetStatus(name string) (*SubServerStatus, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + status, ok := s.subServers[name] + if !ok { + return nil, errors.New("a sub-server with name %s has not " + + "yet been registered") + } + + return status, nil +} + +// SetEnabled marks the sub-server with the given name as enabled. +// +// NOTE: This will silently fail if the referenced sub-server has not yet been +// registered. +func (s *Manager) SetEnabled(name string) { + s.mu.Lock() + defer s.mu.Unlock() + + log.Debugf("Setting the %s sub-server as enabled", name) + + ss, ok := s.subServers[name] + if !ok { + return + } + + ss.Disabled = false +} + +// SetRunning can be used to set the status of a sub-server as Running +// with no errors. +// +// NOTE: This will silently fail if the referenced sub-server has not yet been +// registered. +func (s *Manager) SetRunning(name string) { + s.mu.Lock() + defer s.mu.Unlock() + + log.Debugf("Setting the %s sub-server as running", name) + + ss, ok := s.subServers[name] + if !ok { + return + } + + ss.Running = true +} + +// SetStopped can be used to set the status of a sub-server as not Running and +// with no errors. +// +// NOTE: This will silently fail if the referenced sub-server has not yet been +// registered. +func (s *Manager) SetStopped(name string) { + s.mu.Lock() + defer s.mu.Unlock() + + log.Debugf("Setting the %s sub-server as stopped", name) + + ss, ok := s.subServers[name] + if !ok { + return + } + + ss.Running = false + ss.Err = "" +} + +// SetErrored can be used to set the status of a sub-server as not Running +// and also to set an error message for the sub-server. +// +// NOTE: This will silently fail if the referenced sub-server has not yet been +// registered. +func (s *Manager) SetErrored(name string, errStr string, + params ...interface{}) { + + s.mu.Lock() + defer s.mu.Unlock() + + log.Debugf("Setting the %s sub-server as errored: %s", name, errStr) + + ss, ok := s.subServers[name] + if !ok { + return + } + + err := fmt.Sprintf(errStr, params...) + log.Errorf("could not start the %s sub-server: %s", name, err) + + ss.Running = false + ss.Err = err +} From 6310eedbfc9bdfbd1193961f8897fab896e24868 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 09:38:33 +0200 Subject: [PATCH 09/20] multi: init status manager Initialise an instance of the status manager in LightningTerminal. Register its gRPC and REST endpoints and also add its method to the set of LiT whitelisted permissions. --- litclient/jsoncallbacks.go | 1 + log.go | 3 ++- perms/permissions.go | 6 +++++- rpc_proxy.go | 12 ++++++++++-- terminal.go | 30 ++++++++++++++++++++++-------- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/litclient/jsoncallbacks.go b/litclient/jsoncallbacks.go index 52ca02529..2a6e4028d 100644 --- a/litclient/jsoncallbacks.go +++ b/litclient/jsoncallbacks.go @@ -50,6 +50,7 @@ var Registrations = []StubPackageRegistration{ litrpc.RegisterAccountsJSONCallbacks, litrpc.RegisterAutopilotJSONCallbacks, litrpc.RegisterFirewallJSONCallbacks, + litrpc.RegisterStatusJSONCallbacks, taprpc.RegisterTaprootAssetsJSONCallbacks, assetwalletrpc.RegisterAssetWalletJSONCallbacks, universerpc.RegisterUniverseJSONCallbacks, diff --git a/log.go b/log.go index 6967e214a..56a1080e8 100644 --- a/log.go +++ b/log.go @@ -11,6 +11,7 @@ import ( mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightninglabs/lightning-terminal/status" "github.com/lightninglabs/lightning-terminal/subservers" "github.com/lightninglabs/loop/loopd" "github.com/lightninglabs/pool" @@ -82,7 +83,7 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { root, autopilotserver.Subsystem, intercept, autopilotserver.UseLogger, ) - + lnd.AddSubLogger(root, status.Subsystem, intercept, status.UseLogger) lnd.AddSubLogger( root, subservers.Subsystem, intercept, subservers.UseLogger, ) diff --git a/perms/permissions.go b/perms/permissions.go index aaf352811..d05318613 100644 --- a/perms/permissions.go +++ b/perms/permissions.go @@ -94,7 +94,11 @@ var ( // whiteListedLitMethods is a map of all LiT's RPC methods that don't // require any macaroon authentication. - whiteListedLitMethods = map[string]struct{}{} + whiteListedLitMethods = map[string][]bakery.Op{ + // The Status service must be available at all times, even + // before we can check macaroons, so we whitelist it. + "/litrpc.Status/SubServerStatus": {}, + } // lndSubServerNameToTag is a map from the name of an LND subserver to // the name of the LND tag that corresponds to the subserver. This map diff --git a/rpc_proxy.go b/rpc_proxy.go index f4a96fc41..d73145f08 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -375,7 +375,7 @@ func (p *rpcProxy) UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - if !p.hasStarted() { + if !p.hasStarted() && !isStatusReq(info.FullMethod) { return nil, ErrWaitingToStart } @@ -419,7 +419,7 @@ func (p *rpcProxy) StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - if !p.hasStarted() { + if !p.hasStarted() && !isStatusReq(info.FullMethod) { return ErrWaitingToStart } @@ -630,3 +630,11 @@ func isGrpcRequest(req *http.Request) bool { return req.ProtoMajor == 2 && strings.HasPrefix(contentType, contentTypeGrpc) } + +// isStatusReq returns true if the given request is intended for the +// litrpc.Status service. +func isStatusReq(uri string) bool { + return strings.HasPrefix( + uri, fmt.Sprintf("/%s", litrpc.Status_ServiceDesc.ServiceName), + ) +} diff --git a/terminal.go b/terminal.go index be784bcf4..e9a2f2bd1 100644 --- a/terminal.go +++ b/terminal.go @@ -30,6 +30,7 @@ import ( mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightninglabs/lightning-terminal/status" "github.com/lightninglabs/lightning-terminal/subservers" "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd" @@ -161,6 +162,7 @@ type LightningTerminal struct { basicClient lnrpc.LightningClient subServerMgr *subservers.Manager + statusMgr *status.Manager autopilotClient autopilotserver.Autopilot @@ -193,7 +195,9 @@ type LightningTerminal struct { // New creates a new instance of the lightning-terminal daemon. func New() *LightningTerminal { - return &LightningTerminal{} + return &LightningTerminal{ + statusMgr: status.NewStatusManager(), + } } // Run starts everything and then blocks until either the application is shut @@ -245,6 +249,7 @@ func (g *LightningTerminal) Run() error { // Register any gRPC services that should be served using LiT's // gRPC server regardless of the LND mode being used. litrpc.RegisterProxyServer(g.rpcProxy.grpcServer, g.rpcProxy) + litrpc.RegisterStatusServer(g.rpcProxy.grpcServer, g.statusMgr) // Start the main web server that dispatches requests either to the // static UI file server or the RPC proxy. This makes it possible to @@ -385,7 +390,7 @@ func (g *LightningTerminal) start() error { ), }, registerGrpcServers: func(server *grpc.Server) { - g.registerSubDaemonGrpcServers(server, false) + g.registerSubDaemonGrpcServers(server, true) }, superMacBaker: superMacBaker, firstConnectionDeadline: g.cfg.FirstLNCConnDeadline, @@ -901,22 +906,24 @@ func (g *LightningTerminal) RegisterGrpcSubserver(server *grpc.Server) error { // Register all other daemon RPC servers that are running in-process. // The LiT session server should be enabled on the main interface. - g.registerSubDaemonGrpcServers(server, true) + g.registerSubDaemonGrpcServers(server, false) return nil } // registerSubDaemonGrpcServers registers the sub daemon (Faraday, Loop, Pool // and LiT session) servers to a given gRPC server, given they are running in -// the local process. The lit session server is gated by its own boolean because -// we don't necessarily want to expose it on all listeners, given its security -// implications. +// the local process. Some of LiT's own sub-servers should be registered with +// LNC sessions and some should not - the forLNCSession boolean can be used to +// control this. func (g *LightningTerminal) registerSubDaemonGrpcServers(server *grpc.Server, - withLitRPC bool) { + forLNCSession bool) { g.subServerMgr.RegisterRPCServices(server) - if withLitRPC { + if forLNCSession { + litrpc.RegisterStatusServer(server, g.statusMgr) + } else { litrpc.RegisterSessionsServer(server, g.sessionRpcServer) litrpc.RegisterAccountsServer(server, g.accountRpcServer) } @@ -980,6 +987,13 @@ func (g *LightningTerminal) RegisterRestSubserver(ctx context.Context, return err } + err = litrpc.RegisterStatusHandlerFromEndpoint( + ctx, mux, endpoint, dialOpts, + ) + if err != nil { + return err + } + return g.subServerMgr.RegisterRestServices(ctx, mux, endpoint, dialOpts) } From 143876d1ec8b4e583fc264de8ccb1118a93a714a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 19:01:35 +0200 Subject: [PATCH 10/20] litcli: prepare for a client connection without a macaroon In preparation for an upcoming commit where litcli commands for the Status server are added, the `getClientConn` method is updated with a `noMac` parameter. If true, them the connection will be made without a macaroon. --- cmd/litcli/accounts.go | 10 +++++----- cmd/litcli/actions.go | 2 +- cmd/litcli/autopilot.go | 8 ++++---- cmd/litcli/main.go | 26 +++++++++++++++----------- cmd/litcli/privacy_map.go | 4 ++-- cmd/litcli/proxy.go | 6 +++--- cmd/litcli/sessions.go | 6 +++--- 7 files changed, 33 insertions(+), 29 deletions(-) diff --git a/cmd/litcli/accounts.go b/cmd/litcli/accounts.go index 306ec90e1..a26cfaf42 100644 --- a/cmd/litcli/accounts.go +++ b/cmd/litcli/accounts.go @@ -78,7 +78,7 @@ var createAccountCommand = cli.Command{ func createAccount(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -185,7 +185,7 @@ var updateAccountCommand = cli.Command{ func updateAccount(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -253,7 +253,7 @@ var listAccountsCommand = cli.Command{ func listAccounts(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -293,7 +293,7 @@ var accountInfoCommand = cli.Command{ func accountInfo(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -341,7 +341,7 @@ var removeAccountCommand = cli.Command{ func removeAccount(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } diff --git a/cmd/litcli/actions.go b/cmd/litcli/actions.go index 27d6b4e71..ebce2da4c 100644 --- a/cmd/litcli/actions.go +++ b/cmd/litcli/actions.go @@ -99,7 +99,7 @@ var listActionsCommand = cli.Command{ func listActions(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } diff --git a/cmd/litcli/autopilot.go b/cmd/litcli/autopilot.go index 97a185d30..befc5a038 100644 --- a/cmd/litcli/autopilot.go +++ b/cmd/litcli/autopilot.go @@ -116,7 +116,7 @@ var listAutopilotSessionsCmd = cli.Command{ func revokeAutopilotSession(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -144,7 +144,7 @@ func revokeAutopilotSession(ctx *cli.Context) error { func listAutopilotSessions(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -165,7 +165,7 @@ func listAutopilotSessions(ctx *cli.Context) error { func listFeatures(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -189,7 +189,7 @@ func initAutopilotSession(ctx *cli.Context) error { sessionExpiry := time.Now().Add(sessionLength).Unix() ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } diff --git a/cmd/litcli/main.go b/cmd/litcli/main.go index 8c40775da..921a670e1 100644 --- a/cmd/litcli/main.go +++ b/cmd/litcli/main.go @@ -89,13 +89,15 @@ func fatal(err error) { os.Exit(1) } -func connectClient(ctx *cli.Context) (grpc.ClientConnInterface, func(), error) { +func connectClient(ctx *cli.Context, noMac bool) (grpc.ClientConnInterface, + func(), error) { + rpcServer := ctx.GlobalString("rpcserver") tlsCertPath, macPath, err := extractPathArgs(ctx) if err != nil { return nil, nil, err } - conn, err := getClientConn(rpcServer, tlsCertPath, macPath) + conn, err := getClientConn(rpcServer, tlsCertPath, macPath, noMac) if err != nil { return nil, nil, err } @@ -104,18 +106,20 @@ func connectClient(ctx *cli.Context) (grpc.ClientConnInterface, func(), error) { return conn, cleanup, nil } -func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, - error) { - - // We always need to send a macaroon. - macOption, err := readMacaroon(macaroonPath) - if err != nil { - return nil, err - } +func getClientConn(address, tlsCertPath, macaroonPath string, noMac bool) ( + *grpc.ClientConn, error) { opts := []grpc.DialOption{ grpc.WithDefaultCallOptions(maxMsgRecvSize), - macOption, + } + + if !noMac { + macOption, err := readMacaroon(macaroonPath) + if err != nil { + return nil, err + } + + opts = append(opts, macOption) } // TLS cannot be disabled, we'll always have a cert file to read. diff --git a/cmd/litcli/privacy_map.go b/cmd/litcli/privacy_map.go index 6bf0c9f7a..e71574913 100644 --- a/cmd/litcli/privacy_map.go +++ b/cmd/litcli/privacy_map.go @@ -58,7 +58,7 @@ var privacyMapConvertStrCommand = cli.Command{ func privacyMapConvertStr(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -112,7 +112,7 @@ var privacyMapConvertUint64Command = cli.Command{ func privacyMapConvertUint64(ctx *cli.Context) error { ctxb := context.Background() - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } diff --git a/cmd/litcli/proxy.go b/cmd/litcli/proxy.go index 4fc165be6..08a4ff58e 100644 --- a/cmd/litcli/proxy.go +++ b/cmd/litcli/proxy.go @@ -53,7 +53,7 @@ var litCommands = []cli.Command{ } func getInfo(ctx *cli.Context) error { - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -72,7 +72,7 @@ func getInfo(ctx *cli.Context) error { } func shutdownLit(ctx *cli.Context) error { - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -109,7 +109,7 @@ func bakeSuperMacaroon(ctx *cli.Context) error { } suffix := binary.BigEndian.Uint32(suffixBytes[:]) - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } diff --git a/cmd/litcli/sessions.go b/cmd/litcli/sessions.go index 3a18fb4e0..0075be327 100644 --- a/cmd/litcli/sessions.go +++ b/cmd/litcli/sessions.go @@ -96,7 +96,7 @@ var addSessionCommand = cli.Command{ } func addSession(ctx *cli.Context) error { - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -229,7 +229,7 @@ var sessionStateMap = map[litrpc.SessionState]sessionFilter{ func listSessions(filter sessionFilter) func(ctx *cli.Context) error { return func(ctx *cli.Context) error { - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } @@ -279,7 +279,7 @@ var revokeSessionCommand = cli.Command{ } func revokeSession(ctx *cli.Context) error { - clientConn, cleanup, err := connectClient(ctx) + clientConn, cleanup, err := connectClient(ctx, false) if err != nil { return err } From 4e2ec08767e9c5b90056ddc8f034395b5e201db7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 2 May 2023 20:40:16 +0200 Subject: [PATCH 11/20] cmd/litcli: commands for status server Note that the client connection for these commands does not require a macaroon. --- cmd/litcli/main.go | 1 + cmd/litcli/status.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 cmd/litcli/status.go diff --git a/cmd/litcli/main.go b/cmd/litcli/main.go index 921a670e1..6c632c71c 100644 --- a/cmd/litcli/main.go +++ b/cmd/litcli/main.go @@ -77,6 +77,7 @@ func main() { app.Commands = append(app.Commands, autopilotCommands) app.Commands = append(app.Commands, litCommands...) app.Commands = append(app.Commands, helperCommands) + app.Commands = append(app.Commands, statusCommands...) err := app.Run(os.Args) if err != nil { diff --git a/cmd/litcli/status.go b/cmd/litcli/status.go new file mode 100644 index 000000000..ffd031bb7 --- /dev/null +++ b/cmd/litcli/status.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + + "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/urfave/cli" +) + +var statusCommands = []cli.Command{ + { + Name: "status", + Usage: "info about litd status", + Category: "Status", + Action: getStatus, + }, +} + +func getStatus(ctx *cli.Context) error { + clientConn, cleanup, err := connectClient(ctx, true) + if err != nil { + return err + } + defer cleanup() + litClient := litrpc.NewStatusClient(clientConn) + + // Get LiT's status. + ctxb := context.Background() + litResp, err := litClient.SubServerStatus( + ctxb, &litrpc.SubServerStatusReq{}, + ) + if err != nil { + return err + } + + printRespJSON(litResp) + + // Get LND's state. + lndClient := lnrpc.NewStateClient(clientConn) + lndResp, err := lndClient.GetState(ctxb, &lnrpc.GetStateRequest{}) + if err != nil { + return err + } + + printRespJSON(lndResp) + + return nil +} From 3769467c24e85c4dd927b9c286845b66d649d0e0 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 09:55:38 +0200 Subject: [PATCH 12/20] subservers+terminal: integrate status server Pass the status manager to the sub-server manager so that the sub-server manager can inform the status manager of any sub-servers that have started or failed to start. --- subservers/manager.go | 50 ++++++++++++++++++++++++++++------------- subservers/subserver.go | 13 ++++++----- terminal.go | 12 +++------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/subservers/manager.go b/subservers/manager.go index 577df9686..c4e5f18fc 100644 --- a/subservers/manager.go +++ b/subservers/manager.go @@ -9,6 +9,7 @@ import ( restProxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightninglabs/lightning-terminal/perms" + "github.com/lightninglabs/lightning-terminal/status" "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -32,20 +33,27 @@ var ( // Manager manages a set of subServer objects. type Manager struct { - servers []*subServerWrapper - permsMgr *perms.Manager - mu sync.RWMutex + servers []*subServerWrapper + permsMgr *perms.Manager + statusServer *status.Manager + mu sync.RWMutex } -// NewManager constructs a new subServerMgr. -func NewManager(permsMgr *perms.Manager) *Manager { +// NewManager constructs a new Manager. +func NewManager(permsMgr *perms.Manager, + statusServer *status.Manager) *Manager { + return &Manager{ - permsMgr: permsMgr, + permsMgr: permsMgr, + statusServer: statusServer, } } // AddServer adds a new subServer to the manager's set. func (s *Manager) AddServer(ss SubServer, enable bool) { + // Register all sub-servers with the status server. + s.statusServer.RegisterSubServer(ss.Name()) + // If the sub-server has explicitly been disabled, then we don't add it // to the set of servers tracked by the Manager. if !enable { @@ -65,12 +73,15 @@ func (s *Manager) AddServer(ss SubServer, enable bool) { s.permsMgr.RegisterSubServer( ss.Name(), ss.Permissions(), ss.WhiteListedURLs(), ) + + // Mark the sub-server as enabled with the status manager. + s.statusServer.SetEnabled(ss.Name()) } // StartIntegratedServers starts all the manager's sub-servers that should be // started in integrated mode. func (s *Manager) StartIntegratedServers(lndClient lnrpc.LightningClient, - lndGrpc *lndclient.GrpcLndServices, withMacaroonService bool) error { + lndGrpc *lndclient.GrpcLndServices, withMacaroonService bool) { s.mu.Lock() defer s.mu.Unlock() @@ -82,19 +93,24 @@ func (s *Manager) StartIntegratedServers(lndClient lnrpc.LightningClient, err := ss.startIntegrated( lndClient, lndGrpc, withMacaroonService, + func(err error) { + s.statusServer.SetErrored( + ss.Name(), err.Error(), + ) + }, ) if err != nil { - return fmt.Errorf("unable to start %v in integrated "+ - "mode: %v", ss.Name(), err) + s.statusServer.SetErrored(ss.Name(), err.Error()) + continue } - } - return nil + s.statusServer.SetRunning(ss.Name()) + } } // ConnectRemoteSubServers creates connections to all the manager's sub-servers // that are running remotely. -func (s *Manager) ConnectRemoteSubServers() error { +func (s *Manager) ConnectRemoteSubServers() { s.mu.Lock() defer s.mu.Unlock() @@ -105,12 +121,12 @@ func (s *Manager) ConnectRemoteSubServers() error { err := ss.connectRemote() if err != nil { - return fmt.Errorf("failed to connect to remote %s: %v", - ss.Name(), err) + s.statusServer.SetErrored(ss.Name(), err.Error()) + continue } - } - return nil + s.statusServer.SetRunning(ss.Name()) + } } // RegisterRPCServices registers all the manager's sub-servers with the given @@ -301,6 +317,8 @@ func (s *Manager) Stop() error { log.Errorf("Error stopping %s: %v", ss.Name(), err) returnErr = err } + + s.statusServer.SetStopped(ss.Name()) } return returnErr diff --git a/subservers/subserver.go b/subservers/subserver.go index c5c0ea484..8605eb422 100644 --- a/subservers/subserver.go +++ b/subservers/subserver.go @@ -99,7 +99,8 @@ func (s *subServerWrapper) stop() error { // startIntegrated starts the subServer in integrated mode. func (s *subServerWrapper) startIntegrated(lndClient lnrpc.LightningClient, - lndGrpc *lndclient.GrpcLndServices, withMacaroonService bool) error { + lndGrpc *lndclient.GrpcLndServices, withMacaroonService bool, + onError func(error)) error { err := s.Start(lndClient, lndGrpc, withMacaroonService) if err != nil { @@ -121,11 +122,11 @@ func (s *subServerWrapper) startIntegrated(lndClient lnrpc.LightningClient, // happens. We don't need to try to stop it again. s.setStarted(false) - err = fmt.Errorf("received critical error from "+ - "sub-server (%s), shutting down: %v", - s.Name(), err) - - log.Error(err) + onError( + fmt.Errorf("received critical error from "+ + "sub-server (%s), shutting down: %v", + s.Name(), err), + ) case <-s.quit: } diff --git a/terminal.go b/terminal.go index e9a2f2bd1..501646030 100644 --- a/terminal.go +++ b/terminal.go @@ -234,7 +234,7 @@ func (g *LightningTerminal) Run() error { // Create the instances of our subservers now so we can hook them up to // lnd once it's fully started. - g.subServerMgr = subservers.NewManager(g.permsMgr) + g.subServerMgr = subservers.NewManager(g.permsMgr, g.statusMgr) // Register our sub-servers. This must be done before the REST proxy is // set up so that the correct REST handlers are registered. @@ -511,10 +511,7 @@ func (g *LightningTerminal) start() error { // Initialise any connections to sub-servers that we are running in // remote mode. - if err := g.subServerMgr.ConnectRemoteSubServers(); err != nil { - return fmt.Errorf("error connecting to remote sub-servers: %v", - err) - } + g.subServerMgr.ConnectRemoteSubServers() // bakeSuperMac is a closure that can be used to bake a new super // macaroon that contains all active permissions. @@ -615,12 +612,9 @@ func (g *LightningTerminal) start() error { // Both connection types are ready now, let's start our sub-servers if // they should be started locally as an integrated service. - err = g.subServerMgr.StartIntegratedServers( + g.subServerMgr.StartIntegratedServers( g.basicClient, g.lndClient, createDefaultMacaroons, ) - if err != nil { - return fmt.Errorf("could not start subservers: %v", err) - } err = g.startInternalSubServers(createDefaultMacaroons) if err != nil { From 21456a5887ba0619ea028b1efbe40c37e82fc4bd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 10:00:55 +0200 Subject: [PATCH 13/20] subservers: add Handles method Add a Handles method to the subserver Manager which takes a URI string and returns true if one of the sub-servers it manages owns the given URI. --- subservers/manager.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/subservers/manager.go b/subservers/manager.go index c4e5f18fc..12aa8bcd5 100644 --- a/subservers/manager.go +++ b/subservers/manager.go @@ -300,6 +300,23 @@ func (s *Manager) ReadRemoteMacaroon(uri string) (bool, []byte, error) { return false, nil, nil } +// Handles returns true if one of its sub-servers owns the given URI along with +// the name of the service. +func (s *Manager) Handles(uri string) (bool, string) { + s.mu.RLock() + defer s.mu.RUnlock() + + for _, ss := range s.servers { + if !s.permsMgr.IsSubServerURI(ss.Name(), uri) { + continue + } + + return true, ss.Name() + } + + return false, "" +} + // Stop stops all the manager's sub-servers func (s *Manager) Stop() error { var returnErr error From 75cfa0b0534b263b342469a5b3f781ae6bcb65ff Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 11:19:14 +0200 Subject: [PATCH 14/20] rpc_proxy: only allow requests if sub-server is ready --- rpc_proxy.go | 79 ++++++++++++++++++++++++++++++++++++++++++---------- terminal.go | 1 + 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/rpc_proxy.go b/rpc_proxy.go index d73145f08..484f33701 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -15,6 +15,7 @@ import ( "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/perms" "github.com/lightninglabs/lightning-terminal/session" + litstatus "github.com/lightninglabs/lightning-terminal/status" "github.com/lightninglabs/lightning-terminal/subservers" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/macaroons" @@ -67,7 +68,8 @@ func (e *proxyErr) Unwrap() error { // component. func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, superMacValidator session.SuperMacaroonValidator, - permsMgr *perms.Manager, subServerMgr *subservers.Manager) *rpcProxy { + permsMgr *perms.Manager, subServerMgr *subservers.Manager, + statusMgr *litstatus.Manager) *rpcProxy { // The gRPC web calls are protected by HTTP basic auth which is defined // by base64(username:password). Because we only have a password, we @@ -88,6 +90,7 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, macValidator: validator, superMacValidator: superMacValidator, subServerMgr: subServerMgr, + statusMgr: statusMgr, } p.grpcServer = grpc.NewServer( // From the grpxProxy doc: This codec is *crucial* to the @@ -162,6 +165,7 @@ type rpcProxy struct { basicAuth string permsMgr *perms.Manager subServerMgr *subservers.Manager + statusMgr *litstatus.Manager bakeSuperMac bakeSuperMac @@ -333,6 +337,10 @@ func (p *rpcProxy) makeDirector(allowLitRPC bool) func(ctx context.Context, } } + if err := p.checkSubSystemStarted(requestURI); err != nil { + return outCtx, nil, err + } + // Direct the call to the correct backend. All gRPC calls end up // here since our gRPC server instance doesn't have any handlers // registered itself. So all daemon calls that are remote are @@ -359,12 +367,6 @@ func (p *rpcProxy) makeDirector(allowLitRPC bool) func(ctx context.Context, ) } - // If the rpcProxy has not started yet, then the lnd connection - // will not be initialised yet. - if !p.hasStarted() { - return outCtx, nil, ErrWaitingToStart - } - return outCtx, p.lndConn, nil } } @@ -375,15 +377,15 @@ func (p *rpcProxy) UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - if !p.hasStarted() && !isStatusReq(info.FullMethod) { - return nil, ErrWaitingToStart - } - uriPermissions, ok := p.permsMgr.URIPermissions(info.FullMethod) if !ok { return nil, ErrUnknownRequest } + if err := p.checkSubSystemStarted(info.FullMethod); err != nil { + return nil, err + } + // For now, basic authentication is just a quick fix until we // have proper macaroon support implemented in the UI. We allow // gRPC web requests to have it and "convert" the auth into a @@ -419,15 +421,15 @@ func (p *rpcProxy) StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - if !p.hasStarted() && !isStatusReq(info.FullMethod) { - return ErrWaitingToStart - } - uriPermissions, ok := p.permsMgr.URIPermissions(info.FullMethod) if !ok { return ErrUnknownRequest } + if err := p.checkSubSystemStarted(info.FullMethod); err != nil { + return err + } + // For now, basic authentication is just a quick fix until we // have proper macaroon support implemented in the UI. We allow // gRPC web requests to have it and "convert" the auth into a @@ -604,6 +606,53 @@ func (p *rpcProxy) convertSuperMacaroon(ctx context.Context, macHex string, return nil, nil } +// checkSubSystemStarted checks if the subsystem responsible for handling the +// given URI has started. +func (p *rpcProxy) checkSubSystemStarted(requestURI string) error { + // A request to Lit's status server is always allowed. + if isStatusReq(requestURI) { + return nil + } + + // Check if the rpcProxy has started yet. + if !p.hasStarted() { + return ErrWaitingToStart + } + + // Currently, Lit and LND are not registered with the sub-server + // manager, so we let any request for them through. + if p.permsMgr.IsSubServerURI(subservers.LIT, requestURI) || + p.permsMgr.IsSubServerURI(subservers.LND, requestURI) { + + return nil + } + + // Check that the sub-server manager does have a sub-server registered + // that can handle the given URI. + handled, system := p.subServerMgr.Handles(requestURI) + if !handled { + return fmt.Errorf("unknown gRPC web request: %v", requestURI) + } + + // Check with the status manger to see if the sub-server is ready to + // handle the request. + serverStatus, err := p.statusMgr.GetStatus(system) + if err != nil { + return err + } + + if serverStatus.Disabled { + return fmt.Errorf("%s has been disabled", system) + } + + if !serverStatus.Running { + return fmt.Errorf("%s is not running: %s", system, + serverStatus.Err) + } + + return nil +} + // readMacaroon tries to read the macaroon file at the specified path and create // gRPC dial options from it. func readMacaroon(macPath string) ([]byte, error) { diff --git a/terminal.go b/terminal.go index 501646030..d3b49c5cc 100644 --- a/terminal.go +++ b/terminal.go @@ -244,6 +244,7 @@ func (g *LightningTerminal) Run() error { // server is started. g.rpcProxy = newRpcProxy( g.cfg, g, g.validateSuperMacaroon, g.permsMgr, g.subServerMgr, + g.statusMgr, ) // Register any gRPC services that should be served using LiT's From 0f53f434d01828f1f9b16bc7c95128a7a5e76b88 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 7 Aug 2023 12:41:43 +0200 Subject: [PATCH 15/20] rpc_proxy+terminal: add LND and LiT to status server --- rpc_proxy.go | 19 ++++++++--------- terminal.go | 59 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/rpc_proxy.go b/rpc_proxy.go index 484f33701..ad7c2d448 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -619,18 +619,17 @@ func (p *rpcProxy) checkSubSystemStarted(requestURI string) error { return ErrWaitingToStart } - // Currently, Lit and LND are not registered with the sub-server - // manager, so we let any request for them through. - if p.permsMgr.IsSubServerURI(subservers.LIT, requestURI) || - p.permsMgr.IsSubServerURI(subservers.LND, requestURI) { + handled, system := p.subServerMgr.Handles(requestURI) + switch { + case handled: - return nil - } + case p.permsMgr.IsSubServerURI(subservers.LIT, requestURI): + system = subservers.LIT - // Check that the sub-server manager does have a sub-server registered - // that can handle the given URI. - handled, system := p.subServerMgr.Handles(requestURI) - if !handled { + case p.permsMgr.IsSubServerURI(subservers.LND, requestURI): + system = subservers.LND + + default: return fmt.Errorf("unknown gRPC web request: %v", requestURI) } diff --git a/terminal.go b/terminal.go index d3b49c5cc..818f4a3c8 100644 --- a/terminal.go +++ b/terminal.go @@ -232,6 +232,10 @@ func (g *LightningTerminal) Run() error { return fmt.Errorf("could not create permissions manager") } + // Register LND and LiT with the status manager. + g.statusMgr.RegisterAndEnableSubServer(subservers.LND) + g.statusMgr.RegisterAndEnableSubServer(subservers.LIT) + // Create the instances of our subservers now so we can hook them up to // lnd once it's fully started. g.subServerMgr = subservers.NewManager(g.permsMgr, g.statusMgr) @@ -273,8 +277,9 @@ func (g *LightningTerminal) Run() error { // could not start or LND could not start or be connected to. startErr := g.start() if startErr != nil { - log.Errorf("Error starting Lightning Terminal: %v", startErr) - return startErr + g.statusMgr.SetErrored( + subservers.LIT, "could not start Lit: %v", startErr, + ) } // Now block until we receive an error or the main shutdown @@ -447,8 +452,13 @@ func (g *LightningTerminal) start() error { if e, ok := err.(*flags.Error); err != nil && (!ok || e.Type != flags.ErrHelp) { - log.Errorf("Error running main lnd: %v", err) + errStr := fmt.Sprintf("Error running main "+ + "lnd: %v", err) + log.Errorf(errStr) + + g.statusMgr.SetErrored(subservers.LND, errStr) g.errQueue.ChanIn() <- err + return } @@ -474,10 +484,18 @@ func (g *LightningTerminal) start() error { case <-readyChan: case err := <-g.errQueue.ChanOut(): - return err + g.statusMgr.SetErrored( + subservers.LND, "error from errQueue channel", + ) + + return fmt.Errorf("could not start LND: %v", err) case <-lndQuit: - return nil + g.statusMgr.SetErrored( + subservers.LND, "lndQuit channel closed", + ) + + return fmt.Errorf("LND has stopped") case <-interceptor.ShutdownChannel(): return fmt.Errorf("received the shutdown signal") @@ -507,6 +525,10 @@ func (g *LightningTerminal) start() error { // Connect to LND. g.lndConn, err = connectLND(g.cfg, bufRpcListener) if err != nil { + g.statusMgr.SetErrored( + subservers.LND, "could not connect to LND: %v", err, + ) + return fmt.Errorf("could not connect to LND") } @@ -537,6 +559,11 @@ func (g *LightningTerminal) start() error { err) } + // We can now set the status of LND as running. + // This is done _before_ we wait for the macaroon so that + // LND commands to create and unlock a wallet can be allowed. + g.statusMgr.SetRunning(subservers.LND) + // Now that we have started the main UI web server, show some useful // information to the user so they can access the web UI easily. if err := g.showStartupInfo(); err != nil { @@ -556,7 +583,11 @@ func (g *LightningTerminal) start() error { return err case <-lndQuit: - return nil + g.statusMgr.SetErrored( + subservers.LND, "lndQuit channel closed", + ) + + return fmt.Errorf("LND has stopped") case <-interceptor.ShutdownChannel(): return fmt.Errorf("received the shutdown signal") @@ -592,8 +623,11 @@ func (g *LightningTerminal) start() error { // Set up all the LND clients required by LiT. err = g.setUpLNDClients() if err != nil { - log.Errorf("Could not set up LND clients: %v", err) - return err + g.statusMgr.SetErrored( + subservers.LND, "could not set up LND clients: %v", err, + ) + + return fmt.Errorf("could not start LND") } // If we're in integrated and stateless init mode, we won't create @@ -622,6 +656,9 @@ func (g *LightningTerminal) start() error { return fmt.Errorf("could not start litd sub-servers: %v", err) } + // We can now set the status of LiT as running. + g.statusMgr.SetRunning(subservers.LIT) + // Now block until we receive an error or the main shutdown signal. select { case err := <-g.errQueue.ChanOut(): @@ -631,7 +668,11 @@ func (g *LightningTerminal) start() error { } case <-lndQuit: - return nil + g.statusMgr.SetErrored( + subservers.LND, "lndQuit channel closed", + ) + + return fmt.Errorf("LND is not running") case <-interceptor.ShutdownChannel(): log.Infof("Shutdown signal received") From a673dec347bb5f381b0084f62730d49f5e12da46 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 3 May 2023 12:25:34 +0200 Subject: [PATCH 16/20] itest: use status server to determine when Lit is ready --- itest/litd_node.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/itest/litd_node.go b/itest/litd_node.go index fe3f71dae..8ed759447 100644 --- a/itest/litd_node.go +++ b/itest/litd_node.go @@ -26,6 +26,7 @@ import ( "github.com/lightninglabs/faraday/frdrpc" terminal "github.com/lightninglabs/lightning-terminal" "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/lightninglabs/lightning-terminal/subservers" "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/pool/poolrpc" "github.com/lightninglabs/taproot-assets/taprpc" @@ -723,7 +724,37 @@ func (hn *HarnessNode) Start(litdBinary string, litdError chan<- error, func (hn *HarnessNode) WaitUntilStarted(conn grpc.ClientConnInterface, timeout time.Duration) error { - err := hn.waitForState(conn, timeout, func(s lnrpc.WalletState) bool { + // First wait for Litd status server to show that LND has started. + ctx := context.Background() + rawConn, err := connectLitRPC( + ctx, hn.Cfg.LitAddr(), hn.Cfg.LitTLSCertPath, "", + ) + if err != nil { + return err + } + + litConn := litrpc.NewStatusClient(rawConn) + + err = wait.NoError(func() error { + states, err := litConn.SubServerStatus( + ctx, &litrpc.SubServerStatusReq{}, + ) + if err != nil { + return err + } + + lndStatus, ok := states.SubServers[subservers.LND] + if !ok || !lndStatus.Running { + return fmt.Errorf("LND has not yet started") + } + + return nil + }, lntest.DefaultTimeout) + if err != nil { + return err + } + + err = hn.waitForState(conn, timeout, func(s lnrpc.WalletState) bool { return s >= lnrpc.WalletState_SERVER_ACTIVE }) if err != nil { From 96779c965e167a5df891760e188c8135fe07835d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 19:10:54 +0200 Subject: [PATCH 17/20] terminal: only shutdown sub-servers on shutdown --- terminal.go | 48 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/terminal.go b/terminal.go index 818f4a3c8..a4ac4afdf 100644 --- a/terminal.go +++ b/terminal.go @@ -287,16 +287,9 @@ func (g *LightningTerminal) Run() error { <-shutdownInterceptor.ShutdownChannel() log.Infof("Shutdown signal received") - if g.rpcProxy != nil { - if err := g.rpcProxy.Stop(); err != nil { - log.Errorf("Error stopping rpc proxy: %v", err) - } - } - - if g.httpServer != nil { - if err := g.httpServer.Close(); err != nil { - log.Errorf("Error stopping UI server: %v", err) - } + err = g.shutdownSubServers() + if err != nil { + log.Errorf("Error shutting down: %v", err) } g.wg.Wait() @@ -501,27 +494,6 @@ func (g *LightningTerminal) start() error { return fmt.Errorf("received the shutdown signal") } - // We now know that starting lnd was successful. If we now run into an - // error, we must shut down lnd correctly. - defer func() { - err := g.shutdownSubServers() - if err != nil { - log.Errorf("Error shutting down: %v", err) - } - - if g.rpcProxy != nil { - if err := g.rpcProxy.Stop(); err != nil { - log.Errorf("Error stopping rpc proxy: %v", err) - } - } - - if g.httpServer != nil { - if err := g.httpServer.Close(); err != nil { - log.Errorf("Error stopping UI server: %v", err) - } - } - }() - // Connect to LND. g.lndConn, err = connectLND(g.cfg, bufRpcListener) if err != nil { @@ -1207,6 +1179,20 @@ func (g *LightningTerminal) shutdownSubServers() error { } } + if g.rpcProxy != nil { + if err := g.rpcProxy.Stop(); err != nil { + log.Errorf("Error stopping rpc proxy: %v", err) + returnErr = err + } + } + + if g.httpServer != nil { + if err := g.httpServer.Close(); err != nil { + log.Errorf("Error stopping UI server: %v", err) + returnErr = err + } + } + // Do we have any last errors to display? We use an anonymous function, // so we can use return instead of breaking to a label in the default // case. From cb804891a20cbe81981d7c9c60d8780626ee3a8c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 4 May 2023 10:32:59 +0200 Subject: [PATCH 18/20] rpc_proxy: always allow Proxy calls --- rpc_proxy.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rpc_proxy.go b/rpc_proxy.go index ad7c2d448..798f8592e 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -609,8 +609,8 @@ func (p *rpcProxy) convertSuperMacaroon(ctx context.Context, macHex string, // checkSubSystemStarted checks if the subsystem responsible for handling the // given URI has started. func (p *rpcProxy) checkSubSystemStarted(requestURI string) error { - // A request to Lit's status server is always allowed. - if isStatusReq(requestURI) { + // A request to Lit's status and proxy services is always allowed. + if isStatusReq(requestURI) || isProxyReq(requestURI) { return nil } @@ -630,7 +630,7 @@ func (p *rpcProxy) checkSubSystemStarted(requestURI string) error { system = subservers.LND default: - return fmt.Errorf("unknown gRPC web request: %v", requestURI) + return ErrUnknownRequest } // Check with the status manger to see if the sub-server is ready to @@ -686,3 +686,11 @@ func isStatusReq(uri string) bool { uri, fmt.Sprintf("/%s", litrpc.Status_ServiceDesc.ServiceName), ) } + +// isProxyReq returns true if the given request is intended for the litrpc.Proxy +// service. +func isProxyReq(uri string) bool { + return strings.HasPrefix( + uri, fmt.Sprintf("/%s", litrpc.Proxy_ServiceDesc.ServiceName), + ) +} From 014140d44271ff11d72ccb2fbd64dd6f382fb03f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sun, 21 May 2023 20:49:51 +0200 Subject: [PATCH 19/20] itest: add test for Status server endpoint --- itest/litd_mode_integrated_test.go | 33 ++++++++++++++++++++++++++++-- itest/litd_mode_remote_test.go | 10 +++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 5493f7b39..2d5aeecdf 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -200,6 +200,14 @@ var ( litConn := litrpc.NewProxyClient(c) return litConn.GetInfo(ctx, &litrpc.GetInfoRequest{}) } + statusRequestFn = func(ctx context.Context, + c grpc.ClientConnInterface) (proto.Message, error) { + + litConn := litrpc.NewStatusClient(c) + return litConn.SubServerStatus( + ctx, &litrpc.SubServerStatusReq{}, + ) + } litMacaroonFn = func(cfg *LitNodeConfig) string { return cfg.LitMacPath } @@ -338,6 +346,16 @@ var ( grpcWebURI: "/litrpc.Proxy/GetInfo", restWebURI: "/v1/proxy/info", litOnly: true, + }, { + name: "litrpc-status", + macaroonFn: emptyMacaroonFn, + requestFn: statusRequestFn, + successPattern: "\"sub_servers\":", + allowedThroughLNC: true, + grpcWebURI: "/litrpc.Status/SubServerStatus", + restWebURI: "/v1/status", + noAuth: true, + litOnly: true, }} // customURIs is a map of endpoint URIs that we want to allow via a @@ -691,13 +709,20 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpointDisabled := subServersDisabled && endpoint.canDisable + expectedErr := "permission denied" + if endpoint.noAuth { + expectedErr = "unknown service" + } + tt.Run(endpoint.name+" lit port", func(ttt *testing.T) { allowed := customURIs[endpoint.grpcWebURI] + runLNCAuthTest( ttt, rawLNCConn, endpoint.requestFn, endpoint.successPattern, - allowed, "permission denied", - endpointDisabled, endpoint.noAuth, + allowed, expectedErr, + endpointDisabled, + endpoint.noAuth, ) }) } @@ -817,6 +842,10 @@ func runGRPCAuthTest(t *testing.T, hostPort, tlsCertPath, macPath string, require.NoError(t, err) ctxm = macaroonContext(ctxt, macBytes) resp, err = makeRequest(ctxm, rawConn) + if disabled { + require.ErrorContains(t, err, disabledErr) + return + } require.NoError(t, err) json, err := marshalOptions.Marshal(resp) diff --git a/itest/litd_mode_remote_test.go b/itest/litd_mode_remote_test.go index b3cb6122b..41f34c13f 100644 --- a/itest/litd_mode_remote_test.go +++ b/itest/litd_mode_remote_test.go @@ -236,13 +236,19 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T, endpointDisabled := subServersDisabled && endpoint.canDisable + expectedErr := "permission denied" + if endpoint.noAuth { + expectedErr = "unknown service" + } + tt.Run(endpoint.name+" lit port", func(ttt *testing.T) { allowed := customURIs[endpoint.grpcWebURI] runLNCAuthTest( ttt, rawLNCConn, endpoint.requestFn, endpoint.successPattern, - allowed, "permission denied", - endpointDisabled, endpoint.noAuth, + allowed, expectedErr, + endpointDisabled, + endpoint.noAuth, ) }) } From 861d64cfbe86e9fae6ea7ccbbf0e4069856d2dd8 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 16 Jun 2023 11:28:36 +0200 Subject: [PATCH 20/20] app+proto: add status server to build-protos.js --- app/scripts/build-protos.js | 3 +- app/src/types/generated/lit-status_pb.d.ts | 68 +++ app/src/types/generated/lit-status_pb.js | 480 ++++++++++++++++++ .../generated/lit-status_pb_service.d.ts | 63 +++ .../types/generated/lit-status_pb_service.js | 61 +++ proto/lit-status.proto | 31 ++ 6 files changed, 705 insertions(+), 1 deletion(-) create mode 100644 app/src/types/generated/lit-status_pb.d.ts create mode 100644 app/src/types/generated/lit-status_pb.js create mode 100644 app/src/types/generated/lit-status_pb_service.d.ts create mode 100644 app/src/types/generated/lit-status_pb_service.js create mode 100644 proto/lit-status.proto diff --git a/app/scripts/build-protos.js b/app/scripts/build-protos.js index 40152c758..1f93bd647 100644 --- a/app/scripts/build-protos.js +++ b/app/scripts/build-protos.js @@ -62,6 +62,7 @@ const filePatches = { 'lit-autopilot': 'litrpc: {}', 'firewall': 'litrpc: {}', 'proxy': 'litrpc: {}', + 'lit-status': 'litrpc: {}', }; /** @@ -87,7 +88,7 @@ const download = async () => { } // copy the lit proto files from litrpc to the proto dir so that the original // files are not modified by `sanitize` - const litProtoFiles = ['lit-sessions', 'lit-accounts', 'lit-autopilot', 'proxy', 'firewall']; + const litProtoFiles = ['lit-sessions', 'lit-accounts', 'lit-autopilot', 'proxy', 'firewall', 'lit-status']; for (name of litProtoFiles) { const src = join(appPath, '..', 'litrpc', `${name}.proto`); const dest = join(appPath, '..', 'proto', `${name}.proto`); diff --git a/app/src/types/generated/lit-status_pb.d.ts b/app/src/types/generated/lit-status_pb.d.ts new file mode 100644 index 000000000..0cef648ef --- /dev/null +++ b/app/src/types/generated/lit-status_pb.d.ts @@ -0,0 +1,68 @@ +// package: litrpc +// file: lit-status.proto + +import * as jspb from "google-protobuf"; + +export class SubServerStatusReq extends jspb.Message { + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SubServerStatusReq.AsObject; + static toObject(includeInstance: boolean, msg: SubServerStatusReq): SubServerStatusReq.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SubServerStatusReq, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SubServerStatusReq; + static deserializeBinaryFromReader(message: SubServerStatusReq, reader: jspb.BinaryReader): SubServerStatusReq; +} + +export namespace SubServerStatusReq { + export type AsObject = { + } +} + +export class SubServerStatusResp extends jspb.Message { + getSubServersMap(): jspb.Map; + clearSubServersMap(): void; + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SubServerStatusResp.AsObject; + static toObject(includeInstance: boolean, msg: SubServerStatusResp): SubServerStatusResp.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SubServerStatusResp, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SubServerStatusResp; + static deserializeBinaryFromReader(message: SubServerStatusResp, reader: jspb.BinaryReader): SubServerStatusResp; +} + +export namespace SubServerStatusResp { + export type AsObject = { + subServersMap: Array<[string, SubServerStatus.AsObject]>, + } +} + +export class SubServerStatus extends jspb.Message { + getDisabled(): boolean; + setDisabled(value: boolean): void; + + getRunning(): boolean; + setRunning(value: boolean): void; + + getError(): string; + setError(value: string): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SubServerStatus.AsObject; + static toObject(includeInstance: boolean, msg: SubServerStatus): SubServerStatus.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SubServerStatus, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SubServerStatus; + static deserializeBinaryFromReader(message: SubServerStatus, reader: jspb.BinaryReader): SubServerStatus; +} + +export namespace SubServerStatus { + export type AsObject = { + disabled: boolean, + running: boolean, + error: string, + } +} + diff --git a/app/src/types/generated/lit-status_pb.js b/app/src/types/generated/lit-status_pb.js new file mode 100644 index 000000000..c62099739 --- /dev/null +++ b/app/src/types/generated/lit-status_pb.js @@ -0,0 +1,480 @@ +/* eslint-disable */ +var proto = { litrpc: {} }; + +/** + * @fileoverview + * @enhanceable + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +goog.exportSymbol('proto.litrpc.SubServerStatus', null, global); +goog.exportSymbol('proto.litrpc.SubServerStatusReq', null, global); +goog.exportSymbol('proto.litrpc.SubServerStatusResp', null, global); + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.SubServerStatusReq = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.SubServerStatusReq, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.SubServerStatusReq.displayName = 'proto.litrpc.SubServerStatusReq'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.SubServerStatusReq.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.SubServerStatusReq.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.SubServerStatusReq} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SubServerStatusReq.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.SubServerStatusReq} + */ +proto.litrpc.SubServerStatusReq.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.SubServerStatusReq; + return proto.litrpc.SubServerStatusReq.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.SubServerStatusReq} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.SubServerStatusReq} + */ +proto.litrpc.SubServerStatusReq.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.SubServerStatusReq.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.SubServerStatusReq.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.SubServerStatusReq} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SubServerStatusReq.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.SubServerStatusResp = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.SubServerStatusResp, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.SubServerStatusResp.displayName = 'proto.litrpc.SubServerStatusResp'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.SubServerStatusResp.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.SubServerStatusResp.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.SubServerStatusResp} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SubServerStatusResp.toObject = function(includeInstance, msg) { + var f, obj = { + subServersMap: (f = msg.getSubServersMap()) ? f.toObject(includeInstance, proto.litrpc.SubServerStatus.toObject) : [] + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.SubServerStatusResp} + */ +proto.litrpc.SubServerStatusResp.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.SubServerStatusResp; + return proto.litrpc.SubServerStatusResp.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.SubServerStatusResp} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.SubServerStatusResp} + */ +proto.litrpc.SubServerStatusResp.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = msg.getSubServersMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.litrpc.SubServerStatus.deserializeBinaryFromReader, ""); + }); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.SubServerStatusResp.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.SubServerStatusResp.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.SubServerStatusResp} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SubServerStatusResp.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSubServersMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(1, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.litrpc.SubServerStatus.serializeBinaryToWriter); + } +}; + + +/** + * map sub_servers = 1; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.litrpc.SubServerStatusResp.prototype.getSubServersMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 1, opt_noLazyCreate, + proto.litrpc.SubServerStatus)); +}; + + +proto.litrpc.SubServerStatusResp.prototype.clearSubServersMap = function() { + this.getSubServersMap().clear(); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.SubServerStatus = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.SubServerStatus, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.SubServerStatus.displayName = 'proto.litrpc.SubServerStatus'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.SubServerStatus.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.SubServerStatus.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.SubServerStatus} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SubServerStatus.toObject = function(includeInstance, msg) { + var f, obj = { + disabled: jspb.Message.getFieldWithDefault(msg, 1, false), + running: jspb.Message.getFieldWithDefault(msg, 2, false), + error: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.SubServerStatus} + */ +proto.litrpc.SubServerStatus.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.SubServerStatus; + return proto.litrpc.SubServerStatus.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.SubServerStatus} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.SubServerStatus} + */ +proto.litrpc.SubServerStatus.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setDisabled(value); + break; + case 2: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setRunning(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setError(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.SubServerStatus.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.SubServerStatus.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.SubServerStatus} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SubServerStatus.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getDisabled(); + if (f) { + writer.writeBool( + 1, + f + ); + } + f = message.getRunning(); + if (f) { + writer.writeBool( + 2, + f + ); + } + f = message.getError(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional bool disabled = 1; + * Note that Boolean fields may be set to 0/1 when serialized from a Java server. + * You should avoid comparisons like {@code val === true/false} in those cases. + * @return {boolean} + */ +proto.litrpc.SubServerStatus.prototype.getDisabled = function() { + return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 1, false)); +}; + + +/** @param {boolean} value */ +proto.litrpc.SubServerStatus.prototype.setDisabled = function(value) { + jspb.Message.setProto3BooleanField(this, 1, value); +}; + + +/** + * optional bool running = 2; + * Note that Boolean fields may be set to 0/1 when serialized from a Java server. + * You should avoid comparisons like {@code val === true/false} in those cases. + * @return {boolean} + */ +proto.litrpc.SubServerStatus.prototype.getRunning = function() { + return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 2, false)); +}; + + +/** @param {boolean} value */ +proto.litrpc.SubServerStatus.prototype.setRunning = function(value) { + jspb.Message.setProto3BooleanField(this, 2, value); +}; + + +/** + * optional string error = 3; + * @return {string} + */ +proto.litrpc.SubServerStatus.prototype.getError = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** @param {string} value */ +proto.litrpc.SubServerStatus.prototype.setError = function(value) { + jspb.Message.setProto3StringField(this, 3, value); +}; + + +goog.object.extend(exports, proto.litrpc); diff --git a/app/src/types/generated/lit-status_pb_service.d.ts b/app/src/types/generated/lit-status_pb_service.d.ts new file mode 100644 index 000000000..e740c0fee --- /dev/null +++ b/app/src/types/generated/lit-status_pb_service.d.ts @@ -0,0 +1,63 @@ +// package: litrpc +// file: lit-status.proto + +import * as lit_status_pb from "./lit-status_pb"; +import {grpc} from "@improbable-eng/grpc-web"; + +type StatusSubServerStatus = { + readonly methodName: string; + readonly service: typeof Status; + readonly requestStream: false; + readonly responseStream: false; + readonly requestType: typeof lit_status_pb.SubServerStatusReq; + readonly responseType: typeof lit_status_pb.SubServerStatusResp; +}; + +export class Status { + static readonly serviceName: string; + static readonly SubServerStatus: StatusSubServerStatus; +} + +export type ServiceError = { message: string, code: number; metadata: grpc.Metadata } +export type Status = { details: string, code: number; metadata: grpc.Metadata } + +interface UnaryResponse { + cancel(): void; +} +interface ResponseStream { + cancel(): void; + on(type: 'data', handler: (message: T) => void): ResponseStream; + on(type: 'end', handler: (status?: Status) => void): ResponseStream; + on(type: 'status', handler: (status: Status) => void): ResponseStream; +} +interface RequestStream { + write(message: T): RequestStream; + end(): void; + cancel(): void; + on(type: 'end', handler: (status?: Status) => void): RequestStream; + on(type: 'status', handler: (status: Status) => void): RequestStream; +} +interface BidirectionalStream { + write(message: ReqT): BidirectionalStream; + end(): void; + cancel(): void; + on(type: 'data', handler: (message: ResT) => void): BidirectionalStream; + on(type: 'end', handler: (status?: Status) => void): BidirectionalStream; + on(type: 'status', handler: (status: Status) => void): BidirectionalStream; +} + +export class StatusClient { + readonly serviceHost: string; + + constructor(serviceHost: string, options?: grpc.RpcOptions); + subServerStatus( + requestMessage: lit_status_pb.SubServerStatusReq, + metadata: grpc.Metadata, + callback: (error: ServiceError|null, responseMessage: lit_status_pb.SubServerStatusResp|null) => void + ): UnaryResponse; + subServerStatus( + requestMessage: lit_status_pb.SubServerStatusReq, + callback: (error: ServiceError|null, responseMessage: lit_status_pb.SubServerStatusResp|null) => void + ): UnaryResponse; +} + diff --git a/app/src/types/generated/lit-status_pb_service.js b/app/src/types/generated/lit-status_pb_service.js new file mode 100644 index 000000000..f4e16ab0e --- /dev/null +++ b/app/src/types/generated/lit-status_pb_service.js @@ -0,0 +1,61 @@ +// package: litrpc +// file: lit-status.proto + +var lit_status_pb = require("./lit-status_pb"); +var grpc = require("@improbable-eng/grpc-web").grpc; + +var Status = (function () { + function Status() {} + Status.serviceName = "litrpc.Status"; + return Status; +}()); + +Status.SubServerStatus = { + methodName: "SubServerStatus", + service: Status, + requestStream: false, + responseStream: false, + requestType: lit_status_pb.SubServerStatusReq, + responseType: lit_status_pb.SubServerStatusResp +}; + +exports.Status = Status; + +function StatusClient(serviceHost, options) { + this.serviceHost = serviceHost; + this.options = options || {}; +} + +StatusClient.prototype.subServerStatus = function subServerStatus(requestMessage, metadata, callback) { + if (arguments.length === 2) { + callback = arguments[1]; + } + var client = grpc.unary(Status.SubServerStatus, { + request: requestMessage, + host: this.serviceHost, + metadata: metadata, + transport: this.options.transport, + debug: this.options.debug, + onEnd: function (response) { + if (callback) { + if (response.status !== grpc.Code.OK) { + var err = new Error(response.statusMessage); + err.code = response.status; + err.metadata = response.trailers; + callback(err, null); + } else { + callback(null, response.message); + } + } + } + }); + return { + cancel: function () { + callback = null; + client.close(); + } + }; +}; + +exports.StatusClient = StatusClient; + diff --git a/proto/lit-status.proto b/proto/lit-status.proto new file mode 100644 index 000000000..c7ec55838 --- /dev/null +++ b/proto/lit-status.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package litrpc; + +option go_package = "github.com/lightninglabs/lightning-terminal/litrpc"; + +// The Status server can be used to query the state of various LiT sub-servers. +service Status { + rpc SubServerStatus (SubServerStatusReq) returns (SubServerStatusResp); +} + +message SubServerStatusReq { +} + +message SubServerStatusResp { + // A map of sub-server names to their status. + map sub_servers = 1; +} + +message SubServerStatus { + // disabled is true if the sub-server is available in the LiT package but + // has explicitly been disabled. + bool disabled = 1; + + // running is true if the sub-server is currently running. + bool running = 2; + + // error describes an error that might have resulted in the sub-server not + // starting up properly. + string error = 3; +}