Fix for Resource temporarily unavailable error for CLN channel alias list

Fixes #1501
This commit is contained in:
ShahanaFarooqui 2025-07-30 00:12:18 -07:00
parent aae46f6443
commit 1cec7b125b
11 changed files with 119 additions and 24 deletions

View file

@ -14,11 +14,12 @@ export const listPeerChannels = (req, res, next) => {
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeerchannels'; options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeerchannels';
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels });
return Promise.all(body.channels?.map((channel) => { const getPeerAliasesTasks = body.channels.map((channel) => () => {
channel.to_them_msat = channel.total_msat - channel.to_us_msat; channel.to_them_msat = channel.total_msat - channel.to_us_msat;
channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - (channel.total_msat - channel.to_us_msat)) / channel.total_msat)).toFixed(3); channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - channel.to_them_msat) / channel.total_msat)).toFixed(3);
return getAlias(req.session.selectedNode, channel, 'peer_id'); return getAlias(req.session.selectedNode, channel, 'peer_id');
})).then((values) => { });
common.runWithConcurrencyLimit(getPeerAliasesTasks, 20, () => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels });
return res.status(200).json(body.channels || []); return res.status(200).json(body.channels || []);
}); });

View file

@ -4,6 +4,7 @@ import { Common } from '../../utils/common.js';
let options = null; let options = null;
const logger = Logger; const logger = Logger;
const common = Common; const common = Common;
const aliasCache = new Map();
export const getRoute = (req, res, next) => { export const getRoute = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' });
options = common.getOptions(req); options = common.getOptions(req);
@ -80,20 +81,28 @@ export const listNodes = (req, res, next) => {
}); });
}; };
export const getAlias = (selNode, peer, id) => { export const getAlias = (selNode, peer, id) => {
options.url = selNode.settings.lnServerUrl + '/v1/listnodes'; const peerId = peer[id];
if (!peer[id]) { if (!peerId) {
logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Network', msg: 'Empty Peer ID' }); logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Network', msg: 'Empty Peer ID' });
peer.alias = ''; peer.alias = '';
return peer; return Promise.resolve(peer);
} }
options.body = { id: peer[id] }; if (aliasCache.has(peerId)) {
peer.alias = aliasCache.get(peerId);
return Promise.resolve(peer);
}
options.url = selNode.settings.lnServerUrl + '/v1/listnodes';
options.body = { id: peerId };
return request.post(options).then((body) => { return request.post(options).then((body) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Network', msg: 'Peer Alias Finished', data: body }); logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Network', msg: 'Peer Alias Finished', data: body });
peer.alias = body.nodes[0] && body.nodes[0].alias ? body.nodes[0].alias : peer[id].substring(0, 20); const alias = body.nodes?.[0]?.alias || peerId.substring(0, 20);
aliasCache.set(peerId, alias);
peer.alias = alias;
return peer; return peer;
}).catch((errRes) => { }).catch((errRes) => {
common.handleError(errRes, 'Network', 'Peer Alias Error', selNode); common.handleError(errRes, 'Network', 'Peer Alias Error', selNode);
peer.alias = peer[id].substring(0, 20); const alias = peerId.substring(0, 20);
peer.alias = alias;
return peer; return peer;
}); });
}; };

View file

@ -608,6 +608,39 @@ export class CommonService {
const dataStr = foundDataLine ? foundDataLine.substring((foundDataLine.indexOf(search_string)) + search_string.length) : '{}'; const dataStr = foundDataLine ? foundDataLine.substring((foundDataLine.indexOf(search_string)) + search_string.length) : '{}';
return JSON.parse(dataStr); return JSON.parse(dataStr);
}; };
this.runWithConcurrencyLimit = (tasks, limit, done) => {
const results = new Array(tasks.length);
let nextIndex = 0;
let activeCount = 0;
const runNext = () => {
if (nextIndex >= tasks.length) {
if (activeCount === 0) {
done(results); // all tasks are finished
}
return;
}
const currentIndex = nextIndex++;
activeCount++;
const task = tasks[currentIndex];
if (typeof task !== 'function') {
results[currentIndex] = { error: new Error('Invalid task at index ' + currentIndex) };
activeCount--;
runNext();
return;
}
Promise.resolve().then(() => task()).then((result) => {
results[currentIndex] = result;
}).catch((err) => {
results[currentIndex] = { error: err };
}).finally(() => {
activeCount--;
runNext();
});
};
for (let i = 0; i < limit && i < tasks.length; i++) {
runNext();
}
};
} }
} }
export const Common = new CommonService(); export const Common = new CommonService();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,11 +14,12 @@ export const listPeerChannels = (req, res, next) => {
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeerchannels'; options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeerchannels';
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels });
return Promise.all(body.channels?.map((channel) => { const getPeerAliasesTasks = body.channels.map((channel) => () => {
channel.to_them_msat = channel.total_msat - channel.to_us_msat; channel.to_them_msat = channel.total_msat - channel.to_us_msat;
channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - (channel.total_msat - channel.to_us_msat)) / channel.total_msat)).toFixed(3); channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - channel.to_them_msat) / channel.total_msat)).toFixed(3);
return getAlias(req.session.selectedNode, channel, 'peer_id'); return getAlias(req.session.selectedNode, channel, 'peer_id');
})).then((values) => { });
common.runWithConcurrencyLimit(getPeerAliasesTasks, 20, () => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels });
return res.status(200).json(body.channels || []); return res.status(200).json(body.channels || []);
}); });

View file

@ -6,6 +6,7 @@ import { SelectedNode } from '../../models/config.model.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
const aliasCache = new Map<string, string>();
export const getRoute = (req, res, next) => { export const getRoute = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' });
@ -79,20 +80,31 @@ export const listNodes = (req, res, next) => {
}; };
export const getAlias = (selNode: SelectedNode, peer: any, id: string) => { export const getAlias = (selNode: SelectedNode, peer: any, id: string) => {
options.url = selNode.settings.lnServerUrl + '/v1/listnodes'; const peerId = peer[id];
if (!peer[id]) { if (!peerId) {
logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Network', msg: 'Empty Peer ID' }); logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Network', msg: 'Empty Peer ID' });
peer.alias = ''; peer.alias = '';
return peer; return Promise.resolve(peer);
} }
options.body = { id : peer[id] };
if (aliasCache.has(peerId)) {
peer.alias = aliasCache.get(peerId)!;
return Promise.resolve(peer);
}
options.url = selNode.settings.lnServerUrl + '/v1/listnodes';
options.body = { id: peerId };
return request.post(options).then((body) => { return request.post(options).then((body) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Network', msg: 'Peer Alias Finished', data: body }); logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Network', msg: 'Peer Alias Finished', data: body });
peer.alias = body.nodes[0] && body.nodes[0].alias ? body.nodes[0].alias : peer[id].substring(0, 20); const alias = body.nodes?.[0]?.alias || peerId.substring(0, 20);
aliasCache.set(peerId, alias);
peer.alias = alias;
return peer; return peer;
}).catch((errRes) => { }).catch((errRes) => {
common.handleError(errRes, 'Network', 'Peer Alias Error', selNode); common.handleError(errRes, 'Network', 'Peer Alias Error', selNode);
peer.alias = peer[id].substring(0, 20); const alias = peerId.substring(0, 20);
peer.alias = alias;
return peer; return peer;
}); });
}; };

View file

@ -581,6 +581,45 @@ export class CommonService {
return JSON.parse(dataStr); return JSON.parse(dataStr);
}; };
public runWithConcurrencyLimit = (tasks, limit, done) => {
const results = new Array(tasks.length);
let nextIndex = 0;
let activeCount = 0;
const runNext = () => {
if (nextIndex >= tasks.length) {
if (activeCount === 0) {
done(results); // all tasks are finished
}
return;
}
const currentIndex = nextIndex++;
activeCount++;
const task = tasks[currentIndex];
if (typeof task !== 'function') {
results[currentIndex] = { error: new Error('Invalid task at index ' + currentIndex) };
activeCount--;
runNext();
return;
}
Promise.resolve().then(() => task()).then((result) => {
results[currentIndex] = result;
}).catch((err) => {
results[currentIndex] = { error: err };
}).finally(() => {
activeCount--;
runNext();
});
};
for (let i = 0; i < limit && i < tasks.length; i++) {
runNext();
}
};
} }
export const Common = new CommonService(); export const Common = new CommonService();

View file

@ -305,7 +305,6 @@
.tab-badge { .tab-badge {
& .mat-badge-content { & .mat-badge-content {
font-size: 90%; font-size: 90%;
padding-bottom: 2px;
&.mat-badge-active { &.mat-badge-active {
background: $primary-color; background: $primary-color;
} }
@ -313,8 +312,9 @@
} }
.mat-badge-medium.mat-badge-after .mat-badge-content { .mat-badge-medium.mat-badge-after .mat-badge-content {
right: unset;
margin-left: $gap !important; margin-left: $gap !important;
width: fit-content;
padding: 0 $gap;
@include for_screensize(phone) { @include for_screensize(phone) {
margin-left: 0 !important; margin-left: 0 !important;
} }