lightning
Version:
Lightning Network client library
193 lines (158 loc) • 6.19 kB
JavaScript
const asyncAuto = require('async/auto');
const asyncMap = require('async/map');
const {returnResult} = require('asyncjs-util');
const {externalType} = require('./constants');
const {isLnd} = require('./../../lnd_requests');
const {maxScore} = require('./constants');
const {prefAttachType} = require('./constants');
const {weightedType} = require('./constants');
const {wrongLnd} = require('./constants');
const {isArray} = Array;
const {keys} = Object;
const method = 'status';
const notFoundIndex = -1;
const type = 'autopilot';
/** Get Autopilot status
Optionally, get the score of nodes as considered by the autopilot.
Local scores reflect an internal scoring that includes local channel info
Permission `info:read` is required
{
lnd: <Authenticated LND Object>
[node_scores]: [<Get Score For Public Key Hex String>]
}
@returns via cbk or Promise
{
is_enabled: <Autopilot is Enabled Bool>
nodes: [{
local_preferential_score: <Local-adjusted Pref Attachment Score Number>
local_score: <Local-adjusted Externally Set Score Number>
preferential_score: <Preferential Attachment Score Number>
public_key: <Node Public Key Hex String>
score: <Externally Set Score Number>
weighted_local_score: <Combined Weighted Locally-Adjusted Score Number>
weighted_score: <Combined Weighted Score Number>
}]
}
*/
module.exports = (args, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!isLnd({method, type, lnd: args.lnd})) {
return cbk([400, 'ExpectedAutopilotEnabledLndToGetAutopilotStatus']);
}
return cbk();
},
// Get node scores
getNodeScores: ['validate', ({}, cbk) => {
return asyncMap([false, true], (isLocal, cbk) => {
if (!args.node_scores) {
return cbk();
}
return args.lnd.autopilot.queryScores({
ignore_local_state: !isLocal,
pubkeys: args.node_scores,
},
(err, res) => {
if (!!err) {
return cbk([503, 'UnexpectedErrorGettingNodeScores', {err}]);
}
if (!res) {
return cbk([503, 'ExpectedResultForLocalNodeScoresQuery']);
}
if (!isArray(res.results)) {
return cbk([503, 'ExpectedArrayOfResultsForNodesScoresQuery']);
}
if (res.results.findIndex(n => !n) !== notFoundIndex) {
return cbk([503, 'UnexpectedHeuristicResultSetInNodeScores']);
}
return cbk(null, {is_local: isLocal, results: res.results});
});
},
cbk);
}],
// Get status
getStatus: ['validate', ({}, cbk) => {
return args.lnd.autopilot.status({}, (err, res) => {
if (!!err && err.message === wrongLnd) {
return cbk([400, 'ExpectedLndBuiltWithAutopilotToGetStatus']);
}
if (!!err) {
return cbk([503, 'UnexpectedErrorGettingAutopilotStatus', {err}]);
}
if (!res) {
return cbk([503, 'UnexpectedEmptyResultGettingAutopilotStatus']);
}
if (res.active !== false && res.active !== true) {
return cbk([503, 'UnexpectedResponseForAutopilotStatusQuery']);
}
return cbk(null, {is_enabled: res.active});
});
}],
// Node scores
nodes: ['getNodeScores', ({getNodeScores}, cbk) => {
return asyncMap(getNodeScores, (gotResult, cbk) => {
if (!gotResult) {
return cbk(null, []);
}
return asyncMap(gotResult.results, ({heuristic, scores}, cbk) => {
if (!heuristic) {
return cbk([503, 'ExpectedHeuristicLabelForNodeScoreSet']);
}
if (!scores) {
return cbk([503, 'ExpectedScoresForNodesInScoreResults']);
}
return cbk(null, keys(scores).map(publicKey => ({
heuristic,
is_local: gotResult.is_local,
public_key: publicKey,
score: maxScore * scores[publicKey],
})));
},
cbk);
},
(err, nodes) => {
if (!!err) {
return cbk(err);
}
if (!args.node_scores) {
return cbk();
}
const flattenDeep = arr => !isArray(arr) ? [arr] :
arr.reduce((a, b) => a.concat(flattenDeep(b)) , []);
const allNodes = flattenDeep(nodes);
const publicKeys = {};
allNodes.forEach(node => publicKeys[node.public_key] = {});
const nodesWithScores = keys(publicKeys).map(publicKey => {
const scores = allNodes.filter(n => n.public_key === publicKey);
const score = scores.filter(n => n.heuristic === externalType);
const pref = scores.filter(n => n.heuristic === prefAttachType);
const weighted = scores.filter(n => n.heuristic === weightedType);
const localScore = score.find(n => n.is_local);
const localWeighted = weighted.find(n => n.is_local);
const localPreferential = pref.find(n => n.is_local);
const globalScore = score.find(n => !n.is_local);
const globalPreferential = pref.find(n => !n.is_local);
const globalWeighted = weighted.find(n => !n.is_local);
return {
local_preferential_score: localPreferential.score,
local_score: localScore.score,
preferential_score: globalPreferential.score,
public_key: publicKey,
score: globalScore.score,
weighted_local_score: localWeighted.score,
weighted_score: globalWeighted.score,
};
});
return cbk(null, nodesWithScores);
});
}],
// Summary
autopilot: ['getStatus', 'nodes', ({getStatus, nodes}, cbk) => {
return cbk(null, {is_enabled: getStatus.is_enabled, nodes});
}],
},
returnResult({reject, resolve, of: 'autopilot'}, cbk));
});
};