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/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..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 { @@ -89,13 +90,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 +107,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 } 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 +} 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/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_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index f2e98a13c..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 } @@ -217,6 +225,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, @@ -259,6 +272,7 @@ var ( allowedThroughLNC: true, grpcWebURI: "/frdrpc.FaradayServer/RevenueReport", restWebURI: "/v1/faraday/revenue", + canDisable: true, }, { name: "looprpc", macaroonFn: loopMacaroonFn, @@ -267,6 +281,7 @@ var ( allowedThroughLNC: true, grpcWebURI: "/looprpc.SwapClient/ListSwaps", restWebURI: "/v1/loop/swaps", + canDisable: true, }, { name: "poolrpc", macaroonFn: poolMacaroonFn, @@ -275,6 +290,7 @@ var ( allowedThroughLNC: true, grpcWebURI: "/poolrpc.Trader/GetInfo", restWebURI: "/v1/pool/info", + canDisable: true, }, { name: "taprpc", macaroonFn: tapMacaroonFn, @@ -329,6 +345,17 @@ var ( allowedThroughLNC: false, 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 @@ -354,6 +381,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) @@ -451,7 +481,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", ) }) @@ -484,7 +514,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", ) }) @@ -554,7 +584,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", ) }) @@ -679,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, ) }) } @@ -805,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, ) }) } diff --git a/itest/litd_node.go b/itest/litd_node.go index ec85a53f1..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,49 +724,93 @@ 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 { - return s >= lnrpc.WalletState_SERVER_ACTIVE - }) + // 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 } - ctxt, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return wait.NoError(func() error { - faradayClient, err := hn.faradayClient() - if err != nil { - return err - } + litConn := litrpc.NewStatusClient(rawConn) - _, err = faradayClient.RevenueReport( - ctxt, &frdrpc.RevenueReportRequest{}, + err = wait.NoError(func() error { + states, err := litConn.SubServerStatus( + ctx, &litrpc.SubServerStatusReq{}, ) if err != nil { return err } - loopClient, err := hn.loopClient() - if err != nil { - return err + lndStatus, ok := states.SubServers[subservers.LND] + if !ok || !lndStatus.Running { + return fmt.Errorf("LND has not yet started") } - _, err = loopClient.ListSwaps(ctxt, &looprpc.ListSwapsRequest{}) - if err != nil { - return err + 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 { + 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 { + if faradayMode != terminal.ModeDisable { + faradayClient, err := hn.faradayClient() + if err != nil { + return err + } + + _, err = faradayClient.RevenueReport( + ctxt, &frdrpc.RevenueReportRequest{}, + ) + if err != nil { + return err + } } - poolClient, err := hn.poolClient() - 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 = poolClient.GetInfo(ctxt, &poolrpc.GetInfoRequest{}) - 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 + } } - tapMode, _ := hn.Cfg.ActiveArgs.getArg("taproot-assets-mode") if tapMode != terminal.ModeDisable { tapClient, err := hn.tapClient() if err != nil { 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) } 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/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) + } +} 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/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; +} diff --git a/rpc_proxy.go b/rpc_proxy.go index f4a96fc41..798f8592e 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() { - 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() { - 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,52 @@ 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 and proxy services is always allowed. + if isStatusReq(requestURI) || isProxyReq(requestURI) { + return nil + } + + // Check if the rpcProxy has started yet. + if !p.hasStarted() { + return ErrWaitingToStart + } + + handled, system := p.subServerMgr.Handles(requestURI) + switch { + case handled: + + case p.permsMgr.IsSubServerURI(subservers.LIT, requestURI): + system = subservers.LIT + + case p.permsMgr.IsSubServerURI(subservers.LND, requestURI): + system = subservers.LND + + default: + return ErrUnknownRequest + } + + // 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) { @@ -630,3 +678,19 @@ 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), + ) +} + +// 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), + ) +} 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 +} diff --git a/subservers/manager.go b/subservers/manager.go index 9b39d29a2..12aa8bcd5 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,37 +33,55 @@ 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) { +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 { + 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(), ) + + // 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() @@ -74,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() @@ -97,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 @@ -276,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 @@ -293,6 +334,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 66f5c35f4..a4ac4afdf 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 @@ -228,9 +232,13 @@ 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.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. @@ -240,8 +248,14 @@ 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 + // 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 // unlock lnd through the UI. @@ -263,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 @@ -272,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() @@ -381,7 +389,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, @@ -437,8 +445,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 } @@ -464,48 +477,36 @@ 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") } - // 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 { + g.statusMgr.SetErrored( + subservers.LND, "could not connect to LND: %v", err, + ) + return fmt.Errorf("could not connect to LND") } // 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. @@ -530,6 +531,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 { @@ -549,7 +555,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") @@ -585,8 +595,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 @@ -606,28 +619,32 @@ 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 { 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(): 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: - 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") @@ -897,25 +914,26 @@ 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) - litrpc.RegisterProxyServer(server, g.rpcProxy) } litrpc.RegisterFirewallServer(server, g.sessionRpcServer) @@ -977,6 +995,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) } @@ -1154,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. @@ -1468,25 +1507,31 @@ 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, - )) + 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.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.subServerMgr.AddServer( + subservers.NewPoolSubServer( + g.cfg.Pool, g.cfg.Remote.Pool, g.cfg.poolRemote, + ), g.cfg.PoolMode != ModeDisable, + ) - 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