diff --git a/app/src/types/generated/lit-status_pb.d.ts b/app/src/types/generated/lit-status_pb.d.ts index 0cef648ef..ba026b13f 100644 --- a/app/src/types/generated/lit-status_pb.d.ts +++ b/app/src/types/generated/lit-status_pb.d.ts @@ -48,6 +48,9 @@ export class SubServerStatus extends jspb.Message { getError(): string; setError(value: string): void; + getCustomStatus(): string; + setCustomStatus(value: string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SubServerStatus.AsObject; static toObject(includeInstance: boolean, msg: SubServerStatus): SubServerStatus.AsObject; @@ -63,6 +66,7 @@ export namespace SubServerStatus { disabled: boolean, running: boolean, error: string, + customStatus: string, } } diff --git a/app/src/types/generated/lit-status_pb.js b/app/src/types/generated/lit-status_pb.js index c62099739..5ab4e7cd0 100644 --- a/app/src/types/generated/lit-status_pb.js +++ b/app/src/types/generated/lit-status_pb.js @@ -326,7 +326,8 @@ 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, "") + error: jspb.Message.getFieldWithDefault(msg, 3, ""), + customStatus: jspb.Message.getFieldWithDefault(msg, 4, "") }; if (includeInstance) { @@ -375,6 +376,10 @@ proto.litrpc.SubServerStatus.deserializeBinaryFromReader = function(msg, reader) var value = /** @type {string} */ (reader.readString()); msg.setError(value); break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setCustomStatus(value); + break; default: reader.skipField(); break; @@ -425,6 +430,13 @@ proto.litrpc.SubServerStatus.serializeBinaryToWriter = function(message, writer) f ); } + f = message.getCustomStatus(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } }; @@ -477,4 +489,19 @@ proto.litrpc.SubServerStatus.prototype.setError = function(value) { }; +/** + * optional string custom_status = 4; + * @return {string} + */ +proto.litrpc.SubServerStatus.prototype.getCustomStatus = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** @param {string} value */ +proto.litrpc.SubServerStatus.prototype.setCustomStatus = function(value) { + jspb.Message.setProto3StringField(this, 4, value); +}; + + goog.object.extend(exports, proto.litrpc); diff --git a/app/src/util/tests/sampleData.ts b/app/src/util/tests/sampleData.ts index 77a80ed0a..6497171fc 100644 --- a/app/src/util/tests/sampleData.ts +++ b/app/src/util/tests/sampleData.ts @@ -1071,6 +1071,7 @@ export const litSubServerStatus: STATUS.SubServerStatusResp.AsObject = { disabled: false, running: true, error: '', + customStatus: '', }, ], [ @@ -1079,6 +1080,7 @@ export const litSubServerStatus: STATUS.SubServerStatusResp.AsObject = { disabled: false, running: true, error: '', + customStatus: '', }, ], [ @@ -1087,6 +1089,7 @@ export const litSubServerStatus: STATUS.SubServerStatusResp.AsObject = { disabled: false, running: true, error: '', + customStatus: '', }, ], [ @@ -1095,6 +1098,7 @@ export const litSubServerStatus: STATUS.SubServerStatusResp.AsObject = { disabled: false, running: true, error: '', + customStatus: '', }, ], [ @@ -1103,6 +1107,7 @@ export const litSubServerStatus: STATUS.SubServerStatusResp.AsObject = { disabled: false, running: true, error: '', + customStatus: '', }, ], [ @@ -1111,6 +1116,7 @@ export const litSubServerStatus: STATUS.SubServerStatusResp.AsObject = { disabled: false, running: true, error: '', + customStatus: '', }, ], ], diff --git a/autopilotserverrpc/autopilotserver.pb.go b/autopilotserverrpc/autopilotserver.pb.go index cff40e070..5109d6d30 100644 --- a/autopilotserverrpc/autopilotserver.pb.go +++ b/autopilotserverrpc/autopilotserver.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: autopilotserver.proto diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go index 79e42dfb2..48d03112a 100644 --- a/litrpc/firewall.pb.go +++ b/litrpc/firewall.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: firewall.proto diff --git a/litrpc/lit-accounts.pb.go b/litrpc/lit-accounts.pb.go index ff99e25fb..1df886ffe 100644 --- a/litrpc/lit-accounts.pb.go +++ b/litrpc/lit-accounts.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: lit-accounts.proto diff --git a/litrpc/lit-autopilot.pb.go b/litrpc/lit-autopilot.pb.go index 733ca7883..f95717775 100644 --- a/litrpc/lit-autopilot.pb.go +++ b/litrpc/lit-autopilot.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: lit-autopilot.proto diff --git a/litrpc/lit-sessions.pb.go b/litrpc/lit-sessions.pb.go index 68fb3ac3a..4073f70a6 100644 --- a/litrpc/lit-sessions.pb.go +++ b/litrpc/lit-sessions.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: lit-sessions.proto diff --git a/litrpc/lit-status.pb.go b/litrpc/lit-status.pb.go index fee2cd92a..70fcfab4e 100644 --- a/litrpc/lit-status.pb.go +++ b/litrpc/lit-status.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: lit-status.proto @@ -119,6 +119,10 @@ type SubServerStatus struct { // 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"` + // custom_status details a custom state that the sub-server has entered, + // which is unique to the sub-server, and which is not the standard + // disabled, running or errored state. + CustomStatus string `protobuf:"bytes,4,opt,name=custom_status,json=customStatus,proto3" json:"custom_status,omitempty"` } func (x *SubServerStatus) Reset() { @@ -174,6 +178,13 @@ func (x *SubServerStatus) GetError() string { return "" } +func (x *SubServerStatus) GetCustomStatus() string { + if x != nil { + return x.CustomStatus + } + return "" +} + var File_lit_status_proto protoreflect.FileDescriptor var file_lit_status_proto_rawDesc = []byte{ @@ -191,23 +202,25 @@ var file_lit_status_proto_rawDesc = []byte{ 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, + 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x82, + 0x01, 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, 0x12, 0x23, + 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 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 ( diff --git a/litrpc/lit-status.proto b/litrpc/lit-status.proto index c7ec55838..7fb0d9672 100644 --- a/litrpc/lit-status.proto +++ b/litrpc/lit-status.proto @@ -28,4 +28,9 @@ message SubServerStatus { // error describes an error that might have resulted in the sub-server not // starting up properly. string error = 3; + + // custom_status details a custom state that the sub-server has entered, + // which is unique to the sub-server, and which is not the standard + // disabled, running or errored state. + string custom_status = 4; } diff --git a/litrpc/lit-status.swagger.json b/litrpc/lit-status.swagger.json index 02c72af40..532fb220c 100644 --- a/litrpc/lit-status.swagger.json +++ b/litrpc/lit-status.swagger.json @@ -54,6 +54,10 @@ "error": { "type": "string", "description": "error describes an error that might have resulted in the sub-server not\nstarting up properly." + }, + "custom_status": { + "type": "string", + "description": "custom_status details a custom state that the sub-server has entered,\nwhich is unique to the sub-server, and which is not the standard\ndisabled, running or errored state." } } }, diff --git a/litrpc/proxy.pb.go b/litrpc/proxy.pb.go index 26a9cf0a5..b57321938 100644 --- a/litrpc/proxy.pb.go +++ b/litrpc/proxy.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.6.1 // source: proxy.proto diff --git a/proto/lit-status.proto b/proto/lit-status.proto index c7ec55838..7fb0d9672 100644 --- a/proto/lit-status.proto +++ b/proto/lit-status.proto @@ -28,4 +28,9 @@ message SubServerStatus { // error describes an error that might have resulted in the sub-server not // starting up properly. string error = 3; + + // custom_status details a custom state that the sub-server has entered, + // which is unique to the sub-server, and which is not the standard + // disabled, running or errored state. + string custom_status = 4; } diff --git a/rpc_proxy.go b/rpc_proxy.go index f1be75934..3bd20cf19 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -638,18 +638,17 @@ func (p *rpcProxy) checkSubSystemStarted(requestURI string) error { // Check with the status manger to see if the sub-server is ready to // handle the request. - serverStatus, err := p.statusMgr.GetStatus(system) + ready, disabled, err := p.statusMgr.IsSystemReady(system, requestURI) if err != nil { return err } - if serverStatus.Disabled { + if disabled { return fmt.Errorf("%s has been disabled", system) } - if !serverStatus.Running { - return fmt.Errorf("%s is not running: %s", system, - serverStatus.Err) + if !ready { + return fmt.Errorf("%s is not ready for: %s", system, requestURI) } return nil diff --git a/status/manager.go b/status/manager.go index d3996416a..cb2f2befc 100644 --- a/status/manager.go +++ b/status/manager.go @@ -9,24 +9,54 @@ import ( "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 +// SubServerOption defines a functional option that can be used to modify the +// values of a subServer's fields. +type SubServerOption func(status *subServer) + +// WithIsReadyOverride is a functional option that can be used to set a callback +// function that is used to check if a system is ready _iff_ the system running +// status is not yet true. The call-back will be passed the request URI along +// with any manual status that has been set for the subsystem. +func WithIsReadyOverride(fn func(string, string) (bool, bool)) SubServerOption { + return func(status *subServer) { + status.isReadyOverride = fn + } +} + +// subServer represents the status of a sub-server. +type subServer struct { + // disabled is true if the sub-server is available in the LiT bundle but // has explicitly been disabled by the user. - Disabled bool + disabled bool - // Running is true if the sub-server is enabled and has successfully + // 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 + running bool + + // customStatus is a string that details a custom status of the + // sub-server, if the the sub-server is in a custom state. This status + // can be set to a unique status that only exists for the specific + // sub-server, and will be displayed to the user with the + // litrpc.SubServerStatus. + customStatus string + + // err will be a non-empty string if the sub-server failed to start. + err string + + // isReadyOverride is a call back that, when set and only if `running` + // is not yet true, will be used to determine if a system is ready for + // a call. We will pass the request URI to this method along with the + // `manualStatus`. The first returned boolean is true if the system + // should be seen as ready and the second is true if the override does + // handle the given request. If it does not, then we will fall back to + // our normal is-ready check. + isReadyOverride func(string, string) (bool, bool) } -// newSubServerStatus constructs a new SubServerStatus. -func newSubServerStatus() *SubServerStatus { - return &SubServerStatus{ - Disabled: true, +// newSubServer constructs a new subServer. +func newSubServer(disabled bool, opts ...SubServerOption) *subServer { + return &subServer{ + disabled: disabled, } } @@ -36,17 +66,52 @@ func newSubServerStatus() *SubServerStatus { type Manager struct { litrpc.UnimplementedStatusServer - subServers map[string]*SubServerStatus + subServers map[string]*subServer mu sync.RWMutex } // NewStatusManager constructs a new Manager. func NewStatusManager() *Manager { return &Manager{ - subServers: make(map[string]*SubServerStatus), + subServers: make(map[string]*subServer), } } +// IsSystemReady shows if the given sub-server ready to handle the a request for +// the passed request URI. The first returned boolean is true if the system +// is ready to handle the request. The second returned boolean is true if the +// system has been disabled. +func (s *Manager) IsSystemReady(name, req string) (bool, bool, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + server, ok := s.subServers[name] + if !ok { + return false, false, errors.New("a sub-server with " + + "name %s has not yet been registered") + } + + if server.disabled { + return false, true, nil + } + + // If there is no override for this server or if the server is already + // running then we just return the 'running' status. + if server.isReadyOverride == nil || server.running { + return server.running, false, nil + } + + // Otherwise, we check the override to see if this request is handled + // by the override and if it is, then if the override permits this call. + isReady, handled := server.isReadyOverride(req, server.customStatus) + if handled { + return isReady, false, nil + } + + // Otherwise, we just return the running status. + return server.running, false, nil +} + // SubServerStatus queries the current status of a given sub-server. // // NOTE: this is part of the litrpc.StatusServer interface. @@ -60,9 +125,10 @@ func (s *Manager) SubServerStatus(_ context.Context, 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, + Disabled: status.disabled, + Running: status.running, + Error: status.err, + CustomStatus: status.customStatus, } } @@ -73,38 +139,44 @@ func (s *Manager) SubServerStatus(_ context.Context, // RegisterSubServer will create a new sub-server entry for the Manager to // keep track of. -func (s *Manager) RegisterSubServer(name string) { +func (s *Manager) RegisterSubServer(name string, opts ...SubServerOption) { s.mu.RLock() defer s.mu.RUnlock() - s.subServers[name] = newSubServerStatus() + s.registerSubServerUnsafe(name, true, opts...) } // 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) { +func (s *Manager) RegisterAndEnableSubServer(name string, + opts ...SubServerOption) { + s.mu.RLock() defer s.mu.RUnlock() - ss := newSubServerStatus() - ss.Disabled = false + s.registerSubServerUnsafe(name, false, opts...) +} + +func (s *Manager) registerSubServerUnsafe(name string, disabled bool, + opts ...SubServerOption) { + + ss := newSubServer(disabled, opts...) 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() +// SetCustomStatus updates the custom status of the given sub-server to the +// passed status. +func (s *Manager) SetCustomStatus(name, customStatus string) { + s.mu.Lock() + defer s.mu.Unlock() - status, ok := s.subServers[name] + ss, ok := s.subServers[name] if !ok { - return nil, errors.New("a sub-server with name %s has not " + - "yet been registered") + return } - return status, nil + ss.customStatus = customStatus } // SetEnabled marks the sub-server with the given name as enabled. @@ -122,7 +194,7 @@ func (s *Manager) SetEnabled(name string) { return } - ss.Disabled = false + ss.disabled = false } // SetRunning can be used to set the status of a sub-server as Running @@ -141,7 +213,9 @@ func (s *Manager) SetRunning(name string) { return } - ss.Running = true + ss.running = true + ss.err = "" + ss.customStatus = "" } // SetStopped can be used to set the status of a sub-server as not Running and @@ -160,8 +234,9 @@ func (s *Manager) SetStopped(name string) { return } - ss.Running = false - ss.Err = "" + ss.running = false + ss.err = "" + ss.customStatus = "" } // SetErrored can be used to set the status of a sub-server as not Running @@ -175,16 +250,18 @@ func (s *Manager) SetErrored(name string, errStr string, s.mu.Lock() defer s.mu.Unlock() - log.Debugf("Setting the %s sub-server as errored: %s", name, errStr) + err := fmt.Sprintf(errStr, params...) + + log.Debugf("Setting the %s sub-server as errored: %s", name, err) 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 + ss.running = false + ss.err = err + ss.customStatus = "" } diff --git a/terminal.go b/terminal.go index f0c1e9350..3d0bc964d 100644 --- a/terminal.go +++ b/terminal.go @@ -48,7 +48,6 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" - "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/rpcperms" @@ -66,6 +65,12 @@ const ( MainnetServer = "autopilot.lightning.finance:12010" TestnetServer = "test.autopilot.lightning.finance:12010" + // lndWalletReadyStatus is a custom status that will be used with the + // LND subserver. If the subserver is in this state then it will allow + // certain wallet calls through while denying other calls that require + // LND to be fully started. + lndWalletReadyStatus = "Wallet Ready" + defaultServerTimeout = 10 * time.Second defaultConnectTimeout = 15 * time.Second defaultStartupTimeout = 5 * time.Second @@ -232,8 +237,24 @@ func (g *LightningTerminal) Run() error { return fmt.Errorf("could not create permissions manager") } + // The litcli status command will call the "/lnrpc.State/GetState" RPC. + // As the status command is available to the user before the macaroons + // have been loaded/created, and before the lnd clients have been + // set up, we need to override the isReady check for this specific + // URI as soon as LND can accept the call, i.e. when the lnd sub-server + // is in the "Wallet Ready" state. + lndOverride := func(uri, manualStatus string) (bool, bool) { + if uri != "/lnrpc.State/GetState" { + return false, false + } + + return manualStatus == lndWalletReadyStatus, true + } + // Register LND, LiT and Accounts with the status manager. - g.statusMgr.RegisterAndEnableSubServer(subservers.LND) + g.statusMgr.RegisterAndEnableSubServer( + subservers.LND, status.WithIsReadyOverride(lndOverride), + ) g.statusMgr.RegisterAndEnableSubServer(subservers.LIT) g.statusMgr.RegisterSubServer(subservers.ACCOUNTS) @@ -548,10 +569,12 @@ 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) + // We now set a custom status for the LND sub-server to indicate that + // the wallet is ready. + // This is done _before_ we have set up the lnd clients so that the + // litcli status command won't error before the lnd sub-server has + // been marked as running. + g.statusMgr.SetCustomStatus(subservers.LND, lndWalletReadyStatus) // 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. @@ -610,7 +633,7 @@ func (g *LightningTerminal) start() error { } // Set up all the LND clients required by LiT. - err = g.setUpLNDClients() + err = g.setUpLNDClients(lndQuit) if err != nil { g.statusMgr.SetErrored( subservers.LND, "could not set up LND clients: %v", err, @@ -619,6 +642,10 @@ func (g *LightningTerminal) start() error { return fmt.Errorf("could not start LND") } + // Mark that lnd is now completely running after connecting the + // lnd clients. + g.statusMgr.SetRunning(subservers.LND) + // If we're in integrated and stateless init mode, we won't create // macaroon files in any of the subserver daemons. createDefaultMacaroons := true @@ -671,8 +698,9 @@ func (g *LightningTerminal) start() error { } // setUpLNDClients sets up the various LND clients required by LiT. -func (g *LightningTerminal) setUpLNDClients() error { +func (g *LightningTerminal) setUpLNDClients(lndQuit chan struct{}) error { var ( + err error insecure bool clientOptions []lndclient.BasicClientOption ) @@ -694,25 +722,57 @@ func (g *LightningTerminal) setUpLNDClients() error { clientOptions = append(clientOptions, lndclient.Insecure()) } + // checkRunning checks if we should continue running for the duration of + // the defaultStartupTimeout, or else returns an error indicating why + // a shut-down is needed. + checkRunning := func() error { + select { + case err := <-g.errQueue.ChanOut(): + return fmt.Errorf("error from subsystem: %v", err) + + case <-lndQuit: + return fmt.Errorf("LND has stopped") + + case <-interceptor.ShutdownChannel(): + return fmt.Errorf("received the shutdown signal") + + case <-time.After(defaultStartupTimeout): + return nil + } + } + // The main RPC listener of lnd might need some time to start, it could // be that we run into a connection refused a few times. We use the // basic client connection to find out if the RPC server is started yet // because that doesn't do anything else than just connect. We'll check // if lnd is also ready to be used in the next step. log.Infof("Connecting basic lnd client") - err := wait.NoError(func() error { + + for { // Create an lnd client now that we have the full configuration. // We'll need a basic client and a full client because not all // subservers have the same requirements. - var err error g.basicClient, err = lndclient.NewBasicClient( - host, tlsPath, filepath.Dir(macPath), string(network), - clientOptions..., + host, tlsPath, filepath.Dir(macPath), + string(network), clientOptions..., ) - return err - }, defaultStartupTimeout) - if err != nil { - return fmt.Errorf("could not create basic LND Client: %v", err) + if err == nil { + log.Infof("Basic lnd client connected") + + break + } + + g.statusMgr.SetErrored( + subservers.LIT, + "Error when setting up basic LND Client: %v", err, + ) + + err = checkRunning() + if err != nil { + return err + } + + log.Infof("Retrying to connect basic lnd client") } // Now we know that the connection itself is ready. But we also need to @@ -740,23 +800,39 @@ func (g *LightningTerminal) setUpLNDClients() error { }() log.Infof("Connecting full lnd client") - g.lndClient, err = lndclient.NewLndServices( - &lndclient.LndServicesConfig{ - LndAddress: host, - Network: network, - TLSPath: tlsPath, - Insecure: insecure, - CustomMacaroonPath: macPath, - CustomMacaroonHex: hex.EncodeToString(macData), - BlockUntilChainSynced: true, - BlockUntilUnlocked: true, - CallerCtx: ctxc, - CheckVersion: minimalCompatibleVersion, - }, - ) - if err != nil { - return fmt.Errorf("could not create LND Services client: %v", - err) + for { + g.lndClient, err = lndclient.NewLndServices( + &lndclient.LndServicesConfig{ + LndAddress: host, + Network: network, + TLSPath: tlsPath, + Insecure: insecure, + CustomMacaroonPath: macPath, + CustomMacaroonHex: hex.EncodeToString(macData), + BlockUntilChainSynced: true, + BlockUntilUnlocked: true, + CallerCtx: ctxc, + CheckVersion: minimalCompatibleVersion, + }, + ) + if err == nil { + log.Infof("Full lnd client connected") + + break + } + + g.statusMgr.SetErrored( + subservers.LIT, + "Error when creating LND Services client: %v", + err, + ) + + err = checkRunning() + if err != nil { + return err + } + + log.Infof("Retrying to create LND Services client") } // Pass LND's build tags to the permission manager so that it can