Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lnd manager #163

Merged
merged 14 commits into from
Sep 28, 2018
130 changes: 93 additions & 37 deletions app/lightning.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PirosB3 seems a bit excessive to me to drop the client on every gRPC error we are receiving 🤔


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);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PirosB3 looks like we are losing the original gRPC error here.
We need it to be able to handle it correctly on the web-client side.

}
} else {
logger.debug(method + ":", response);
resolve(response);
}
});
});
}
}

}
};

module.exports = LightningManager;
2 changes: 1 addition & 1 deletion app/lnd.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion app/lnsignauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion app/lnsignpayreqauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading