diff --git a/app/lightning.js b/app/lightning.js index 591d7d6..ed32ff8 100644 --- a/app/lightning.js +++ b/app/lightning.js @@ -4,53 +4,109 @@ const fs = require("fs"); const logger = require("winston"); const debug = require("debug")("lncliweb:lightning"); -// expose the routes to our app with module.exports -module.exports = function (protoPath, lndHost, lndCertPath, macaroonPath) { +const LightningError = Object.freeze({ + "WALLET_LOCKED": "WALLET_LOCKED", + "NODE_UNREACHABLE": "NODE_UNREACHABLE", + "UNCATEGORIZED": "UNCATEGORIZED" +}); - process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA"; - const lnrpcDescriptor = grpc.load(protoPath); +/** + * Defines a wrapper around the Lightning gRPC API, with error support, retry, and async API. + * Every call towards `Lightning` should be handled through the `Call` API. + */ +class LightningManager { - if (lndCertPath) { + getActiveClient() { + if (!this.activeClient) { + logger.info("Recreating active client"); + this.credentials = this.generateCredentials(this.lndCert, {macaroonPath: this.macaroonPath}); + this.activeClient = new this.lnrpcDescriptor.lnrpc.Lightning(this.lndHost, this.credentials); + } + return this.activeClient; + } - if (fs.existsSync(lndCertPath)) { + generateCredentials(lndCert, options) { + let credentials = grpc.credentials.createSsl(lndCert); - const lndCert = fs.readFileSync(lndCertPath); - const sslCreds = grpc.credentials.createSsl(lndCert); + // If macaroon path was specified load credentials using macaroon metadata. + if (options.macaroonPath) { + if (fs.existsSync(options.macaroonPath)) { + let macaroonCreds = grpc.credentials.createFromMetadataGenerator(function (args, callback) { + let adminMacaroon = fs.readFileSync(options.macaroonPath); + let metadata = new grpc.Metadata(); + metadata.add("macaroon", adminMacaroon.toString("hex")); + callback(null, metadata); + }); + credentials = grpc.credentials.combineChannelCredentials(credentials, macaroonCreds); + } else { + logger.error("The specified macaroon file "+ options.macaroonPath + " was not found.\n" + + "Please add the missing lnd macaroon file or update/remove the path in the application configuration."); + process.exit(1); + } + } - var credentials; - if (macaroonPath) { - if (fs.existsSync(macaroonPath)) { - var macaroonCreds = grpc.credentials.createFromMetadataGenerator(function (args, callback) { - const adminMacaroon = fs.readFileSync(macaroonPath); - const metadata = new grpc.Metadata(); - metadata.add("macaroon", adminMacaroon.toString("hex")); - callback(null, metadata); - }); - credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds); - } else { - logger.error("The specified macaroon file \"" + macaroonPath + "\" was not found.\n" - + "Please add the missing lnd macaroon file or update/remove the path in the application configuration."); - process.exit(1); - } - } else { - credentials = sslCreds; - } + return credentials; + } - return new lnrpcDescriptor.lnrpc.Lightning(lndHost, credentials); + /** + * @constructor + * @param {string} protoPath - the path to the `rpc.proto` file that defined the `Lightning` RPC interface + * @param {string} lndHost - the host and port of the LND node (ex. "locahost:10003") + * @param {string} lndCertPath - the path to the SSL certificate used by LND + * @param {?string} macaroonPath - the path to the macarron file to use. Can be `null` if no macaroon should be used. + */ + constructor(protoPath, lndHost, lndCertPath, macaroonPath) { + process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA"; - } else { + if (!fs.existsSync(lndCertPath)) { + logger.error("Required lnd certificate path missing from application configuration."); + process.exit(1); + } - logger.error("The specified lnd certificate file \"" + lndCertPath + "\" was not found.\n" - + "Please add the missing lnd certificate file or update the path in the application configuration."); - process.exit(1); + // Define credentials for SSL certificate generated by LND, and active client + this.lndHost = lndHost; + this.lndCert = fs.readFileSync(lndCertPath); + this.lnrpcDescriptor = grpc.load(protoPath); + this.macaroonPath = macaroonPath; + this.activeClient = null; + } - } + /* + * Calls a Lightning gRPC method. + * @param {string} method - the gRPC method to call (ex. "getInfo") + * @param {Object} parameters - optional key/value parameters to supply for the API call + * @returns {Promise} - if successful, response if an Object containing API result payload, otherwise it will fail + with a LightningError. + */ + async call(method, parameters) { + return new Promise((resolve, reject) => { + let activeClient = this.getActiveClient(); + activeClient[method](parameters, (err, response) => { + if (err) { - } else { + // drop active client, so that it can be recreated + this.activeClient = null; - logger.error("Required lnd certificate path missing from application configuration."); - process.exit(1); + switch(err.code) { + case grpc.status.UNIMPLEMENTED: + reject(LightningError.WALLET_LOCKED); + break; + case grpc.status.UNAVAILABLE: + reject(LightningError.NODE_UNREACHABLE); + break; + default: + logger.error("Unrecognized gRPC error: " + err); + reject(LightningError.UNCATEGORIZED); + } + } else { + logger.debug(method + ":", response); + resolve(response); + } + }); + }); + } +} - } -}; + +module.exports = LightningManager; diff --git a/app/lnd.js b/app/lnd.js index f7321fb..4bb25d8 100644 --- a/app/lnd.js +++ b/app/lnd.js @@ -17,7 +17,7 @@ module.exports = function (lightning) { logger.debug("Lnd invoices subscription stream already opened."); } else { logger.debug("Opening lnd invoices subscription stream..."); - lndInvoicesStream = lightning.subscribeInvoices({}); + lndInvoicesStream = lightning.getActiveClient().subscribeInvoices({}); logger.debug("Lnd invoices subscription stream opened."); lndInvoicesStream.on("data", function (data) { logger.debug("SubscribeInvoices Data", data); diff --git a/app/lnsignauth.js b/app/lnsignauth.js index 8e15d27..151d319 100644 --- a/app/lnsignauth.js +++ b/app/lnsignauth.js @@ -23,7 +23,7 @@ module.exports = function (lightning, config) { debug("sessionID.signature:", user.name); - lightning.verifyMessage({ msg: Buffer.from(req.sessionID, "utf8"), signature: user.name }, function (err, response) { + lightning.getActiveClient().verifyMessage({ msg: Buffer.from(req.sessionID, "utf8"), signature: user.name }, function (err, response) { if (err) { debug("VerifyMessage Error:", err); unauthorized(res); diff --git a/app/lnsignpayreqauth.js b/app/lnsignpayreqauth.js index 843b1b1..db0c55a 100644 --- a/app/lnsignpayreqauth.js +++ b/app/lnsignpayreqauth.js @@ -28,7 +28,7 @@ module.exports = function (lightning, config) { debug("payreq.signature:", user.name); - lightning.verifyMessage({ msg: Buffer.from(config.defaultAuthPayReq, "utf8"), signature: user.name }, function (err, verifMsgResponse) { + lightning.getActiveClient().verifyMessage({ msg: Buffer.from(config.defaultAuthPayReq, "utf8"), signature: user.name }, function (err, verifMsgResponse) { if (err) { debug("VerifyMessage Error:", err); unauthorized(res); diff --git a/app/routes.js b/app/routes.js index 54fb6e0..49c6b6b 100644 --- a/app/routes.js +++ b/app/routes.js @@ -12,431 +12,221 @@ const DEFAULT_FINAL_CLTV_DELTA = 144; // expose the routes to our app with module.exports module.exports = function (app, lightning, db, config) { - // api --------------------------------------------------------------------- - - // get lnd network info - app.get("/api/lnd/getnetworkinfo", function (req, res) { - lightning.getNetworkInfo({}, function (err, response) { - if (err) { - logger.debug("GetNetworkInfo Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("GetNetworkInfo:", response); - res.json(response); - } - }); - }); - - // get lnd info - app.get("/api/lnd/getinfo", function (req, res) { - lightning.getInfo({}, function (err, response) { - if (err) { - logger.debug("GetInfo Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("GetInfo:", response); - if (!response.uris || response.uris.length === 0) { - if (config.lndAddress) { - response.uris = [ - response.identity_pubkey + "@" + config.lndAddress - ]; - } - } - res.json(response); - } - }); - }); - - // get lnd node info - app.post("/api/lnd/getnodeinfo", function (req, res) { - lightning.getNodeInfo({ pub_key: req.body.pubkey }, function (err, response) { - if (err) { - logger.debug("GetNodeInfo Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("GetNodeInfo:", response); - res.json(response); - } - }); - }); - - // get lnd node active channels list - app.get("/api/lnd/listpeers", function (req, res) { - lightning.listPeers({}, function (err, response) { - if (err) { - logger.debug("ListPeers Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ListPeers:", response); - res.json(response); - } - }); - }); - - // get lnd node opened channels list - app.get("/api/lnd/listchannels", function (req, res) { - lightning.listChannels({}, function (err, response) { - if (err) { - logger.debug("ListChannels Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ListChannels:", response); - res.json(response); - } - }); - }); - - // get lnd node pending channels list - app.get("/api/lnd/pendingchannels", function (req, res) { - lightning.pendingChannels({}, function (err, response) { - if (err) { - logger.debug("PendingChannels Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("PendingChannels:", response); - res.json(response); - } - }); - }); - - // get lnd node payments list - app.get("/api/lnd/listpayments", function (req, res) { - lightning.listPayments({}, function (err, response) { - if (err) { - logger.debug("ListPayments Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ListPayments:", response); - res.json(response); - } - }); - }); - - // get lnd node invoices list - app.get("/api/lnd/listinvoices", function (req, res) { - lightning.listInvoices({}, function (err, response) { - if (err) { - logger.debug("ListInvoices Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ListInvoices:", response); - res.json(response); - } - }); - }); - - // get lnd node forwarding history - app.get("/api/lnd/forwardinghistory", function (req, res) { - lightning.forwardingHistory({}, function (err, response) { - if (err) { - logger.debug("ForwardingHistory Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ForwardingHistory:", response); - res.json(response); - } - }); - }); - - // get the lnd node wallet balance - app.get("/api/lnd/walletbalance", function (req, res) { - lightning.walletBalance({}, function (err, response) { - if (err) { - logger.debug("WalletBalance Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("WalletBalance:", response); - res.json(response); - } - }); - }); - - // get the lnd node channel balance - app.get("/api/lnd/channelbalance", function (req, res) { - lightning.channelBalance({}, function (err, response) { - if (err) { - logger.debug("ChannelBalance Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ChannelBalance:", response); - res.json(response); - } - }); - }); - - // connect peer to lnd node - app.post("/api/lnd/connectpeer", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - var connectRequest = { addr: { pubkey: req.body.pubkey, host: req.body.host }, perm: true }; - logger.debug("ConnectPeer Request:", connectRequest); - lightning.connectPeer(connectRequest, function (err, response) { - if (err) { - logger.debug("ConnectPeer Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("ConnectPeer:", response); - res.json(response); - } - }); - } - }); - - // disconnect peer from lnd node - app.post("/api/lnd/disconnectpeer", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - var disconnectRequest = { pub_key: req.body.pubkey }; - logger.debug("DisconnectPeer Request:", disconnectRequest); - lightning.disconnectPeer(disconnectRequest, function (err, response) { - if (err) { - logger.debug("DisconnectPeer Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("DisconnectPeer:", response); - res.json(response); - } - }); - } - }); - - // addinvoice - app.post("/api/lnd/addinvoice", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - var invoiceRequest = { memo: req.body.memo }; - if (req.body.value) { - invoiceRequest.value = req.body.value; - } - if (req.body.expiry) { - invoiceRequest.expiry = req.body.expiry; - } - lightning.addInvoice(invoiceRequest, function (err, response) { - if (err) { - logger.debug("AddInvoice Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("AddInvoice:", response); - res.json(response); - } - }); - } - }); - - // sendpayment - app.post("/api/lnd/sendpayment", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - var paymentRequest = { payment_request: req.body.payreq }; - if (req.body.amt) { - paymentRequest.amt = req.body.amt; - } - logger.debug("Sending payment", paymentRequest); - lightning.sendPaymentSync(paymentRequest, function (err, response) { - if (err) { - logger.debug("SendPayment Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("SendPayment:", response); - res.json(response); - } - }); - } - }); - - // decodepayreq - app.post("/api/lnd/decodepayreq", function (req, res) { - lightning.decodePayReq({ pay_req: req.body.payreq }, function (err, response) { - if (err) { - logger.debug("DecodePayReq Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("DecodePayReq:", response); - res.json(response); - } - }); - }); + /* + * Creates an adapter between Express requests and Lightning gRPC requests via LightningManager. + * + * @param {string} methodName - the RPC call to perform on the Lightning service + * @param {?bool} options.isLimitedToAuthorizedUser - forces request to come from an autorized client + * @param {?function} options.preHook - if present, calls the function associated with this variable, and feeds + the result as parameters to the RPC call. + * @param {?function} options.postHook - if present, calls the function associated with this variable, and transforms + the result from the RPC call. This function must return a valid Object + */ + var lightningRPCAdapter = function(methodName, options) { + return async function(req, res) { + + options = options || {}; + + // if isLimitedToAuthorizedUser is true, we check if the `limituser` flag + // is set on the request, and short-circuit the request if the user is not + // authorized. + if (options.isLimitedToAuthorizedUser && req.limituser) { + return res.sendStatus(403); + } + + // By default, input parameters are empty. if preHook was defined, we call + // this and use the result and input parameters + var params = {}; + if (options.preHook) { + params = options.preHook(req); + } + + try { + let response = await lightning.call(methodName, params); + + // If result needs to be manipulated before it's returned + // to the client (because postHook is defined), call postHook + // and use the result as payload to return via JSON + if (options.postHook) { + response = options.postHook(req, response); + } + res.json(response); + } catch(e) { + res.json({ error: e }); + } + } + }; - // queryroute - app.post("/api/lnd/queryroute", function (req, res) { + // api --------------------------------------------------------------------- + app.get("/api/lnd/getnetworkinfo", lightningRPCAdapter("getNetworkInfo")); + app.post("/api/lnd/getnodeinfo", lightningRPCAdapter("getNodeInfo")); + app.get("/api/lnd/listpeers", lightningRPCAdapter("listPeers")); + app.get("/api/lnd/listhannels", lightningRPCAdapter("listChannels")); + app.get("/api/lnd/listpeers", lightningRPCAdapter("listPeers")); + app.get("/api/lnd/listchannels", lightningRPCAdapter("listChannels")); + app.get("/api/lnd/pendingchannels", lightningRPCAdapter("pendingChannels")); + app.get("/api/lnd/listpayments", lightningRPCAdapter("listPayments")); + app.get("/api/lnd/listinvoices", lightningRPCAdapter("listInvoices")); + app.get("/api/lnd/forwardinghistory", lightningRPCAdapter("forwardingHistory")); + app.get("/api/lnd/walletbalance", lightningRPCAdapter("walletBalance")); + app.get("/api/lnd/channelbalance", lightningRPCAdapter("channelBalance")); + + app.get("/api/lnd/getinfo", lightningRPCAdapter("getInfo", { + postHook: (req, response) => { + if ((!response.uris || response.uris.length === 0) && (config.lndAddress)) { + response.uris = [response.identity_pubkey + "@" + config.lndAddress]; + } + return response; + } + })); + + app.get("/api/lnd/connectpeer", lightningRPCAdapter("connectPeer", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return { addr: { pubkey: req.body.pubkey, host: req.body.host }, perm: true }; + } + })); + + app.post("/api/lnd/disconnectPeer", lightningRPCAdapter("disconnectPeer", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return {pub_key: req.body.pubkey}; + } + })); + + app.post("/api/lnd/addinvoice", lightningRPCAdapter("addInvoice", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + var invoiceRequest = { memo: req.body.memo }; + if (req.body.value) { + invoiceRequest.value = req.body.value; + } + if (req.body.expiry) { + invoiceRequest.expiry = req.body.expiry; + } + return invoiceRequest; + } + })); + + app.post("/api/lnd/sendpayment", lightningRPCAdapter("addInvoice", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + var paymentRequest = { payment_request: req.body.payreq }; + if (req.body.amt) { + paymentRequest.amt = req.body.amt; + } + return paymentRequest; + } + })); + + app.post("/api/lnd/decodepayreq", lightningRPCAdapter("decodePayReq", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return {pay_req: req.body.payreq}; + } + })); + + app.post("/api/lnd/queryroute", lightningRPCAdapter("queryRoutes", { + preHook: (req) => { var numRoutes = config.maxNumRoutesToQuery || DEFAULT_MAX_NUM_ROUTES_TO_QUERY; var finalCltvDelta = config.finalCltvDelta || DEFAULT_FINAL_CLTV_DELTA; - lightning.queryRoutes({ - pub_key: req.body.pubkey, - amt: req.body.amt, - num_routes: numRoutes, - final_cltv_delta: finalCltvDelta - }, function (err, response) { - if (err) { - logger.debug("QueryRoute Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("QueryRoute:", response); - res.json(response); - } - }); - }); - - // sendtoroute - app.post("/api/lnd/sendtoroute", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - var sendToRouteRequest = { - payment_hash_string: req.body.payhash, - routes: JSON.parse(req.body.routes) - }; - logger.debug("SendToRoute", sendToRouteRequest); - lightning.sendToRouteSync(sendToRouteRequest, function (err, response) { - if (err) { - logger.debug("SendToRoute Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("SendToRoute:", response); - res.json(response); - } - }); - } - }); - - // newaddress - app.post("/api/lnd/newaddress", function (req, res) { - lightning.newAddress({ type: req.body.type }, function (err, response) { - if (err) { - logger.debug("NewAddress Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("NewAddress:", response); - res.json(response); - } - }); - }); - - // sendcoins - app.post("/api/lnd/sendcoins", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - var sendCoinsRequest = { addr: req.body.addr, amount: req.body.amount }; - logger.debug("SendCoins", sendCoinsRequest); - lightning.sendCoins(sendCoinsRequest, function (err, response) { - if (err) { - logger.debug("SendCoins Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("SendCoins:", response); - res.json(response); - } - }); - } - }); - - // rendergraph - app.post("/api/lnd/rendergraph", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - if (commandExistsSync("dot")) { - lightning.describeGraph({}, function (err, response) { - if (err) { - logger.debug("DescribeGraph Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("DescribeGraph:", response); - - var peers = req.body.peers || {}; - - var nodesMap = {}; - var nodes = response.nodes; - var i; - var node; - for (i = 0; i < nodes.length; i++) { - node = nodes[i]; - nodesMap[node.pub_key] = node; - } - - var channeledNodes = {}; - var edges = response.edges; - var edge; - for (i = 0; i < edges.length; i++) { - edge = edges[i]; - if (nodesMap[edge.node1_pub] && nodesMap[edge.node2_pub]) { // skip buggy edges - channeledNodes[edge.node1_pub] = edge.node1_pub; - channeledNodes[edge.node2_pub] = edge.node2_pub; - } - } - - // Create digraph - var graphName = "LightningNetwork"; - var g = graphviz.graph(graphName); - - for (var nodePubKey in channeledNodes) { - if (channeledNodes.hasOwnProperty(nodePubKey)) { - // Add node - node = nodesMap[nodePubKey]; - var peer = peers[nodePubKey]; - var nodeLabel; - if (peer && peer.alias) { - nodeLabel = peer.alias; - } else { - nodeLabel = node.pub_key.substr(0, 10); - } - console.log(node, nodeLabel); - g.addNode(node.pub_key, { label: nodeLabel }); - } - } - - for (i = 0; i < edges.length; i++) { - // Add edge - edge = edges[i]; - if (channeledNodes[edge.node1_pub] && channeledNodes[edge.node2_pub]) { // skip buggy edges - var edgeLabel = " " + edge.channel_id.substr(0, 10); - g.addEdge(edge.node1_pub, edge.node2_pub, { label: edgeLabel, fontsize: "12.0" }); - } - } - - // Print the dot script - console.log(g.to_dot()); - - // Set GraphViz path (if not in your path) - //g.setGraphVizPath("/usr/local/bin"); - // Generate a SVG output - g.output("svg", __dirname + "/../data/networkgraph.svg"); - - res.json(response); - } - }); - } else { - logger.debug("Missing graphviz"); - return res.status(500).send("Missing graphviz"); - } - } - }); + return { + pub_key: req.body.pubkey, + amt: req.body.amt, + num_routes: numRoutes, + final_cltv_delta: finalCltvDelta + }; + } + })); + + app.post("/api/lnd/sendtoroute", lightningRPCAdapter("sendToRouteSync", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return { + payment_hash_string: req.body.payhash, + routes: JSON.parse(req.body.routes) + }; + } + })); + + app.post("/api/lnd/newaddress", lightningRPCAdapter("newAddress", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return {type: req.body.type}; + } + })); + + app.post("/api/lnd/sendcoins", lightningRPCAdapter("sendCoins", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return {addr: req.body.addr, amount: req.body.amount}; + } + })); + + app.post("/api/lnd/rendergraph", lightningRPCAdapter("describeGraph", { + isLimitedToAuthorizedUser: true, + postHook: (req, response) => { + var peers = req.body.peers || {}; + + var nodesMap = {}; + var nodes = response.nodes; + var i; + var node; + for (i = 0; i < nodes.length; i++) { + node = nodes[i]; + nodesMap[node.pub_key] = node; + } + + var channeledNodes = {}; + var edges = response.edges; + var edge; + for (i = 0; i < edges.length; i++) { + edge = edges[i]; + if (nodesMap[edge.node1_pub] && nodesMap[edge.node2_pub]) { // skip buggy edges + channeledNodes[edge.node1_pub] = edge.node1_pub; + channeledNodes[edge.node2_pub] = edge.node2_pub; + } + } + + // Create digraph + var graphName = "LightningNetwork"; + var g = graphviz.graph(graphName); + + for (var nodePubKey in channeledNodes) { + if (channeledNodes.hasOwnProperty(nodePubKey)) { + // Add node + node = nodesMap[nodePubKey]; + var peer = peers[nodePubKey]; + var nodeLabel; + if (peer && peer.alias) { + nodeLabel = peer.alias; + } else { + nodeLabel = node.pub_key.substr(0, 10); + } + console.log(node, nodeLabel); + g.addNode(node.pub_key, { label: nodeLabel }); + } + } + + for (i = 0; i < edges.length; i++) { + // Add edge + edge = edges[i]; + if (channeledNodes[edge.node1_pub] && channeledNodes[edge.node2_pub]) { // skip buggy edges + var edgeLabel = " " + edge.channel_id.substr(0, 10); + g.addEdge(edge.node1_pub, edge.node2_pub, { label: edgeLabel, fontsize: "12.0" }); + } + } + + // Print the dot script + console.log(g.to_dot()); + + // Set GraphViz path (if not in your path) + //g.setGraphVizPath("/usr/local/bin"); + // Generate a SVG output + g.output("svg", __dirname + "/../data/networkgraph.svg"); + + } + })); // networkgraph.svg app.get("/api/lnd/networkgraph.svg", function (req, res) { @@ -447,37 +237,22 @@ module.exports = function (app, lightning, db, config) { } }); - // signmessage - app.post("/api/lnd/signmessage", function (req, res) { - if (req.limituser) { - return res.sendStatus(403); // forbidden - } else { - lightning.signMessage({ msg: Buffer.from(req.body.msg, "utf8") }, function (err, response) { - if (err) { - logger.debug("SignMessage Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("SignMessage:", response); - res.json(response); - } - }); - } - }); - - // verifymessage - app.post("/api/lnd/verifymessage", function (req, res) { - lightning.verifyMessage({ msg: Buffer.from(req.body.msg, "utf8"), signature: req.body.signature }, function (err, response) { - if (err) { - logger.debug("VerifyMessage Error:", err); - err.error = err.message; - res.send(err); - } else { - logger.debug("VerifyMessage:", response); - res.json(response); - } - }); - }); + app.post("/api/lnd/signmessage", lightningRPCAdapter("signMessage", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return {msg: Buffer.from(req.body.msg, "utf8")}; + } + })); + + app.post("/api/lnd/verifymessage", lightningRPCAdapter("verifyMessage", { + isLimitedToAuthorizedUser: true, + preHook: (req) => { + return { + msg: Buffer.from(req.body.msg, "utf8"), + signature: req.body.signature + }; + } + })); // ln-payreq-auth.html app.get("/ln-payreq-auth.html", function (req, res) { diff --git a/app/server-le.js b/app/server-le.js index 9413a83..3214a05 100644 --- a/app/server-le.js +++ b/app/server-le.js @@ -37,11 +37,7 @@ module.exports = function (program) { // db init ================= const db = require("./database")(defaults.dataPath); - // setup lightning client ================= - const lndHost = program.lndhost || defaults.lndHost; - const lndCertPath = program.lndCertPath || defaults.lndCertPath; - const macaroonPath = program.macaroonPath || defaults.macaroonPath; - const lightning = require("./lightning")(defaults.lndProto, lndHost, lndCertPath, macaroonPath); + var lightning = module.makeLightningManager(program); // init lnd module ================= const lnd = require("./lnd")(lightning); diff --git a/app/server-utils.js b/app/server-utils.js index 967ce61..1119b27 100644 --- a/app/server-utils.js +++ b/app/server-utils.js @@ -1,13 +1,32 @@ // app/utils.js const debug = require("debug")("lncliweb:utils"); +const defaults = require("../config/defaults"); const logger = require("winston"); +const LightningManager = require('./lightning') // TODO module.exports = function (server) { var module = {}; + server.makeLightningManager = function(program) { + var lndHost = program.lndhost || defaults.lndHost; + var lndCertPath = program.lndCertPath || defaults.lndCertPath; + + // If `disableMacaroon` is set, ignore macaroon support for the session. Otherwise + // we read from `macarooonPath` variable and alternatively fallback to default `macaroonPath`. + var macaroonPath = null; + if (program.disableMacaroon) { + console.log("Macaroon support is disabled") + } else { + macaroonPath = program.macaroonPath || defaults.macaroonPath; + console.log("Macaroon support is enabled. Macaroon path is " + macaroonPath); + } + + return new LightningManager(defaults.lndProto, lndHost, lndCertPath, macaroonPath); + } + server.getURL = function () { return "http" + (this.useTLS ? "s" : "") + "://" + this.serverHost + (this.useTLS diff --git a/app/server.js b/app/server.js index 52a19fc..112546e 100644 --- a/app/server.js +++ b/app/server.js @@ -34,24 +34,7 @@ module.exports = function (program) { // db init ================= const db = require("./database")(defaults.dataPath); - // setup lightning client ================= - const lndHost = program.lndhost || defaults.lndHost; - - - // define macaroon configuration here. - const lndCertPath = program.lndCertPath || defaults.lndCertPath; - - // If `disableMacaroon` is set, ignore macaroon support for the session. Otherwise - // we read from `macarooonPath` variable and alternatively fallback to default `macaroonPath`. - var macaroonPath = null; - if (program.disableMacaroon) { - console.log("Macaroon support is disabled") - } else { - macaroonPath = program.macaroonPath || defaults.macaroonPath; - console.log("Macaroon support is enabled. Macaroon path is " + macaroonPath); - } - - const lightning = require("./lightning")(defaults.lndProto, lndHost, lndCertPath, macaroonPath); + var lightning = module.makeLightningManager(program); // init lnd module ================= const lnd = require("./lnd")(lightning); diff --git a/app/sockets.js b/app/sockets.js index 5ec7ce2..27c9d99 100644 --- a/app/sockets.js +++ b/app/sockets.js @@ -243,7 +243,7 @@ module.exports = function (io, lightning, lnd, login, pass, limitlogin, limitpas } debug("openChannelRequest", openChannelRequest); - var call = lightning.openChannel(openChannelRequest); + var call = lightning.getActiveClient().openChannel(openChannelRequest); call.on("data", function (data) { logger.debug("OpenChannel Data", data); socket.emit(OPENCHANNEL_EVENT, { rid: rid, evt: "data", data: data }); @@ -291,7 +291,7 @@ module.exports = function (io, lightning, lnd, login, pass, limitlogin, limitpas }; debug("closeChannelRequest", closeChannelRequest); - var call = lightning.closeChannel(closeChannelRequest); + var call = lightning.getActiveClient().closeChannel(closeChannelRequest); call.on("data", function (data) { logger.debug("CloseChannel Data", data); socket.emit(CLOSECHANNEL_EVENT, { rid: rid, evt: "data", data: data });