Skip to content

Commit

Permalink
Added C-Lightning (Sparko)
Browse files Browse the repository at this point in the history
  • Loading branch information
chill117 committed Apr 11, 2022
1 parent 8d90a72 commit a949e32
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 108 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

* TBD:
* Added C-Lightning backend - JSON-RPC over unix sock or HTTP-RPC made available by Sparko plugin
* v1.1.1:
* Added missing form inputs
* Fix mutation of check method error messages on backend prototypes
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Node.js module to integrate with various Lightning Network node software and ser

The following list includes all the Lightning Network node software and service providers which are supported:
* [Lightning Network Daemon (lnd)](https://github.com/LightningNetwork/lnd)
* [C-Lightning](https://github.com/ElementsProject/lightning) - JSON-RPC interface over unix sock or HTTP-RPC interface made available by [Sparko plugin](https://github.com/fiatjaf/sparko)
* [coinos](https://coinos.io/home)
* [lnbits](https://github.com/lnbits/lnbits-legend)
* [lndhub](https://github.com/BlueWallet/LndHub)
Expand Down Expand Up @@ -118,6 +119,17 @@ Lightning Network Daemon (lnd):
* __torSocksProxy__ - If hostname contains an onion address, the backend will try to connect to it using the the TOR socks proxy. Default:
* `127.0.0.1:9050`

C-Lightning (unix sock):
* __unixSockPath__ - The absolute file path to the unix sock of c-lightning. Example:
* `/path/to/unix/sock/.lightning/lightning-rpc`

C-Lightning (Sparko):
* __baseUrl__ - Full URL and path of the Sparko plugin's HTTP-RPC API. Onion addresses are supported. Examples:
* `https://127.0.0.1:9737/rpc`
* `http://esdlkvxdkwxz6yqs6rquapg4xxt4pt4guj24k75pdnquo5nau135ugyd.onion/rpc`
* __cert__ - The TLS certificate used by the Sparko plugin.
* __accessKey__ - See `--sparko-keys=` in your lightningd config.

coinos:
* __baseUrl__ - The URL of the CoinOS instance. Example:
* `https://coinos.io`
Expand Down
158 changes: 158 additions & 0 deletions lib/backends/c-lightning-sparko.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
const assert = require('assert');
const HttpLightningBackend = require('../HttpLightningBackend');
const CLightningBackend = require('./c-lightning');
const tlsCheck = require('../tlsCheck');
const tls = require('tls');
const url = require('url');
const { ValidationError } = require('@bleskomat/form');

class Backend extends HttpLightningBackend {

// https://github.com/fiatjaf/sparko
static name = 'c-lightning-sparko';

constructor(options) {
options = options || {};
super(Backend.name, options, {
defaultOptions: {
requestContentType: 'json',
responseType: 'json',
},
requiredOptions: ['baseUrl', 'accessKey'],
});
this.options.headers['X-Access'] = this.options.accessKey;
}

getNodeUri() {
return CLightningBackend.prototype.getNodeUri.apply(this, arguments);
}

openChannel() {
return CLightningBackend.prototype.openChannel.apply(this, arguments);
}

payInvoice() {
return CLightningBackend.prototype.payInvoice.apply(this, arguments);
}

addInvoice() {
return CLightningBackend.prototype.addInvoice.apply(this, arguments);
}

getInvoiceStatus() {
return CLightningBackend.prototype.getInvoiceStatus.apply(this, arguments);
}

cmd(method, params) {
const data = { method, params };
const uri = '';
return this.request('post', uri, data).then(result => {
return result;
}).catch(error => {
if (/Unexpected HTTP response status: 401/i.test(error.message)) {
throw new Error('Unauthorized');
}
throw error;
});
}
};

Backend.prototype.checkMethodErrorMessages = {
payInvoice: {
ok: [
'not reachable directly and all routehints were unusable',
'No path found',
'failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)',
'Cannot split payment any further',
],
notOk: [
'Unauthorized',
'Prefix bc is not for testnet',
'Socks5 proxy rejected connection - Failure',
],
},
};

Backend.form = {
label: 'C-Lightning (Sparko)',
inputs: [
{
name: 'baseUrl',
label: 'Base URL',
help: 'Full URL and path of the Sparko plugin\'s HTTP-RPC API. Onion addresses are supported.',
type: 'text',
placeholder: 'https://127.0.0.1:9737/rpc',
default: '',
required: true,
validate: function(value) {
if (value) {
let parsedUrl;
try { parsedUrl = url.parse(value); } catch (error) {
throw new ValidationError('"Base URL" must be a valid URL - e.g. https://127.0.0.1:9737/rpc');
}
const { hostname, protocol } = parsedUrl;
assert.ok(hostname.substr(-6) === '.onion' || protocol === 'https:', new ValidationError('Except in the case of onion addresses, "Base URL" must use the https protocol.'));
}
},
},
{
name: 'cert',
label: 'TLS Certificate',
help: 'Automatically retrieved from service at the URL provided above. Please check this against your sparko plugin\'s tls cert file.',
type: 'textarea',
default: '',
readonly: true,
required: function(data) {
const baseUrl = data && data['c-lightning-sparko[baseUrl]'] || null;
return baseUrl && baseUrl.split('://')[0] === 'https';
},
validate: function(value, data) {
if (value) {
const baseUrl = data && data['c-lightning-sparko[baseUrl]'] || null;
if (baseUrl) {

let parsedUrl;
try { parsedUrl = url.parse(baseUrl); } catch (error) {
// Base URL not valid. Skip this check.
}
const { host } = parsedUrl;
return tlsCheck(host, {
requestCert: false,
rejectUnauthorized: false,
secureContext: tls.createSecureContext({
ca: value,
}),
}).then(result => {
assert.ok(result.authorized, new ValidationError(`Unable to establish secure connection to ${host} with the provided TLS certificate`));
});
}
}
},
rows: 4,
},
{
name: 'fingerprint',
label: 'Fingerprint (sha1)',
type: 'text',
default: '',
disabled: true,
},
{
name: 'fingerprint256',
label: 'Fingerprint (sha256)',
type: 'text',
default: '',
disabled: true,
},
{
name: 'accessKey',
label: 'Access Key',
help: 'See "--sparko-keys=" in your lightningd config',
type: 'text',
default: '',
required: true,
},
],
};

module.exports = Backend;
5 changes: 4 additions & 1 deletion lib/backends/clightning.js → lib/backends/c-lightning.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const JsonRpcLightningBackend = require('../JsonRpcLightningBackend');

class Backend extends JsonRpcLightningBackend {

static name = 'clightning';
static name = 'c-lightning';

constructor(options) {
options = options || {};
Expand Down Expand Up @@ -94,6 +94,9 @@ Backend.prototype.checkMethodErrorMessages = {
payInvoice: {
ok: [
'not reachable directly and all routehints were unusable',
'No path found',
'failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)',
'Cannot split payment any further',
],
notOk: [
'Could not connect to unix sock',
Expand Down
10 changes: 9 additions & 1 deletion lib/createForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const valuesToBackendConfig = function(values) {
const { backend } = values;
let config = {};
Object.entries(values).forEach(([key, value], index) => {
const match = key.match(/([a-z]+)\[([^\[\]]+)\]/i);
const match = key.match(/([^\[]+)\[([^\[\]]+)\]/i);
if (!match || !match[1] || !match[2]) return null;
if (match[1] !== backend) return null;
config[match[2]] = value;
Expand All @@ -92,6 +92,14 @@ const valuesToBackendConfig = function(values) {
config.cert = config.cert && { data: config.cert } || null;
config.macaroon = config.macaroon && { data: config.macaroon } || null;
break;
case 'c-lightning-sparko':
if (typeof config.fingerprint !== 'undefined') {
delete config.fingerprint;
}
if (typeof config.fingerprint256 !== 'undefined') {
delete config.fingerprint256;
}
break;
}
return config;
};
Expand Down
8 changes: 6 additions & 2 deletions test/unit/lib/backends/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('backends', function() {
getBackends().forEach(Backend => {

const { name } = Backend;
const NAME = name.toUpperCase();
const NAME = name.replace(/-/g, '').toUpperCase();

describe(name, function() {

Expand All @@ -33,7 +33,11 @@ describe('backends', function() {

it('payInvoice', function() {
this.timeout(30000);
return checkBackend(name, config, { method: 'payInvoice', network });
return checkBackend(name, config, { method: 'payInvoice', network }).then(result => {
assert.strictEqual(typeof result, 'object');
assert.strictEqual(result.ok, true);
assert.strictEqual(typeof result.message, 'undefined');
});
});
});

Expand Down
Loading

0 comments on commit a949e32

Please sign in to comment.