From eb90434c28f7d19b1e172226438291195f8105f0 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 29 Jul 2022 17:41:09 +0200 Subject: [PATCH] Delete historical generation code --- .../lightning/clightning/clightning-client.ts | 265 +++++++++++++++++- .../clightning/clightning-convert.ts | 44 +-- .../src/api/lightning/clightning/jsonrpc.ts | 249 ---------------- .../lightning-api-abstract-factory.ts | 2 - .../api/lightning/lightning-api-factory.ts | 5 +- .../src/tasks/lightning/node-sync.service.ts | 2 +- 6 files changed, 295 insertions(+), 272 deletions(-) delete mode 100644 backend/src/api/lightning/clightning/jsonrpc.ts diff --git a/backend/src/api/lightning/clightning/clightning-client.ts b/backend/src/api/lightning/clightning/clightning-client.ts index 2b974bca0..629092d03 100644 --- a/backend/src/api/lightning/clightning/clightning-client.ts +++ b/backend/src/api/lightning/clightning/clightning-client.ts @@ -1,4 +1,263 @@ -import config from '../../../config'; -import CLightningClient from './jsonrpc'; +// Imported from https://github.com/shesek/lightning-client-js -export default new CLightningClient(config.CLIGHTNING.SOCKET); +'use strict'; + +const methods = [ + 'addgossip', + 'autocleaninvoice', + 'check', + 'checkmessage', + 'close', + 'connect', + 'createinvoice', + 'createinvoicerequest', + 'createoffer', + 'createonion', + 'decode', + 'decodepay', + 'delexpiredinvoice', + 'delinvoice', + 'delpay', + 'dev-listaddrs', + 'dev-rescan-outputs', + 'disableoffer', + 'disconnect', + 'estimatefees', + 'feerates', + 'fetchinvoice', + 'fundchannel', + 'fundchannel_cancel', + 'fundchannel_complete', + 'fundchannel_start', + 'fundpsbt', + 'getchaininfo', + 'getinfo', + 'getlog', + 'getrawblockbyheight', + 'getroute', + 'getsharedsecret', + 'getutxout', + 'help', + 'invoice', + 'keysend', + 'legacypay', + 'listchannels', + 'listconfigs', + 'listforwards', + 'listfunds', + 'listinvoices', + 'listnodes', + 'listoffers', + 'listpays', + 'listpeers', + 'listsendpays', + 'listtransactions', + 'multifundchannel', + 'multiwithdraw', + 'newaddr', + 'notifications', + 'offer', + 'offerout', + 'openchannel_abort', + 'openchannel_bump', + 'openchannel_init', + 'openchannel_signed', + 'openchannel_update', + 'pay', + 'payersign', + 'paystatus', + 'ping', + 'plugin', + 'reserveinputs', + 'sendinvoice', + 'sendonion', + 'sendonionmessage', + 'sendpay', + 'sendpsbt', + 'sendrawtransaction', + 'setchannelfee', + 'signmessage', + 'signpsbt', + 'stop', + 'txdiscard', + 'txprepare', + 'txsend', + 'unreserveinputs', + 'utxopsbt', + 'waitanyinvoice', + 'waitblockheight', + 'waitinvoice', + 'waitsendpay', + 'withdraw' +]; + + +import EventEmitter from 'events'; +import { existsSync, statSync } from 'fs'; +import { createConnection, Socket } from 'net'; +import { homedir } from 'os'; +import path from 'path'; +import { createInterface, Interface } from 'readline'; +import logger from '../../../logger'; +import { AbstractLightningApi } from '../lightning-api-abstract-factory'; +import { ILightningApi } from '../lightning-api.interface'; +import { convertAndmergeBidirectionalChannels, convertNode } from './clightning-convert'; + +class LightningError extends Error { + type: string = 'lightning'; + message: string = 'lightning-client error'; + + constructor(error) { + super(); + this.type = error.type; + this.message = error.message; + } +} + +const defaultRpcPath = path.join(homedir(), '.lightning') + , fStat = (...p) => statSync(path.join(...p)) + , fExists = (...p) => existsSync(path.join(...p)) + +export default class CLightningClient extends EventEmitter implements AbstractLightningApi { + private rpcPath: string; + private reconnectWait: number; + private reconnectTimeout; + private reqcount: number; + private client: Socket; + private rl: Interface; + private clientConnectionPromise: Promise; + + constructor(rpcPath = defaultRpcPath) { + if (!path.isAbsolute(rpcPath)) { + throw new Error('The rpcPath must be an absolute path'); + } + + if (!fExists(rpcPath) || !fStat(rpcPath).isSocket()) { + // network directory provided, use the lightning-rpc within in + if (fExists(rpcPath, 'lightning-rpc')) { + rpcPath = path.join(rpcPath, 'lightning-rpc'); + } + + // main data directory provided, default to using the bitcoin mainnet subdirectory + // to be removed in v0.2.0 + else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) { + logger.warn(`[CLightningClient] ${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`) + logger.warn(`[CLightningClient] specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`) + rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc') + } + } + + logger.debug(`[CLightningClient] Connecting to ${rpcPath}`); + + super(); + this.rpcPath = rpcPath; + this.reconnectWait = 0.5; + this.reconnectTimeout = null; + this.reqcount = 0; + + const _self = this; + + this.client = createConnection(rpcPath); + this.rl = createInterface({ input: this.client }) + + this.clientConnectionPromise = new Promise(resolve => { + _self.client.on('connect', () => { + logger.info(`[CLightningClient] Lightning client connected`); + _self.reconnectWait = 1; + resolve(); + }); + + _self.client.on('end', () => { + logger.err('[CLightningClient] Lightning client connection closed, reconnecting'); + _self.increaseWaitTime(); + _self.reconnect(); + }); + + _self.client.on('error', error => { + logger.err(`[CLightningClient] Lightning client connection error: ${error}`); + _self.emit('error', error); + _self.increaseWaitTime(); + _self.reconnect(); + }); + }); + + this.rl.on('line', line => { + line = line.trim(); + if (!line) { + return; + } + const data = JSON.parse(line); + // logger.debug(`[CLightningClient] #${data.id} <-- ${JSON.stringify(data.error || data.result)}`); + _self.emit('res:' + data.id, data); + }); + } + + increaseWaitTime(): void { + if (this.reconnectWait >= 16) { + this.reconnectWait = 16; + } else { + this.reconnectWait *= 2; + } + } + + reconnect(): void { + const _self = this; + + if (this.reconnectTimeout) { + return; + } + + this.reconnectTimeout = setTimeout(() => { + logger.debug('[CLightningClient] Trying to reconnect...'); + + _self.client.connect(_self.rpcPath); + _self.reconnectTimeout = null; + }, this.reconnectWait * 1000); + } + + call(method, args = []): Promise { + const _self = this; + + const callInt = ++this.reqcount; + const sendObj = { + jsonrpc: '2.0', + method, + params: args, + id: '' + callInt + }; + + // logger.debug(`[CLightningClient] #${callInt} --> ${method} ${args}`); + + // Wait for the client to connect + return this.clientConnectionPromise + .then(() => new Promise((resolve, reject) => { + // Wait for a response + this.once('res:' + callInt, res => res.error == null + ? resolve(res.result) + : reject(new LightningError(res.error)) + ); + + // Send the command + _self.client.write(JSON.stringify(sendObj)); + })); + } + + async $getNetworkGraph(): Promise { + const listnodes: any[] = await this.call('listnodes'); + const listchannels: any[] = await this.call('listchannels'); + const channelsList = convertAndmergeBidirectionalChannels(listchannels['channels']); + + return { + nodes: listnodes['nodes'].map(node => convertNode(node)), + channels: channelsList, + }; + } +} + +const protify = s => s.replace(/-([a-z])/g, m => m[1].toUpperCase()); + +methods.forEach(k => { + CLightningClient.prototype[protify(k)] = function (...args: any) { + return this.call(k, args); + }; +}); diff --git a/backend/src/api/lightning/clightning/clightning-convert.ts b/backend/src/api/lightning/clightning/clightning-convert.ts index 34ef6f942..8ceec3b7e 100644 --- a/backend/src/api/lightning/clightning/clightning-convert.ts +++ b/backend/src/api/lightning/clightning/clightning-convert.ts @@ -1,6 +1,8 @@ -import logger from "../../../logger"; -import { ILightningApi } from "../lightning-api.interface"; +import { ILightningApi } from '../lightning-api.interface'; +/** + * Convert a clightning "listnode" entry to a lnd node entry + */ export function convertNode(clNode: any): ILightningApi.Node { return { alias: clNode.alias ?? '', @@ -12,7 +14,10 @@ export function convertNode(clNode: any): ILightningApi.Node { }; } -export function convertAndmergeBidirectionalChannels(clChannels: any[]): ILightningApi.Channel[] { +/** + * Convert clightning "listchannels" response to lnd "describegraph.channels" format + */ + export function convertAndmergeBidirectionalChannels(clChannels: any[]): ILightningApi.Channel[] { const consolidatedChannelList: ILightningApi.Channel[] = []; const clChannelsDict = {}; const clChannelsDictCount = {}; @@ -23,27 +28,24 @@ export function convertAndmergeBidirectionalChannels(clChannels: any[]): ILightn clChannelsDictCount[clChannel.short_channel_id] = 1; } else { consolidatedChannelList.push( - buildBidirectionalChannel(clChannel, clChannelsDict[clChannel.short_channel_id]) + buildFullChannel(clChannel, clChannelsDict[clChannel.short_channel_id]) ); delete clChannelsDict[clChannel.short_channel_id]; clChannelsDictCount[clChannel.short_channel_id]++; } } - const bidirectionalChannelsCount = consolidatedChannelList.length; - for (const short_channel_id of Object.keys(clChannelsDict)) { - consolidatedChannelList.push(buildUnidirectionalChannel(clChannelsDict[short_channel_id])); + consolidatedChannelList.push(buildIncompleteChannel(clChannelsDict[short_channel_id])); } - const unidirectionalChannelsCount = consolidatedChannelList.length - bidirectionalChannelsCount; - - logger.debug(`clightning knows ${clChannels.length} channels. ` + - `We found ${bidirectionalChannelsCount} bidirectional channels ` + - `and ${unidirectionalChannelsCount} unidirectional channels.`); return consolidatedChannelList; } -function buildBidirectionalChannel(clChannelA: any, clChannelB: any): ILightningApi.Channel { +/** + * Convert two clightning "getchannels" entries into a full a lnd "describegraph.channels" format + * In this case, clightning knows the channel policy for both nodes + */ +function buildFullChannel(clChannelA: any, clChannelB: any): ILightningApi.Channel { const lastUpdate = Math.max(clChannelA.last_update ?? 0, clChannelB.last_update ?? 0); return { @@ -59,7 +61,11 @@ function buildBidirectionalChannel(clChannelA: any, clChannelB: any): ILightning }; } -function buildUnidirectionalChannel(clChannel: any): ILightningApi.Channel { +/** + * Convert one clightning "getchannels" entry into a full a lnd "describegraph.channels" format + * In this case, clightning knows the channel policy of only one node + */ + function buildIncompleteChannel(clChannel: any): ILightningApi.Channel { return { id: clChannel.short_channel_id, capacity: clChannel.satoshis, @@ -70,7 +76,10 @@ function buildUnidirectionalChannel(clChannel: any): ILightningApi.Channel { }; } -function convertPolicy(clChannel: any): ILightningApi.Policy { +/** + * Convert a clightning "listnode" response to a lnd channel policy format + */ + function convertPolicy(clChannel: any): ILightningApi.Policy { return { public_key: clChannel.source, base_fee_mtokens: clChannel.base_fee_millisatoshi, @@ -82,7 +91,10 @@ function convertPolicy(clChannel: any): ILightningApi.Policy { }; } -function getEmptyPolicy(): ILightningApi.Policy { +/** + * Create an empty channel policy in lnd format + */ + function getEmptyPolicy(): ILightningApi.Policy { return { public_key: 'null', base_fee_mtokens: '0', diff --git a/backend/src/api/lightning/clightning/jsonrpc.ts b/backend/src/api/lightning/clightning/jsonrpc.ts deleted file mode 100644 index d0b187a54..000000000 --- a/backend/src/api/lightning/clightning/jsonrpc.ts +++ /dev/null @@ -1,249 +0,0 @@ -// Imported from https://github.com/shesek/lightning-client-js - -'use strict'; - -const methods = [ - 'addgossip', - 'autocleaninvoice', - 'check', - 'checkmessage', - 'close', - 'connect', - 'createinvoice', - 'createinvoicerequest', - 'createoffer', - 'createonion', - 'decode', - 'decodepay', - 'delexpiredinvoice', - 'delinvoice', - 'delpay', - 'dev-listaddrs', - 'dev-rescan-outputs', - 'disableoffer', - 'disconnect', - 'estimatefees', - 'feerates', - 'fetchinvoice', - 'fundchannel', - 'fundchannel_cancel', - 'fundchannel_complete', - 'fundchannel_start', - 'fundpsbt', - 'getchaininfo', - 'getinfo', - 'getlog', - 'getrawblockbyheight', - 'getroute', - 'getsharedsecret', - 'getutxout', - 'help', - 'invoice', - 'keysend', - 'legacypay', - 'listchannels', - 'listconfigs', - 'listforwards', - 'listfunds', - 'listinvoices', - 'listnodes', - 'listoffers', - 'listpays', - 'listpeers', - 'listsendpays', - 'listtransactions', - 'multifundchannel', - 'multiwithdraw', - 'newaddr', - 'notifications', - 'offer', - 'offerout', - 'openchannel_abort', - 'openchannel_bump', - 'openchannel_init', - 'openchannel_signed', - 'openchannel_update', - 'pay', - 'payersign', - 'paystatus', - 'ping', - 'plugin', - 'reserveinputs', - 'sendinvoice', - 'sendonion', - 'sendonionmessage', - 'sendpay', - 'sendpsbt', - 'sendrawtransaction', - 'setchannelfee', - 'signmessage', - 'signpsbt', - 'stop', - 'txdiscard', - 'txprepare', - 'txsend', - 'unreserveinputs', - 'utxopsbt', - 'waitanyinvoice', - 'waitblockheight', - 'waitinvoice', - 'waitsendpay', - 'withdraw' -]; - - -import EventEmitter from 'events'; -import { existsSync, statSync } from 'fs'; -import { createConnection, Socket } from 'net'; -import { homedir } from 'os'; -import path from 'path'; -import { createInterface, Interface } from 'readline'; -import logger from '../../../logger'; - -class LightningError extends Error { - type: string = 'lightning'; - message: string = 'lightning-client error'; - - constructor(error) { - super(); - this.type = error.type; - this.message = error.message; - } -} - -const defaultRpcPath = path.join(homedir(), '.lightning') - , fStat = (...p) => statSync(path.join(...p)) - , fExists = (...p) => existsSync(path.join(...p)) - -export default class CLightningClient extends EventEmitter { - private rpcPath: string; - private reconnectWait: number; - private reconnectTimeout; - private reqcount: number; - private client: Socket; - private rl: Interface; - private clientConnectionPromise: Promise; - - constructor(rpcPath = defaultRpcPath) { - if (!path.isAbsolute(rpcPath)) { - throw new Error('The rpcPath must be an absolute path'); - } - - if (!fExists(rpcPath) || !fStat(rpcPath).isSocket()) { - // network directory provided, use the lightning-rpc within in - if (fExists(rpcPath, 'lightning-rpc')) { - rpcPath = path.join(rpcPath, 'lightning-rpc'); - } - - // main data directory provided, default to using the bitcoin mainnet subdirectory - // to be removed in v0.2.0 - else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) { - logger.warn(`[CLightningClient] ${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`) - logger.warn(`[CLightningClient] specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`) - rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc') - } - } - - logger.debug(`[CLightningClient] Connecting to ${rpcPath}`); - - super(); - this.rpcPath = rpcPath; - this.reconnectWait = 0.5; - this.reconnectTimeout = null; - this.reqcount = 0; - - const _self = this; - - this.client = createConnection(rpcPath); - this.rl = createInterface({ input: this.client }) - - this.clientConnectionPromise = new Promise(resolve => { - _self.client.on('connect', () => { - logger.debug(`[CLightningClient] Lightning client connected`); - _self.reconnectWait = 1; - resolve(); - }); - - _self.client.on('end', () => { - logger.err('[CLightningClient] Lightning client connection closed, reconnecting'); - _self.increaseWaitTime(); - _self.reconnect(); - }); - - _self.client.on('error', error => { - logger.err(`[CLightningClient] Lightning client connection error: ${error}`); - _self.emit('error', error); - _self.increaseWaitTime(); - _self.reconnect(); - }); - }); - - this.rl.on('line', line => { - line = line.trim(); - if (!line) { - return; - } - const data = JSON.parse(line); - // logger.debug(`[CLightningClient] #${data.id} <-- ${JSON.stringify(data.error || data.result)}`); - _self.emit('res:' + data.id, data); - }); - } - - increaseWaitTime(): void { - if (this.reconnectWait >= 16) { - this.reconnectWait = 16; - } else { - this.reconnectWait *= 2; - } - } - - reconnect(): void { - const _self = this; - - if (this.reconnectTimeout) { - return; - } - - this.reconnectTimeout = setTimeout(() => { - logger.debug('[CLightningClient] Trying to reconnect...'); - - _self.client.connect(_self.rpcPath); - _self.reconnectTimeout = null; - }, this.reconnectWait * 1000); - } - - call(method, args = []): Promise { - const _self = this; - - const callInt = ++this.reqcount; - const sendObj = { - jsonrpc: '2.0', - method, - params: args, - id: '' + callInt - }; - - logger.debug(`[CLightningClient] #${callInt} --> ${method} ${args}`); - - // Wait for the client to connect - return this.clientConnectionPromise - .then(() => new Promise((resolve, reject) => { - // Wait for a response - this.once('res:' + callInt, res => res.error == null - ? resolve(res.result) - : reject(new LightningError(res.error)) - ); - - // Send the command - _self.client.write(JSON.stringify(sendObj)); - })); - } -} - -const protify = s => s.replace(/-([a-z])/g, m => m[1].toUpperCase()); - -methods.forEach(k => { - CLightningClient.prototype[protify(k)] = function (...args: any) { - return this.call(k, args); - }; -}); diff --git a/backend/src/api/lightning/lightning-api-abstract-factory.ts b/backend/src/api/lightning/lightning-api-abstract-factory.ts index 026568c6d..e6691b0a4 100644 --- a/backend/src/api/lightning/lightning-api-abstract-factory.ts +++ b/backend/src/api/lightning/lightning-api-abstract-factory.ts @@ -1,7 +1,5 @@ import { ILightningApi } from './lightning-api.interface'; export interface AbstractLightningApi { - $getNetworkInfo(): Promise; $getNetworkGraph(): Promise; - $getInfo(): Promise; } diff --git a/backend/src/api/lightning/lightning-api-factory.ts b/backend/src/api/lightning/lightning-api-factory.ts index ab551095c..fdadd8230 100644 --- a/backend/src/api/lightning/lightning-api-factory.ts +++ b/backend/src/api/lightning/lightning-api-factory.ts @@ -1,9 +1,12 @@ import config from '../../config'; +import CLightningClient from './clightning/clightning-client'; import { AbstractLightningApi } from './lightning-api-abstract-factory'; import LndApi from './lnd/lnd-api'; function lightningApiFactory(): AbstractLightningApi { - switch (config.LIGHTNING.BACKEND) { + switch (config.LIGHTNING.ENABLED === true && config.LIGHTNING.BACKEND) { + case 'cln': + return new CLightningClient(config.CLIGHTNING.SOCKET); case 'lnd': default: return new LndApi(); diff --git a/backend/src/tasks/lightning/node-sync.service.ts b/backend/src/tasks/lightning/node-sync.service.ts index 10cd2d744..d3367d51c 100644 --- a/backend/src/tasks/lightning/node-sync.service.ts +++ b/backend/src/tasks/lightning/node-sync.service.ts @@ -5,9 +5,9 @@ import bitcoinClient from '../../api/bitcoin/bitcoin-client'; import bitcoinApi from '../../api/bitcoin/bitcoin-api-factory'; import config from '../../config'; import { IEsploraApi } from '../../api/bitcoin/esplora-api.interface'; -import lightningApi from '../../api/lightning/lightning-api-factory'; import { ILightningApi } from '../../api/lightning/lightning-api.interface'; import { $lookupNodeLocation } from './sync-tasks/node-locations'; +import lightningApi from '../../api/lightning/lightning-api-factory'; class NodeSyncService { constructor() {}