From d361df0cffd4c513f9d82759126ae36d8ef2bdf4 Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Thu, 19 Oct 2023 17:38:08 -0700 Subject: [PATCH] Backend controllers --- backend/controllers/cln/balance.js | 29 +++--- backend/controllers/cln/channels.js | 142 +++++++++++++--------------- backend/controllers/cln/fees.js | 11 +-- backend/controllers/cln/getInfo.js | 3 +- backend/controllers/cln/invoices.js | 2 +- backend/controllers/cln/offers.js | 2 +- backend/controllers/cln/peers.js | 2 +- backend/controllers/cln/utility.js | 2 +- backend/routes/cln/channels.js | 4 +- backend/utils/common.js | 31 ++++-- server/controllers/cln/balance.ts | 22 +++-- server/controllers/cln/channels.ts | 132 +++++++++++++------------- server/controllers/cln/fees.ts | 9 +- server/controllers/cln/getInfo.ts | 3 +- server/controllers/cln/invoices.ts | 2 +- server/controllers/cln/offers.ts | 2 +- server/controllers/cln/peers.ts | 2 +- server/controllers/cln/utility.ts | 2 +- server/routes/cln/channels.ts | 4 +- server/utils/common.ts | 31 ++++-- 20 files changed, 240 insertions(+), 197 deletions(-) diff --git a/backend/controllers/cln/balance.js b/backend/controllers/cln/balance.js index fe3cc81f..1f60bdbb 100644 --- a/backend/controllers/cln/balance.js +++ b/backend/controllers/cln/balance.js @@ -10,19 +10,24 @@ export const getBalance = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/getBalance'; + options.url = req.session.selectedNode.ln_server_url + '/v1/listfunds'; request.post(options).then((body) => { - if (!body.totalBalance) { - body.totalBalance = 0; - } - if (!body.confBalance) { - body.confBalance = 0; - } - if (!body.unconfBalance) { - body.unconfBalance = 0; - } - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Balance Received', data: body }); - res.status(200).json(body); + let confBalance = 0; + let unconfBalance = 0; + let totalBalance = 0; + const versionCompatible = common.isVersionCompatible('23.02'); + body.outputs.forEach((output) => { + if (output.status === 'confirmed') { + confBalance = confBalance + (versionCompatible ? (output.amount_msat / 1000) : output.value); + } + else if (output.status === 'unconfirmed') { + unconfBalance = unconfBalance + (versionCompatible ? (output.amount_msat / 1000) : output.value); + } + }); + totalBalance = confBalance + unconfBalance; + const walBalance = { totalBalance: totalBalance || 0, confBalance: confBalance || 0, unconfBalance: unconfBalance || 0 }; + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Balance Received', data: walBalance }); + res.status(200).json(walBalance); }).catch((errRes) => { const err = common.handleError(errRes, 'Balance', 'Get Balance Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); diff --git a/backend/controllers/cln/channels.js b/backend/controllers/cln/channels.js index d89cab74..899b9e67 100644 --- a/backend/controllers/cln/channels.js +++ b/backend/controllers/cln/channels.js @@ -10,59 +10,55 @@ export const listPeerChannels = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels'; + options.url = req.session.selectedNode.ln_server_url + '/v1/listpeerchannels'; request.post(options).then((body) => { - body?.map((channel) => { - if (!channel.alias || channel.alias === '') { - channel.alias = channel.peer_id.substring(0, 20); - } + body.channels.map((channel) => { const local = channel.to_us_msat || 0; const remote = (channel.total_msat - local) || 0; const total = channel.total_msat || 0; - channel.to_them_msat = remote; - channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3); - return channel; + // return getAliasForChannel(channel).then(channelAlias => { + return { + peer_id: channel.peer_id, + peer_connected: channel.peer_connected, + opener: channel.opener, + owner: channel.owner, + short_channel_id: channel.short_channel_id, + channel_id: channel.channel_id, + funding_txid: channel.funding_txid, + private: channel.private, + to_us_msat: channel.to_us_msat, + total_msat: channel.total_msat, + their_reserve_msat: channel.their_reserve_msat, + our_reserve_msat: channel.our_reserve_msat, + spendable_msat: channel.spendable_msat, + receivable_msat: channel.receivable_msat, + funding: channel.funding, + state: channel.state, + fee_base_msat: channel.fee_base_msat, + fee_proportional_millionths: channel.fee_proportional_millionths, + dust_limit_msat: channel.dust_limit_msat, + htlcs: channel.htlcs, + features: channel.features, + alias: channel.peer_id.substring(0, 20), + to_them_msat: remote, + balancedness: (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3) + }; }); - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body }); - res.status(200).json(body); + }).then((listPeerChannels) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: listPeerChannels }); + res.status(200).json(listPeerChannels); }).catch((errRes) => { const err = common.handleError(errRes, 'Channels', 'List Peer Channels Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); }); }; -export const listChannels = (req, res, next) => { - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' }); - options = common.getOptions(req); - if (options.error) { - return res.status(options.statusCode).json({ message: options.message, error: options.error }); - } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels'; - request.post(options).then((body) => { - body?.map((channel) => { - if (!channel.alias || channel.alias === '') { - channel.alias = channel.channel_id.substring(0, 20); - } - const local = channel.to_us_msat || 0; - const remote = (channel.total_msat - local) || 0; - const total = channel.total_msat || 0; - channel.to_them_msat = remote; - channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3); - return channel; - }); - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channels List Received', data: body }); - res.status(200).json(body); - }).catch((errRes) => { - const err = common.handleError(errRes, 'Channels', 'List Channels Error', req.session.selectedNode); - return res.status(err.statusCode).json({ message: err.message, error: err.error }); - }); -}; export const openChannel = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/openChannel'; + options.url = req.session.selectedNode.ln_server_url + '/v1/fundchannel'; options.body = req.body; logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body }); request.post(options).then((body) => { @@ -79,7 +75,7 @@ export const setChannelFee = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/setChannelFee'; + options.url = req.session.selectedNode.ln_server_url + '/v1/setchannel'; options.body = req.body; logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body }); request.post(options).then((body) => { @@ -97,10 +93,10 @@ export const closeChannel = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - const unilateralTimeoutQuery = req.query.force ? '?unilateralTimeout=1' : ''; - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/closeChannel/' + req.params.channelId + unilateralTimeoutQuery; + options.url = req.session.selectedNode.ln_server_url + '/v1/close'; + options.body = { channelId: req.params.channelId, unilaterlaltimeout: req.query.force ? 1 : null }; logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel', data: options.url }); - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body }); res.status(204).json(body); }).catch((errRes) => { @@ -114,16 +110,33 @@ export const getLocalRemoteBalance = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/localremotebal'; + options.url = req.session.selectedNode.ln_server_url + '/v1/listfunds'; request.post(options).then((body) => { - if (!body.localBalance) { - body.localBalance = 0; - } - if (!body.remoteBalance) { - body.remoteBalance = 0; + const versionCompatible = common.isVersionCompatible('23.02'); + let localBalance = 0; + let remoteBalance = 0; + let pendingBalance = 0; + let inactiveBalance = 0; + body.channels.forEach((channel) => { + if ((channel.state === 'CHANNELD_NORMAL') && channel.connected === true) { + localBalance = localBalance + (versionCompatible ? (channel.our_amount_msat) : channel.channel_sat); + remoteBalance = remoteBalance + (versionCompatible ? (channel.amount_msat - channel.our_amount_msat) : (channel.channel_total_sat - channel.channel_sat)); + } + else if ((channel.state === 'CHANNELD_NORMAL') && channel.connected === false) { + inactiveBalance = inactiveBalance + (versionCompatible ? (channel.our_amount_msat) : channel.channel_sat); + } + else if (channel.state === 'CHANNELD_AWAITING_LOCKIN' || channel.state === 'DUALOPEND_AWAITING_LOCKIN') { + pendingBalance = pendingBalance + (versionCompatible ? (channel.our_amount_msat) : channel.channel_sat); + } + }); + if (versionCompatible) { + localBalance = localBalance / 1000; + remoteBalance = remoteBalance / 1000; + inactiveBalance = inactiveBalance / 1000; + pendingBalance = pendingBalance / 1000; } logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Local Remote Balance Received', data: body }); - res.status(200).json(body); + res.status(200).json({ localBalance: localBalance || 0, remoteBalance: remoteBalance || 0, inactiveBalance: inactiveBalance || 0, pendingBalance: pendingBalance || 0 }); }).catch((errRes) => { const err = common.handleError(errRes, 'Channels', 'Local Remote Balance Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); @@ -135,10 +148,11 @@ export const listForwards = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listForwards?status=' + (req.query.status ? req.query.status : 'settled'); + options.url = req.session.selectedNode.ln_server_url + '/v1/listforwards'; + options.body = { status: req.query.status || 'settled' }; request.get(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.query.status, data: body }); - res.status(200).json(body); + res.status(200).json(!body.forwards ? [] : (req.query.status === 'failed' || req.query.status === 'local_failed') ? body.forwards.slice(Math.max(0, body.forwards.length - 1000), Math.max(1000, body.forwards.length)).reverse() : body.forwards.reverse()); }).catch((errRes) => { const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); @@ -150,10 +164,10 @@ export const funderUpdatePolicy = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/funderUpdate'; - if (req.body && req.body.policy) { - options.body = req.body; - } + options.url = req.session.selectedNode.ln_server_url + '/v1/funderupdate'; + // if (req.body && req.body.policy) { + // options.body = req.body; + // } logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body }); request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body }); @@ -165,23 +179,3 @@ export const funderUpdatePolicy = (req, res, next) => { return res.status(err.statusCode).json({ message: err.message, error: err.error }); }); }; -export const listForwardsPaginated = (req, res, next) => { - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Paginated List Forwards..' }); - options = common.getOptions(req); - if (options.error) { - return res.status(options.statusCode).json({ message: options.message, error: options.error }); - } - const { status, maxLen, offset } = req.query; - let queryStr = '?status=' + (status ? status : 'settled'); - queryStr = queryStr + '&maxLen=' + (maxLen ? maxLen : '10'); - queryStr = queryStr + '&offset=' + (offset ? offset : '0'); - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listForwardsPaginated' + queryStr; - logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Paginated Forwarding History url' + options.url }); - request.get(options).then((body) => { - logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Paginated Forwarding History Received For Status ' + req.query.status, data: body }); - res.status(200).json(body); - }).catch((errRes) => { - const err = common.handleError(errRes, 'Channels', 'Paginated Forwarding History Error', req.session.selectedNode); - return res.status(err.statusCode).json({ message: err.message, error: err.error }); - }); -}; diff --git a/backend/controllers/cln/fees.js b/backend/controllers/cln/fees.js index 290df6f9..175a64a6 100644 --- a/backend/controllers/cln/fees.js +++ b/backend/controllers/cln/fees.js @@ -10,13 +10,12 @@ export const getFees = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/getFees'; + options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo'; request.post(options).then((body) => { - if (!body.feeCollected) { - body.feeCollected = 0; - } - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body }); - res.status(200).json(body); + const versionCompatible = common.isVersionCompatible('23.02'); + const feeData = { feeCollected: ((versionCompatible ? body.fees_collected_msat : body.msatoshi_fees_collected) || 0) }; + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: feeData }); + res.status(200).json(feeData); }).catch((errRes) => { const err = common.handleError(errRes, 'Fees', 'Get Fees Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); diff --git a/backend/controllers/cln/getInfo.js b/backend/controllers/cln/getInfo.js index bfb66ab2..3033007c 100644 --- a/backend/controllers/cln/getInfo.js +++ b/backend/controllers/cln/getInfo.js @@ -48,8 +48,9 @@ export const getInfo = (req, res, next) => { body.uris.push(body.id + '@' + addr.address + ':' + addr.port); }); } - req.session.selectedNode.api_version = body.api_version || ''; + common.setVersion(body.version || ''); req.session.selectedNode.ln_version = body.version || ''; + req.session.selectedNode.api_version = body.api_version || ''; logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' }); clWsClient.updateSelectedNode(req.session.selectedNode); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body }); diff --git a/backend/controllers/cln/invoices.js b/backend/controllers/cln/invoices.js index c33f7f2a..eb0feb97 100644 --- a/backend/controllers/cln/invoices.js +++ b/backend/controllers/cln/invoices.js @@ -12,7 +12,7 @@ export const deleteExpiredInvoice = (req, res, next) => { } const queryStr = req.query.maxExpiry ? '?maxexpiry=' + req.query.maxExpiry : ''; options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/delExpiredInvoice' + queryStr; - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body }); res.status(204).json({ status: 'Invoice Deleted Successfully' }); }).catch((errRes) => { diff --git a/backend/controllers/cln/offers.js b/backend/controllers/cln/offers.js index 86353123..202a494f 100644 --- a/backend/controllers/cln/offers.js +++ b/backend/controllers/cln/offers.js @@ -89,7 +89,7 @@ export const disableOffer = (req, res, next) => { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } options.url = req.session.selectedNode.ln_server_url + '/v1/offers/disableOffer/' + req.params.offerID; - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body }); res.status(202).json(body); }).catch((errRes) => { diff --git a/backend/controllers/cln/peers.js b/backend/controllers/cln/peers.js index 253ae491..1cdcb265 100644 --- a/backend/controllers/cln/peers.js +++ b/backend/controllers/cln/peers.js @@ -55,7 +55,7 @@ export const deletePeer = (req, res, next) => { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } options.url = req.session.selectedNode.ln_server_url + '/v1/peer/disconnect/' + req.params.peerId + '?force=' + req.query.force; - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body }); res.status(204).json({}); }).catch((errRes) => { diff --git a/backend/controllers/cln/utility.js b/backend/controllers/cln/utility.js index ec52a0a2..8f12f893 100644 --- a/backend/controllers/cln/utility.js +++ b/backend/controllers/cln/utility.js @@ -72,7 +72,7 @@ export const verifyMessage = (req, res, next) => { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } options.url = req.session.selectedNode.ln_server_url + '/v1/utility/checkMessage/' + req.body.message + '/' + req.body.signature; - request.get(options, (error, response, body) => { + request.post(options, (error, response, body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Verified', data: body }); res.status(201).json(body); }).catch((errRes) => { diff --git a/backend/routes/cln/channels.js b/backend/routes/cln/channels.js index 0365a3c5..9ab1c671 100644 --- a/backend/routes/cln/channels.js +++ b/backend/routes/cln/channels.js @@ -1,15 +1,13 @@ import exprs from 'express'; const { Router } = exprs; import { isAuthenticated } from '../../utils/authCheck.js'; -import { listChannels, listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js'; +import { listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy } from '../../controllers/cln/channels.js'; const router = Router(); -router.get('/listChannels', isAuthenticated, listChannels); router.get('/listPeerChannels', isAuthenticated, listPeerChannels); router.post('/', isAuthenticated, openChannel); router.post('/setChannelFee', isAuthenticated, setChannelFee); router.delete('/:channelId', isAuthenticated, closeChannel); router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance); router.get('/listForwards', isAuthenticated, listForwards); -router.get('/listForwardsPaginated', isAuthenticated, listForwardsPaginated); router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy); export default router; diff --git a/backend/utils/common.js b/backend/utils/common.js index 062f7b97..5827f194 100644 --- a/backend/utils/common.js +++ b/backend/utils/common.js @@ -20,6 +20,7 @@ export class CommonService { this.rtl_cookie_path = ''; this.logout_redirect_link = ''; this.cookie_value = ''; + this.ln_version = ''; this.api_version = ''; this.secret_key = crypto.randomBytes(64).toString('hex'); this.read_dummy_data = false; @@ -423,13 +424,29 @@ export class CommonService { fs.writeFile(channel_backup_file, '', () => { }); }); }; - this.isVersionCompatible = (currentVersion, checkVersion) => { - if (currentVersion) { - const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || []; - const checkVersionsArr = checkVersion.split('.'); - return (+versionsArr[0] > +checkVersionsArr[0]) || - (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) || - (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]); + this.setVersion = (version) => { + this.ln_version = version; + }; + this.isVersionCompatible = (checkVersion) => { + if (this.ln_version && this.ln_version !== '') { + // eslint-disable-next-line prefer-named-capture-group + const pattern = /v?(\d+(\.\d+)*)/; + const match = this.ln_version.match(pattern); + if (match && match.length && match.length > 1) { + this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Global Version ' + match[1] }); + this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Checking Compatiblility with Version ' + checkVersion }); + const currentVersionArr = match[1].split('.') || []; + currentVersionArr[1] = currentVersionArr[1].substring(0, 2); + const checkVersionsArr = checkVersion.split('.'); + checkVersionsArr[1] = checkVersionsArr[1].substring(0, 2); + return (+currentVersionArr[0] > +checkVersionsArr[0]) || + (+currentVersionArr[0] === +checkVersionsArr[0] && +currentVersionArr[1] > +checkVersionsArr[1]) || + (+currentVersionArr[0] === +checkVersionsArr[0] && +currentVersionArr[1] === +checkVersionsArr[1] && +currentVersionArr[2] >= +checkVersionsArr[2]); + } + else { + this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Invalid Version String ' + this.ln_version }); + return false; + } } return false; }; diff --git a/server/controllers/cln/balance.ts b/server/controllers/cln/balance.ts index 383e5a4d..b0c13f41 100644 --- a/server/controllers/cln/balance.ts +++ b/server/controllers/cln/balance.ts @@ -9,13 +9,23 @@ export const getBalance = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Getting Balance..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/getBalance'; + options.url = req.session.selectedNode.ln_server_url + '/v1/listfunds'; request.post(options).then((body) => { - if (!body.totalBalance) { body.totalBalance = 0; } - if (!body.confBalance) { body.confBalance = 0; } - if (!body.unconfBalance) { body.unconfBalance = 0; } - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Balance Received', data: body }); - res.status(200).json(body); + let confBalance = 0; + let unconfBalance = 0; + let totalBalance = 0; + const versionCompatible = common.isVersionCompatible('23.02'); + body.outputs.forEach((output) => { + if (output.status === 'confirmed') { + confBalance = confBalance + (versionCompatible ? (output.amount_msat / 1000) : output.value); + } else if (output.status === 'unconfirmed') { + unconfBalance = unconfBalance + (versionCompatible ? (output.amount_msat / 1000) : output.value); + } + }); + totalBalance = confBalance + unconfBalance; + const walBalance = { totalBalance: totalBalance || 0, confBalance: confBalance || 0, unconfBalance: unconfBalance || 0 }; + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Balance Received', data: walBalance }); + res.status(200).json(walBalance); }).catch((errRes) => { const err = common.handleError(errRes, 'Balance', 'Get Balance Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); diff --git a/server/controllers/cln/channels.ts b/server/controllers/cln/channels.ts index 7701b418..2a364251 100644 --- a/server/controllers/cln/channels.ts +++ b/server/controllers/cln/channels.ts @@ -9,53 +9,54 @@ export const listPeerChannels = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels'; + options.url = req.session.selectedNode.ln_server_url + '/v1/listpeerchannels'; request.post(options).then((body) => { - body?.map((channel) => { - if (!channel.alias || channel.alias === '') { channel.alias = channel.peer_id.substring(0, 20); } + body.channels.map((channel) => { const local = channel.to_us_msat || 0; const remote = (channel.total_msat - local) || 0; const total = channel.total_msat || 0; - channel.to_them_msat = remote; - channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3); - return channel; + // return getAliasForChannel(channel).then(channelAlias => { + return { + peer_id: channel.peer_id, + peer_connected: channel.peer_connected, + opener: channel.opener, + owner: channel.owner, + short_channel_id: channel.short_channel_id, + channel_id: channel.channel_id, + funding_txid: channel.funding_txid, + private: channel.private, + to_us_msat: channel.to_us_msat, + total_msat: channel.total_msat, + their_reserve_msat: channel.their_reserve_msat, + our_reserve_msat: channel.our_reserve_msat, + spendable_msat: channel.spendable_msat, + receivable_msat: channel.receivable_msat, + funding: channel.funding, + state: channel.state, + fee_base_msat: channel.fee_base_msat, + fee_proportional_millionths: channel.fee_proportional_millionths, + dust_limit_msat: channel.dust_limit_msat, + htlcs: channel.htlcs, + features: channel.features, + alias: channel.peer_id.substring(0, 20), + to_them_msat: remote, + balancedness: (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3) + }; }); - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body }); - res.status(200).json(body); + }).then((listPeerChannels) => { + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: listPeerChannels }); + res.status(200).json(listPeerChannels); }).catch((errRes) => { const err = common.handleError(errRes, 'Channels', 'List Peer Channels Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); }); }; -export const listChannels = (req, res, next) => { - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' }); - options = common.getOptions(req); - if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels'; - request.post(options).then((body) => { - body?.map((channel) => { - if (!channel.alias || channel.alias === '') { channel.alias = channel.channel_id.substring(0, 20); } - const local = channel.to_us_msat || 0; - const remote = (channel.total_msat - local) || 0; - const total = channel.total_msat || 0; - channel.to_them_msat = remote; - channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3); - return channel; - }); - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channels List Received', data: body }); - res.status(200).json(body); - }).catch((errRes) => { - const err = common.handleError(errRes, 'Channels', 'List Channels Error', req.session.selectedNode); - return res.status(err.statusCode).json({ message: err.message, error: err.error }); - }); -}; - export const openChannel = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/openChannel'; + options.url = req.session.selectedNode.ln_server_url + '/v1/fundchannel'; options.body = req.body; logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body }); request.post(options).then((body) => { @@ -71,7 +72,7 @@ export const setChannelFee = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Setting Channel Fee..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/setChannelFee'; + options.url = req.session.selectedNode.ln_server_url + '/v1/setchannel'; options.body = req.body; logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body }); request.post(options).then((body) => { @@ -88,10 +89,10 @@ export const closeChannel = (req, res, next) => { req.setTimeout(60000 * 10); // timeout 10 mins options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - const unilateralTimeoutQuery = req.query.force ? '?unilateralTimeout=1' : ''; - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/closeChannel/' + req.params.channelId + unilateralTimeoutQuery; + options.url = req.session.selectedNode.ln_server_url + '/v1/close'; + options.body = { channelId: req.params.channelId, unilaterlaltimeout: req.query.force ? 1 : null }; logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel', data: options.url }); - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body }); res.status(204).json(body); }).catch((errRes) => { @@ -104,12 +105,31 @@ export const getLocalRemoteBalance = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Local & Remote Balances..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/localremotebal'; + options.url = req.session.selectedNode.ln_server_url + '/v1/listfunds'; request.post(options).then((body) => { - if (!body.localBalance) { body.localBalance = 0; } - if (!body.remoteBalance) { body.remoteBalance = 0; } + const versionCompatible = common.isVersionCompatible('23.02'); + let localBalance = 0; + let remoteBalance = 0; + let pendingBalance = 0; + let inactiveBalance = 0; + body.channels.forEach((channel) => { + if ((channel.state === 'CHANNELD_NORMAL') && channel.connected === true) { + localBalance = localBalance + (versionCompatible ? (channel.our_amount_msat) : channel.channel_sat); + remoteBalance = remoteBalance + (versionCompatible ? (channel.amount_msat - channel.our_amount_msat) : (channel.channel_total_sat - channel.channel_sat)); + } else if ((channel.state === 'CHANNELD_NORMAL') && channel.connected === false) { + inactiveBalance = inactiveBalance + (versionCompatible ? (channel.our_amount_msat) : channel.channel_sat); + } else if (channel.state === 'CHANNELD_AWAITING_LOCKIN' || channel.state === 'DUALOPEND_AWAITING_LOCKIN') { + pendingBalance = pendingBalance + (versionCompatible ? (channel.our_amount_msat) : channel.channel_sat); + } + }); + if (versionCompatible) { + localBalance = localBalance / 1000; + remoteBalance = remoteBalance / 1000; + inactiveBalance = inactiveBalance / 1000; + pendingBalance = pendingBalance / 1000; + } logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Local Remote Balance Received', data: body }); - res.status(200).json(body); + res.status(200).json({ localBalance: localBalance || 0, remoteBalance: remoteBalance || 0, inactiveBalance: inactiveBalance || 0, pendingBalance: pendingBalance || 0 }); }).catch((errRes) => { const err = common.handleError(errRes, 'Channels', 'Local Remote Balance Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); @@ -120,10 +140,11 @@ export const listForwards = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listForwards?status=' + (req.query.status ? req.query.status : 'settled'); + options.url = req.session.selectedNode.ln_server_url + '/v1/listforwards'; + options.body = { status: req.query.status || 'settled' }; request.get(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.query.status, data: body }); - res.status(200).json(body); + res.status(200).json(!body.forwards ? [] : (req.query.status === 'failed' || req.query.status === 'local_failed') ? body.forwards.slice(Math.max(0, body.forwards.length - 1000), Math.max(1000, body.forwards.length)).reverse() : body.forwards.reverse()); }).catch((errRes) => { const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); @@ -134,10 +155,10 @@ export const funderUpdatePolicy = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting or Updating Funder Policy..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/funderUpdate'; - if (req.body && req.body.policy) { - options.body = req.body; - } + options.url = req.session.selectedNode.ln_server_url + '/v1/funderupdate'; + // if (req.body && req.body.policy) { + // options.body = req.body; + // } logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body }); request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body }); @@ -149,22 +170,3 @@ export const funderUpdatePolicy = (req, res, next) => { return res.status(err.statusCode).json({ message: err.message, error: err.error }); }); }; - -export const listForwardsPaginated = (req, res, next) => { - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Paginated List Forwards..' }); - options = common.getOptions(req); - if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - const { status, maxLen, offset } = req.query; - let queryStr = '?status=' + (status ? status : 'settled'); - queryStr = queryStr + '&maxLen=' + (maxLen ? maxLen : '10'); - queryStr = queryStr + '&offset=' + (offset ? offset : '0'); - options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listForwardsPaginated' + queryStr; - logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Paginated Forwarding History url' + options.url }); - request.get(options).then((body) => { - logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Paginated Forwarding History Received For Status ' + req.query.status, data: body }); - res.status(200).json(body); - }).catch((errRes) => { - const err = common.handleError(errRes, 'Channels', 'Paginated Forwarding History Error', req.session.selectedNode); - return res.status(err.statusCode).json({ message: err.message, error: err.error }); - }); -}; diff --git a/server/controllers/cln/fees.ts b/server/controllers/cln/fees.ts index c7866a38..ed494f08 100644 --- a/server/controllers/cln/fees.ts +++ b/server/controllers/cln/fees.ts @@ -9,11 +9,12 @@ export const getFees = (req, res, next) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Getting Fees..' }); options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } - options.url = req.session.selectedNode.ln_server_url + '/v1/getFees'; + options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo'; request.post(options).then((body) => { - if (!body.feeCollected) { body.feeCollected = 0; } - logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body }); - res.status(200).json(body); + const versionCompatible = common.isVersionCompatible('23.02'); + const feeData = { feeCollected: ((versionCompatible ? body.fees_collected_msat : body.msatoshi_fees_collected) || 0) }; + logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: feeData }); + res.status(200).json(feeData); }).catch((errRes) => { const err = common.handleError(errRes, 'Fees', 'Get Fees Error', req.session.selectedNode); return res.status(err.statusCode).json({ message: err.message, error: err.error }); diff --git a/server/controllers/cln/getInfo.ts b/server/controllers/cln/getInfo.ts index 71c25452..c4f42405 100644 --- a/server/controllers/cln/getInfo.ts +++ b/server/controllers/cln/getInfo.ts @@ -45,8 +45,9 @@ export const getInfo = (req, res, next) => { body.uris.push(body.id + '@' + addr.address + ':' + addr.port); }); } - req.session.selectedNode.api_version = body.api_version || ''; + common.setVersion(body.version || ''); req.session.selectedNode.ln_version = body.version || ''; + req.session.selectedNode.api_version = body.api_version || ''; logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' }); clWsClient.updateSelectedNode(req.session.selectedNode); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body }); diff --git a/server/controllers/cln/invoices.ts b/server/controllers/cln/invoices.ts index 7d7a9b22..0d1be352 100644 --- a/server/controllers/cln/invoices.ts +++ b/server/controllers/cln/invoices.ts @@ -11,7 +11,7 @@ export const deleteExpiredInvoice = (req, res, next) => { if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } const queryStr = req.query.maxExpiry ? '?maxexpiry=' + req.query.maxExpiry : ''; options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/delExpiredInvoice' + queryStr; - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body }); res.status(204).json({ status: 'Invoice Deleted Successfully' }); }).catch((errRes) => { diff --git a/server/controllers/cln/offers.ts b/server/controllers/cln/offers.ts index dc0bcb81..2e5a2a6b 100644 --- a/server/controllers/cln/offers.ts +++ b/server/controllers/cln/offers.ts @@ -88,7 +88,7 @@ export const disableOffer = (req, res, next) => { options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } options.url = req.session.selectedNode.ln_server_url + '/v1/offers/disableOffer/' + req.params.offerID; - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body }); res.status(202).json(body); }).catch((errRes) => { diff --git a/server/controllers/cln/peers.ts b/server/controllers/cln/peers.ts index 032974d3..728a5e8c 100644 --- a/server/controllers/cln/peers.ts +++ b/server/controllers/cln/peers.ts @@ -52,7 +52,7 @@ export const deletePeer = (req, res, next) => { options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } options.url = req.session.selectedNode.ln_server_url + '/v1/peer/disconnect/' + req.params.peerId + '?force=' + req.query.force; - request.delete(options).then((body) => { + request.post(options).then((body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body }); res.status(204).json({}); }).catch((errRes) => { diff --git a/server/controllers/cln/utility.ts b/server/controllers/cln/utility.ts index 0bae7119..5ef0b9af 100644 --- a/server/controllers/cln/utility.ts +++ b/server/controllers/cln/utility.ts @@ -70,7 +70,7 @@ export const verifyMessage = (req, res, next) => { options = common.getOptions(req); if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } options.url = req.session.selectedNode.ln_server_url + '/v1/utility/checkMessage/' + req.body.message + '/' + req.body.signature; - request.get(options, (error, response, body) => { + request.post(options, (error, response, body) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Verified', data: body }); res.status(201).json(body); }).catch((errRes) => { diff --git a/server/routes/cln/channels.ts b/server/routes/cln/channels.ts index 9b336432..dd0a4e35 100644 --- a/server/routes/cln/channels.ts +++ b/server/routes/cln/channels.ts @@ -1,11 +1,10 @@ import exprs from 'express'; const { Router } = exprs; import { isAuthenticated } from '../../utils/authCheck.js'; -import { listChannels, listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js'; +import { listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy } from '../../controllers/cln/channels.js'; const router = Router(); -router.get('/listChannels', isAuthenticated, listChannels); router.get('/listPeerChannels', isAuthenticated, listPeerChannels); router.post('/', isAuthenticated, openChannel); router.post('/setChannelFee', isAuthenticated, setChannelFee); @@ -13,7 +12,6 @@ router.delete('/:channelId', isAuthenticated, closeChannel); router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance); router.get('/listForwards', isAuthenticated, listForwards); -router.get('/listForwardsPaginated', isAuthenticated, listForwardsPaginated); router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy); diff --git a/server/utils/common.ts b/server/utils/common.ts index 182253b6..1d77d365 100644 --- a/server/utils/common.ts +++ b/server/utils/common.ts @@ -22,6 +22,7 @@ export class CommonService { public rtl_cookie_path = ''; public logout_redirect_link = ''; public cookie_value = ''; + public ln_version = ''; public api_version = ''; public secret_key = crypto.randomBytes(64).toString('hex'); public read_dummy_data = false; @@ -432,13 +433,29 @@ export class CommonService { }); }; - public isVersionCompatible = (currentVersion, checkVersion) => { - if (currentVersion) { - const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || []; - const checkVersionsArr = checkVersion.split('.'); - return (+versionsArr[0] > +checkVersionsArr[0]) || - (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) || - (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]); + public setVersion = (version) => { + this.ln_version = version; + }; + + public isVersionCompatible = (checkVersion) => { + if (this.ln_version && this.ln_version !== '') { + // eslint-disable-next-line prefer-named-capture-group + const pattern = /v?(\d+(\.\d+)*)/; + const match = this.ln_version.match(pattern); + if (match && match.length && match.length > 1) { + this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Global Version ' + match[1] }); + this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Checking Compatiblility with Version ' + checkVersion }); + const currentVersionArr = match[1].split('.') || []; + currentVersionArr[1] = currentVersionArr[1].substring(0, 2); + const checkVersionsArr = checkVersion.split('.'); + checkVersionsArr[1] = checkVersionsArr[1].substring(0, 2); + return (+currentVersionArr[0] > +checkVersionsArr[0]) || + (+currentVersionArr[0] === +checkVersionsArr[0] && +currentVersionArr[1] > +checkVersionsArr[1]) || + (+currentVersionArr[0] === +checkVersionsArr[0] && +currentVersionArr[1] === +checkVersionsArr[1] && +currentVersionArr[2] >= +checkVersionsArr[2]); + } else { + this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Invalid Version String ' + this.ln_version }); + return false; + } } return false; };