lightning
Version:
Lightning Network client library
168 lines (136 loc) • 4.99 kB
JavaScript
const asyncAuto = require('async/auto');
const asyncReduce = require('async/reduce');
const {returnResult} = require('asyncjs-util');
const getForwardingConfidence = require('./../offchain/get_forwarding_confidence');
const getForwardingReputations = require('./../offchain/get_forwarding_reputations');
const getIdentity = require('./../info/get_identity');
const combine = (a, b) => Math.round(a / 1e6 * b / 1e6);
const decBase = 10;
const defaultOdds = 950000;
const fullConfidence = 1e6;
const {isArray} = Array;
const notFoundIndex = -1;
const oddsDenominator = BigInt(1e6);
const unimplemented = 'QueryProbabilityNotImplemented';
/** Get confidence of successfully routing a payment to a destination
Requires `offchain:read` permission
If `from` is not set, self is default
{
[from]: <Starting Hex Serialized Public Key>
hops: [{
forward_mtokens: <Forward Millitokens String>
public_key: <Forward Edge Public Key Hex String>
}]
lnd: <Authenticated LND API Object>
}
@returns via cbk or Promise
{
confidence: <Confidence Score Out Of One Million Number>
}
*/
module.exports = ({from, hops, lnd}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!isArray(hops)) {
return cbk([400, 'ExpectedArrayOfHopsToCalculateRoutingOdds']);
}
if (hops.findIndex(n => !n.public_key) !== notFoundIndex) {
return cbk([400, 'ExpectedHopsWithEdges']);
}
return cbk();
},
// Get reputations
getReputations: ['validate', ({}, cbk) => {
return getForwardingReputations({lnd}, cbk);
}],
// Source key
source: ['validate', ({}, cbk) => {
if (!!from) {
return cbk(null, {public_key: from});
}
return getIdentity({lnd}, cbk);
}],
// Forwarding edges
forwarding: ['source', ({source}, cbk) => {
const forwarding = hops.slice().map((hop, i) => {
return {
channel: hop.channel,
forward_mtokens: hop.forward_mtokens,
from_public_key: !i ? source.public_key : hops[i - 1].public_key,
to_public_key: hops[i].public_key,
};
});
return cbk(null, forwarding.filter(n => !!n.from_public_key));
}],
// Get all confidence scores
getScores: ['source', ({source}, cbk) => {
const pairs = hops.slice().map((hop, i) => {
return {
from: !i ? source.public_key : hops[i - 1].public_key,
mtokens: hop.forward_mtokens,
to: hops[i].public_key,
};
});
return asyncReduce(pairs, fullConfidence, (confidence, pair, cbk) => {
return getForwardingConfidence({
lnd,
from: pair.from,
mtokens: pair.mtokens,
to: pair.to,
},
(err, res) => {
if (!!err) {
return cbk(err);
}
return cbk(null, combine(res.confidence, confidence));
});
},
(err, confidence) => {
if (!!err) {
const [, message] = err;
return message === unimplemented ? cbk() : cbk(err);
}
return cbk(null, {confidence});
});
}],
// Relevant channels
odds: [
'forwarding',
'getReputations',
'getScores',
({forwarding, getReputations, getScores}, cbk) =>
{
const odds = forwarding.map(forward => {
const forwardMtokens = BigInt(forward.forward_mtokens);
const forwardingNode = getReputations.nodes
.find(node => node.public_key === forward.from_public_key);
// Exit early with default odds when node has no reputation at all
if (!forwardingNode) {
return defaultOdds;
}
const forwardingPeer = forwardingNode.peers
.find(peer => peer.to_public_key === forward.to_public_key);
const forwarding = forwardingPeer;
// Exit early with general node odds when no chan reputation exists
if (!forwarding) {
return forwardingNode.confidence || defaultOdds;
}
// Exit early with default odds when reputation is not relevant
if (forwardMtokens < BigInt(forwarding.min_relevant_tokens || '0')) {
return forwardingNode.confidence;
}
return forwarding.confidence;
});
const totalDenominator = odds.slice(1)
.reduce((sum, n) => sum * oddsDenominator, BigInt('1'));
const totalOdds = odds
.reduce((sum, n) => sum * BigInt(n || '0'), BigInt('1'));
const successOdds = (totalOdds / totalDenominator).toString();
return cbk(null, {confidence: parseInt(successOdds, decBase)});
}],
},
returnResult({reject, resolve, of: 'odds'}, cbk));
});
};