mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
fix telegram pay, upgrade avoid on pay/probe
This commit is contained in:
parent
24df4dd996
commit
d9a272d32c
15 changed files with 2551 additions and 2175 deletions
|
|
@ -1,5 +1,12 @@
|
|||
# Versions
|
||||
|
||||
## Version 10.7.0
|
||||
|
||||
- `pay`: Expand `--avoid` support to avoiding channels and tags and peer aliases/pubkeys
|
||||
- `probe`: Expand `--avoid` support to avoiding channels and tags and peer aliases/pubkeys
|
||||
- `telegram`: Fix `/pay` command when paying to nodes that require payment nonces
|
||||
- `telegram`: Fix `/pay` command responding with error when no budget is set for payments
|
||||
|
||||
## Version 10.6.2
|
||||
|
||||
- `open`: Fix ExpectedFinalScriptSigsAndWitnesses error when supplying some PSBTs
|
||||
|
|
|
|||
8
bos
8
bos
|
|
@ -1020,7 +1020,7 @@ prog
|
|||
// Pay a payment request, probing first
|
||||
.command('pay', 'Pay a payment request, probing first')
|
||||
.argument('<request>', 'Payment Request')
|
||||
.option('--avoid <pubkey>', 'Avoid forwarding through node', REPEATABLE)
|
||||
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
||||
.option('--in <public_key>', 'Route through specific peer of destination')
|
||||
.option('--max-fee <max_fee>', 'Maximum fee to pay', INT, 1337)
|
||||
.option('--max-paths <paths>', 'Maximum paths to use', INT, 1)
|
||||
|
|
@ -1038,8 +1038,8 @@ prog
|
|||
return network.pay({
|
||||
lnd,
|
||||
logger,
|
||||
avoid: flatten([options.avoid].filter(n => !!n)),
|
||||
fs: {getFile: readFile},
|
||||
ignore: routing.ignoreFromAvoid({avoid: options.avoid}).ignore,
|
||||
in_through: inThrough.public_key,
|
||||
is_real_payment: true,
|
||||
max_fee: options.maxFee,
|
||||
|
|
@ -1131,7 +1131,7 @@ prog
|
|||
.help('Simulate paying a payment request without actually paying it')
|
||||
.argument('<to>', 'Payment request or node public key')
|
||||
.argument('[amount]', 'Amount to probe, default: request amount')
|
||||
.option('--avoid <pubkey>', 'Avoid forwarding through node', REPEATABLE)
|
||||
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
||||
.option('--find-max', 'Find the maximum routeable amount on success route')
|
||||
.option('--in <public_key>', 'Route through specific peer of destination')
|
||||
.option('--max-paths <max>', 'Maximum paths to use for find-max', INT, 1)
|
||||
|
|
@ -1148,10 +1148,10 @@ prog
|
|||
return network.probe({
|
||||
lnd,
|
||||
logger,
|
||||
avoid: flatten([options.avoid].filter(n => !!n)),
|
||||
destination: args.to.length === 66 ? args.to : undefined,
|
||||
find_max: !!options.findMax ? 16777215 : undefined,
|
||||
fs: {getFile: readFile},
|
||||
ignore: routing.ignoreFromAvoid({avoid: options.avoid}).ignore,
|
||||
in_through: inThrough.public_key,
|
||||
max_paths: options.maxPaths,
|
||||
out: flatten([options.out].filter(n => !!n)),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
|
|||
}]
|
||||
[ignore]: [{
|
||||
from_public_key: <Avoid Node With Public Key Hex String>
|
||||
[to_public_key]: <Avoid Routing To Node With Public Key Hex String>
|
||||
}]
|
||||
[in_through]: <In Through Public Key Hex String>
|
||||
[is_strict_max_fee]: <Avoid Probing Too-High Fee Routes Bool>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const asyncMap = require('async/map');
|
|||
const {findKey} = require('ln-sync');
|
||||
const {formatTokens} = require('ln-sync');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getIdentity} = require('ln-service');
|
||||
const {getSyntheticOutIgnores} = require('probing');
|
||||
const {parsePaymentRequest} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
|
@ -11,6 +12,8 @@ const {subscribeToMultiPathProbe} = require('probing');
|
|||
|
||||
const {describeRoute} = require('./../display');
|
||||
const {describeRoutingFailure} = require('./../display');
|
||||
const {getIgnores} = require('./../routing');
|
||||
const {getTags} = require('./../tags');
|
||||
const probeDestination = require('./probe_destination');
|
||||
|
||||
const cltvDeltaBuffer = 3;
|
||||
|
|
@ -22,13 +25,10 @@ const singlePath = 1;
|
|||
/** Make a payment
|
||||
|
||||
{
|
||||
avoid: [<Avoid Forwarding Through String>]
|
||||
[fs]: {
|
||||
getFile: <Read File Contents Function> (path, cbk) => {}
|
||||
}
|
||||
[ignore]: [{
|
||||
from_public_key: <From Public Key Hex String>
|
||||
[to_public_key]: <To Public Key Hex String>
|
||||
}]
|
||||
[in_through]: <Pay In Through Node With Public Key Hex String>
|
||||
lnd: <Authenticated LND API Object>
|
||||
logger: <Winston Logger Object>
|
||||
|
|
@ -44,6 +44,10 @@ module.exports = (args, cbk) => {
|
|||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!isArray(args.avoid)) {
|
||||
return cbk([400, 'ExpectedAvoidArrayToPayPaymentRequest']);
|
||||
}
|
||||
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedAuthenticatedLndToPayPaymentRequest']);
|
||||
}
|
||||
|
|
@ -81,6 +85,26 @@ module.exports = (args, cbk) => {
|
|||
return cbk();
|
||||
},
|
||||
|
||||
// Get channels for figuring out avoid flags
|
||||
getChannels: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getChannels({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get the node public key
|
||||
getIdentity: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getIdentity({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Find public keys to pay out through
|
||||
getOuts: ['validate', ({}, cbk) => {
|
||||
return asyncMap(args.out, (query, cbk) => {
|
||||
|
|
@ -89,8 +113,46 @@ module.exports = (args, cbk) => {
|
|||
cbk);
|
||||
}],
|
||||
|
||||
// Get tags for figuring out avoid flags
|
||||
getTags: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getTags({fs: args.fs}, cbk);
|
||||
}],
|
||||
|
||||
// Get base ignores
|
||||
getBaseIgnores: [
|
||||
'getChannels',
|
||||
'getIdentity',
|
||||
'getTags',
|
||||
({getChannels, getIdentity, getTags}, cbk) =>
|
||||
{
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk(null, {ignore: []});
|
||||
}
|
||||
|
||||
return getIgnores({
|
||||
avoid: args.avoid,
|
||||
channels: getChannels.channels,
|
||||
in_through: args.in_through,
|
||||
lnd: args.lnd,
|
||||
logger: args.logger,
|
||||
public_key: getIdentity.public_key,
|
||||
tags: getTags.tags,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get synthetic ignores to approximate out
|
||||
getIgnores: ['getOuts', ({getOuts}, cbk) => {
|
||||
getIgnores: [
|
||||
'getBaseIgnores',
|
||||
'getOuts',
|
||||
({getBaseIgnores, getOuts}, cbk) =>
|
||||
{
|
||||
// Exit early when not doing a multi-path
|
||||
if (args.max_paths === singlePath) {
|
||||
return cbk();
|
||||
|
|
@ -98,11 +160,11 @@ module.exports = (args, cbk) => {
|
|||
|
||||
// Exit early when there is no outbound restriction
|
||||
if (!getOuts.length) {
|
||||
return cbk(null, {ignore: args.ignore});
|
||||
return cbk(null, {ignore: getBaseIgnores.ignore});
|
||||
}
|
||||
|
||||
return getSyntheticOutIgnores({
|
||||
ignore: args.ignore,
|
||||
ignore: getBaseIgnores.ignore,
|
||||
lnd: args.lnd,
|
||||
out: getOuts.map(n => n.public_key),
|
||||
},
|
||||
|
|
@ -110,7 +172,10 @@ module.exports = (args, cbk) => {
|
|||
}],
|
||||
|
||||
// Make single-path payment
|
||||
singlePathPay: ['getOuts', ({getOuts}, cbk) => {
|
||||
singlePathPay: [
|
||||
'getBaseIgnores',
|
||||
'getOuts', ({getBaseIgnores, getOuts}, cbk) =>
|
||||
{
|
||||
// Exit early when doing a multi-path
|
||||
if (args.max_paths !== singlePath) {
|
||||
return cbk();
|
||||
|
|
@ -124,7 +189,7 @@ module.exports = (args, cbk) => {
|
|||
|
||||
return probeDestination({
|
||||
fs: args.fs,
|
||||
ignore: args.ignore,
|
||||
ignore: getBaseIgnores.ignore,
|
||||
in_through: args.in_through,
|
||||
is_real_payment: true,
|
||||
logger: args.logger,
|
||||
|
|
|
|||
115
network/probe.js
115
network/probe.js
|
|
@ -4,6 +4,8 @@ const asyncWhilst = require('async/whilst');
|
|||
const {findKey} = require('ln-sync');
|
||||
const {formatTokens} = require('ln-sync');
|
||||
const {getChannel} = require('ln-service');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getIdentity} = require('ln-service');
|
||||
const {getNode} = require('ln-service');
|
||||
const {getSyntheticOutIgnores} = require('probing');
|
||||
const {getWalletVersion} = require('ln-service');
|
||||
|
|
@ -13,6 +15,8 @@ const {subscribeToMultiPathProbe} = require('probing');
|
|||
|
||||
const {describeRoute} = require('./../display');
|
||||
const {describeRoutingFailure} = require('./../display');
|
||||
const {getIgnores} = require('./../routing');
|
||||
const {getTags} = require('./../tags');
|
||||
const {parseAmount} = require('./../display');
|
||||
const probeDestination = require('./probe_destination');
|
||||
|
||||
|
|
@ -28,14 +32,12 @@ const unsupported = 501;
|
|||
/** Probe a destination, looking for multiple non-overlapping paths
|
||||
|
||||
{
|
||||
avoid: [<Avoid Forwarding Through String>]
|
||||
[destination]: <Destination Public Key Hex String>
|
||||
[find_max]: <Find Maximum Payable On Probed Routes Below Tokens Number>
|
||||
[fs]: {
|
||||
getFile: <Read File Contents Function> (path, cbk) => {}
|
||||
}
|
||||
[ignore]: [{
|
||||
from_public_key: <Avoid Node With Public Key Hex String>
|
||||
}]
|
||||
[in_through]: <Pay In Through Public Key Hex String>
|
||||
lnd: <Authenticated LND API Object>
|
||||
logger: <Winston Logger Object>
|
||||
|
|
@ -59,6 +61,10 @@ module.exports = (args, cbk) => {
|
|||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!isArray(args.avoid)) {
|
||||
return cbk([400, 'ExpectedAvoidArrayToProbe']);
|
||||
}
|
||||
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedLndApiObjectToProbe']);
|
||||
}
|
||||
|
|
@ -82,21 +88,6 @@ module.exports = (args, cbk) => {
|
|||
return cbk();
|
||||
},
|
||||
|
||||
// Determine if this wallet is a legacy
|
||||
isLegacy: ['validate', ({}, cbk) => {
|
||||
return getWalletVersion({lnd: args.lnd}, err => {
|
||||
if (!!err && err.slice().shift === unsupported) {
|
||||
return cbk(null, true);
|
||||
}
|
||||
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
return cbk(null, false);
|
||||
});
|
||||
}],
|
||||
|
||||
// Decode payment request
|
||||
decodeRequest: ['validate', ({}, cbk) => {
|
||||
// Exit early when there is no request to decode
|
||||
|
|
@ -114,6 +105,26 @@ module.exports = (args, cbk) => {
|
|||
});
|
||||
}],
|
||||
|
||||
// Get channels for figuring out avoid flags
|
||||
getChannels: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getChannels({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get the node public key
|
||||
getIdentity: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getIdentity({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Find public keys to pay out through
|
||||
getOuts: ['validate', ({}, cbk) => {
|
||||
return asyncMap(args.out, (query, cbk) => {
|
||||
|
|
@ -122,6 +133,31 @@ module.exports = (args, cbk) => {
|
|||
cbk);
|
||||
}],
|
||||
|
||||
// Get tags for figuring out avoid flags
|
||||
getTags: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getTags({fs: args.fs}, cbk);
|
||||
}],
|
||||
|
||||
// Determine if this wallet is a legacy
|
||||
isLegacy: ['validate', ({}, cbk) => {
|
||||
return getWalletVersion({lnd: args.lnd}, err => {
|
||||
if (!!err && err.slice().shift === unsupported) {
|
||||
return cbk(null, true);
|
||||
}
|
||||
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
return cbk(null, false);
|
||||
});
|
||||
}],
|
||||
|
||||
// Parse amount to probe
|
||||
tokens: ['validate', ({}, cbk) => {
|
||||
// Exit early when no tokens are specified
|
||||
|
|
@ -136,8 +172,36 @@ module.exports = (args, cbk) => {
|
|||
}
|
||||
}],
|
||||
|
||||
// Get base ignores
|
||||
getBaseIgnores: [
|
||||
'getChannels',
|
||||
'getIdentity',
|
||||
'getTags',
|
||||
({getChannels, getIdentity, getTags}, cbk) =>
|
||||
{
|
||||
// Exit early when there are no avoids
|
||||
if (!args.avoid.length) {
|
||||
return cbk(null, {ignore: []});
|
||||
}
|
||||
|
||||
return getIgnores({
|
||||
avoid: args.avoid,
|
||||
channels: getChannels.channels,
|
||||
in_through: args.in_through,
|
||||
lnd: args.lnd,
|
||||
logger: args.logger,
|
||||
public_key: getIdentity.public_key,
|
||||
tags: getTags.tags,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get synthetic ignores to approximate out
|
||||
getIgnores: ['getOuts', ({getOuts}, cbk) => {
|
||||
getIgnores: [
|
||||
'getBaseIgnores',
|
||||
'getOuts',
|
||||
({getBaseIgnores, getOuts}, cbk) =>
|
||||
{
|
||||
// Exit early when not doing a multi-path
|
||||
if (!args.find_max && args.max_paths === singlePath) {
|
||||
return cbk();
|
||||
|
|
@ -145,11 +209,11 @@ module.exports = (args, cbk) => {
|
|||
|
||||
// Exit early when there is no outbound restriction
|
||||
if (!getOuts.length) {
|
||||
return cbk(null, {ignore: args.ignore});
|
||||
return cbk(null, {ignore: getBaseIgnores.ignore});
|
||||
}
|
||||
|
||||
return getSyntheticOutIgnores({
|
||||
ignore: args.ignore,
|
||||
ignore: getBaseIgnores.ignore,
|
||||
lnd: args.lnd,
|
||||
out: getOuts.map(n => n.public_key),
|
||||
},
|
||||
|
|
@ -157,7 +221,12 @@ module.exports = (args, cbk) => {
|
|||
}],
|
||||
|
||||
// Probe just through a single path
|
||||
singleProbe: ['getOuts', 'tokens', ({getOuts, tokens}, cbk) => {
|
||||
singleProbe: [
|
||||
'getBaseIgnores',
|
||||
'getOuts',
|
||||
'tokens',
|
||||
({getBaseIgnores, getOuts, tokens}, cbk) =>
|
||||
{
|
||||
// Exit early when not finding max
|
||||
if (!!args.find_max || args.max_paths !== singlePath) {
|
||||
return cbk();
|
||||
|
|
@ -174,7 +243,7 @@ module.exports = (args, cbk) => {
|
|||
tokens,
|
||||
destination: args.destination,
|
||||
fs: args.fs,
|
||||
ignore: args.ignore,
|
||||
ignore: getBaseIgnores.ignore,
|
||||
in_through: args.in_through,
|
||||
lnd: args.lnd,
|
||||
logger: args.logger,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const tokAsMtok = tokens => (BigInt(tokens || 0) * BigInt(1e3)).toString();
|
|||
}
|
||||
[ignore]: [{
|
||||
from_public_key: <Avoid Node With Public Key Hex String>
|
||||
[to_public_key]: <Avoid Routing To Node With Public Key Hex String>
|
||||
}]
|
||||
[in_through]: <Pay In Through Public Key Hex String>
|
||||
[is_push]: <Is Push Payment Bool>
|
||||
|
|
|
|||
4125
package-lock.json
generated
4125
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -30,12 +30,12 @@
|
|||
"hot-formula-parser": "4.0.0",
|
||||
"import-lazy": "4.0.0",
|
||||
"ini": "2.0.0",
|
||||
"inquirer": "8.1.1",
|
||||
"inquirer": "8.1.2",
|
||||
"invoices": "2.0.0",
|
||||
"ln-accounting": "4.3.1",
|
||||
"ln-service": "51.8.8",
|
||||
"ln-service": "51.9.0",
|
||||
"ln-sync": "0.4.6",
|
||||
"ln-telegram": "3.2.8",
|
||||
"ln-telegram": "3.2.9",
|
||||
"moment": "2.29.1",
|
||||
"node-fetch": "2.6.1",
|
||||
"paid-services": "2.0.1",
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
"build-docker": "docker build -t alexbosworth/balanceofsatoshis . && docker save alexbosworth/balanceofsatoshis > balanceofsatoshis.tar && gzip balanceofsatoshis.tar",
|
||||
"postpack": "PACKAGE_VERSION=$(cat package.json | grep \\\"version\\\" | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]') && git tag -s v$PACKAGE_VERSION -m v$PACKAGE_VERSION && git push github --tags",
|
||||
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
||||
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/fiat/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/wallets/*.js"
|
||||
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/fiat/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/wallets/*.js"
|
||||
},
|
||||
"version": "10.6.2"
|
||||
"version": "10.7.0"
|
||||
}
|
||||
|
|
|
|||
196
routing/get_ignores.js
Normal file
196
routing/get_ignores.js
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const asyncMap = require('async/map');
|
||||
const {findKey} = require('ln-sync');
|
||||
const {getChannel} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const flatten = arr => [].concat(...arr);
|
||||
const {isArray} = Array;
|
||||
const isChannel = n => /^\d*x\d*x\d*$/.test(n);
|
||||
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
|
||||
/** Get ignores for avoids
|
||||
|
||||
{
|
||||
avoid: [<Avoid Forwarding Through Node With Public Key Hex String>]
|
||||
channels: [<Channel Object>]
|
||||
[in_through]: <In Through Public Key Hex String>
|
||||
lnd: <Authenticated LND API Object>
|
||||
logger: <Winston Logger Object>
|
||||
[out_through]: <Out Through Public Key Hex String>
|
||||
public_key: <Identity Public Key Hex String>
|
||||
tags: [{
|
||||
alias: <Tag Alias String>
|
||||
id: <Tag Id String>
|
||||
[is_avoided]: <Tag Nodes Are Avoided For Routing Bool>
|
||||
nodes: [<Node Public Key Hex String>]
|
||||
}]
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
ignore: [{
|
||||
from_public_key: <Avoid Node With Public Key Hex String>
|
||||
[to_public_key]: <Avoid Routing To Node With Public Key Hex String>
|
||||
}]
|
||||
}
|
||||
*/
|
||||
module.exports = (args, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!isArray(args.avoid)) {
|
||||
return cbk([400, 'ExpectedArrayOfAvoidIdsToGetIgnores']);
|
||||
}
|
||||
|
||||
if (!isArray(args.channels)) {
|
||||
return cbk([400, 'ExpectedArrayOfChannelsToGetIgnores']);
|
||||
}
|
||||
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedLndToGetIgnores']);
|
||||
}
|
||||
|
||||
if (!args.logger) {
|
||||
return cbk([400, 'ExpectedLoggerToGetIgnores']);
|
||||
}
|
||||
|
||||
if (!args.public_key) {
|
||||
return cbk([400, 'ExpectedPublicKeyToGetIgnores']);
|
||||
}
|
||||
|
||||
if (!isArray(args.tags)) {
|
||||
return cbk([400, 'ExpectedArrayOfTagsToGetIgnores']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Set of avoids
|
||||
avoids: ['validate', ({}, cbk) => {
|
||||
// Find global avoids in tags
|
||||
const tagAvoids = flatten(args.tags
|
||||
.filter(n => !!n.is_avoided)
|
||||
.map(({nodes}) => nodes));
|
||||
|
||||
// Mix global avoids with explicit avoids
|
||||
const avoids = [].concat(args.avoid).concat(tagAvoids)
|
||||
.filter(n => n !== args.public_key)
|
||||
.map(n => n.toLowerCase());
|
||||
|
||||
// Never avoid the source key
|
||||
return cbk(null, uniq(avoids));
|
||||
}],
|
||||
|
||||
// Avoids sorted by type
|
||||
sortedAvoids: ['avoids', ({avoids}, cbk) => {
|
||||
const withKeys = avoids.map(id => {
|
||||
// Exit early when the id is a public key
|
||||
if (isPublicKey(id)) {
|
||||
return {node: {from_public_key: id}};
|
||||
}
|
||||
|
||||
// Exit early when the id is a channel
|
||||
if (isChannel(id)) {
|
||||
return {channel: id};
|
||||
}
|
||||
|
||||
const tagByAlias = args.tags.find(n => n.alias === id);
|
||||
const tagById = args.tags.find(n => n.id === id);
|
||||
|
||||
// Exit early when the id matches a tag alias or id
|
||||
if (!!tagByAlias || !!tagById) {
|
||||
const {nodes} = tagByAlias || tagById;
|
||||
|
||||
args.logger.info({avoiding_tag: `${id}: ${nodes.length} nodes`});
|
||||
|
||||
return nodes.map(n => ({node: {from_public_key: n}}));
|
||||
}
|
||||
|
||||
return {query: id};
|
||||
});
|
||||
|
||||
return cbk(null, flatten(withKeys));
|
||||
}],
|
||||
|
||||
// Get referenced channels
|
||||
getChannelIgnores: ['sortedAvoids', ({sortedAvoids}, cbk) => {
|
||||
const ids = sortedAvoids.map(n => n.channel).filter(n => !!n);
|
||||
|
||||
return asyncMap(ids, (id, cbk) => {
|
||||
return getChannel({id, lnd: args.lnd}, (err, res) => {
|
||||
if (!!err) {
|
||||
return cbk([404, 'FailedToFindChannelToAvoid', {err, id}]);
|
||||
}
|
||||
|
||||
const [node1, node2] = res.policies.map(n => n.public_key);
|
||||
|
||||
const ignore = [
|
||||
{channel: id, from_public_key: node1, to_public_key: node2},
|
||||
{channel: id, from_public_key: node2, to_public_key: node1},
|
||||
];
|
||||
|
||||
return cbk(null, ignore);
|
||||
});
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Resolve referenced queries
|
||||
getQueryIgnores: ['sortedAvoids', ({sortedAvoids}, cbk) => {
|
||||
const queries = sortedAvoids.map(n => n.query).filter(n => !!n);
|
||||
|
||||
return asyncMap(queries, (query, cbk) => {
|
||||
return findKey({
|
||||
query,
|
||||
lnd: args.lnd,
|
||||
channels: args.channels,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
return cbk(null, {from_public_key: res.public_key});
|
||||
});
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Combine ignores together
|
||||
combinedIgnores: [
|
||||
'getChannelIgnores',
|
||||
'getQueryIgnores',
|
||||
'sortedAvoids',
|
||||
({getChannelIgnores, getQueryIgnores, sortedAvoids}, cbk) =>
|
||||
{
|
||||
const chanIgnores = flatten(getChannelIgnores);
|
||||
const nodeIgnores = sortedAvoids.map(n => n.node).filter(n => !!n);
|
||||
|
||||
const ignore = [chanIgnores, getQueryIgnores, nodeIgnores];
|
||||
|
||||
const allIgnores = flatten(ignore).filter(avoid => {
|
||||
const isFromInThrough = avoid.from_public_key === args.in_through;
|
||||
const isFromSelf = avoid.from_public_key === args.public_key;
|
||||
const isToOutThrough = avoid.to_public_key === args.out_through;
|
||||
const isToSelf = avoid.to_public_key === args.public_key;
|
||||
|
||||
if (isFromSelf && isToOutThrough) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isToSelf && isFromInThrough) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return cbk(null, {ignore: allIgnores});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'combinedIgnores'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
@ -4,6 +4,7 @@ const channelsFromHints = require('./channels_from_hints');
|
|||
const getChainFeesChart = require('./get_chain_fees_chart');
|
||||
const getFeesChart = require('./get_fees_chart');
|
||||
const getFeesPaid = require('./get_fees_paid');
|
||||
const getIgnores = require('./get_ignores');
|
||||
const getPastForwards = require('./get_past_forwards');
|
||||
const giftRoute = require('./gift_route');
|
||||
const ignoreFromAvoid = require('./ignore_from_avoid');
|
||||
|
|
@ -15,6 +16,7 @@ module.exports = {
|
|||
getChainFeesChart,
|
||||
getFeesChart,
|
||||
getFeesPaid,
|
||||
getIgnores,
|
||||
getPastForwards,
|
||||
giftRoute,
|
||||
ignoreFromAvoid,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ module.exports = ({ask, fs, lnd, logger, network, node}, cbk) => {
|
|||
fs,
|
||||
lnd,
|
||||
logger,
|
||||
avoid: [],
|
||||
max_fee: defaultMaxFee,
|
||||
max_paths: defaultMaxPaths,
|
||||
out: [],
|
||||
|
|
|
|||
67
tags/get_tags.js
Normal file
67
tags/get_tags.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
const {homedir} = require('os');
|
||||
const {join} = require('path');
|
||||
|
||||
const asyncAuto = require('async/auto');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const defaultTags = {tags: []};
|
||||
const {isArray} = Array;
|
||||
const {parse} = JSON;
|
||||
const tagFilePath = () => join(...[homedir(), '.bos', 'tags.json']);
|
||||
|
||||
/** Get tagged nodes
|
||||
|
||||
{
|
||||
fs: {
|
||||
getFile: <Get File Function>
|
||||
}
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
tags: [{
|
||||
alias: <Tag Alias String>
|
||||
id: <Tag Id String>
|
||||
[is_avoided]: <Avoid Node in Routing Bool>
|
||||
nodes: [<Node Public Key Hex String>]
|
||||
}]
|
||||
}
|
||||
*/
|
||||
module.exports = ({fs}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!fs) {
|
||||
return cbk([400, 'ExpectedFileSystemMethodsToGetTags']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Fetch the tags
|
||||
getTags: ['validate', ({}, cbk) => {
|
||||
return fs.getFile(tagFilePath(), (err, res) => {
|
||||
// Fail back to no tags when there is an error
|
||||
if (!!err || !res) {
|
||||
return cbk(null, defaultTags);
|
||||
}
|
||||
|
||||
try {
|
||||
const {tags} = parse(res.toString());
|
||||
|
||||
// Exit early when tags are not well formed
|
||||
if (!isArray(tags) || !!tags.filter(n => !n).length) {
|
||||
return cbk(null, defaultTags);
|
||||
}
|
||||
|
||||
return cbk(null, {tags});
|
||||
} catch (err) {
|
||||
return cbk(null, defaultTags);
|
||||
}
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'getTags'}, cbk));
|
||||
});
|
||||
};
|
||||
3
tags/index.js
Normal file
3
tags/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const getTags = require('./get_tags');
|
||||
|
||||
module.exports = {getTags};
|
||||
|
|
@ -327,11 +327,11 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
|
|||
}
|
||||
});
|
||||
|
||||
bot.command('pay', async ({message, reply}) => {
|
||||
bot.command('pay', async ctx => {
|
||||
const budget = paymentsLimit;
|
||||
|
||||
if (!budget) {
|
||||
reply(interaction.pay_budget_depleted);
|
||||
ctx.reply(interaction.pay_budget_depleted);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -341,13 +341,13 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
|
|||
|
||||
handlePayCommand({
|
||||
budget,
|
||||
reply,
|
||||
request,
|
||||
from: message.from.id,
|
||||
from: ctx.message.from.id,
|
||||
id: connectedId,
|
||||
key: apiKey.key,
|
||||
nodes: allNodes,
|
||||
text: message.text,
|
||||
reply: ctx.reply,
|
||||
text: ctx.message.text,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
|
|
|
|||
97
test/tags/test_get_tags.js
Normal file
97
test/tags/test_get_tags.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
const {test} = require('@alexbosworth/tap');
|
||||
|
||||
const method = require('./../../tags/get_tags');
|
||||
|
||||
const makeArgs = overrides => {
|
||||
const args = {
|
||||
fs: {
|
||||
getFile: ({}, cbk) => {
|
||||
const tags = [{
|
||||
alias: 'alias',
|
||||
id: Buffer.alloc(32).toString('hex'),
|
||||
nodes: [Buffer.alloc(33, 2).toString('hex')],
|
||||
}];
|
||||
|
||||
return cbk(null, Buffer.from(JSON.stringify({tags})));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Object.keys(overrides).forEach(k => args[k] = overrides[k]);
|
||||
|
||||
return args;
|
||||
};
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: makeArgs({}),
|
||||
description: 'Get tags',
|
||||
expected: {
|
||||
tags: [{
|
||||
alias: 'alias',
|
||||
id: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
nodes: [
|
||||
'020202020202020202020202020202020202020202020202020202020202020202',
|
||||
],
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
args: makeArgs({fs: {getFile: ({}, cbk) => cbk('err')}}),
|
||||
description: 'Get file has an error',
|
||||
expected: {tags: []},
|
||||
},
|
||||
{
|
||||
args: makeArgs({fs: {getFile: ({}, cbk) => cbk()}}),
|
||||
description: 'Get file returns nothing',
|
||||
expected: {tags: []},
|
||||
},
|
||||
{
|
||||
args: makeArgs({
|
||||
fs: {getFile: ({}, cbk) => cbk(null, Buffer.from('invalid_json'))},
|
||||
}),
|
||||
description: 'Get file returns invalid JSON',
|
||||
expected: {tags: []},
|
||||
},
|
||||
{
|
||||
args: makeArgs({
|
||||
fs: {
|
||||
getFile: ({}, cbk) => {
|
||||
return cbk(null, Buffer.from(JSON.stringify({tags: 'tags'})));
|
||||
},
|
||||
},
|
||||
}),
|
||||
description: 'Get file returns invalid tags',
|
||||
expected: {tags: []},
|
||||
},
|
||||
{
|
||||
args: makeArgs({
|
||||
fs: {
|
||||
getFile: ({}, cbk) => {
|
||||
return cbk(null, Buffer.from(JSON.stringify({tags: [null]})));
|
||||
},
|
||||
},
|
||||
}),
|
||||
description: 'Get file returns invalid tag data',
|
||||
expected: {tags: []},
|
||||
},
|
||||
{
|
||||
args: makeArgs({fs: undefined}),
|
||||
description: 'Fs is required',
|
||||
error: [400, 'ExpectedFileSystemMethodsToGetTags'],
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, async ({end, equal, rejects, strictSame}) => {
|
||||
if (!!error) {
|
||||
await rejects(method(args, args.test), error, 'Got error');
|
||||
} else {
|
||||
const res = await method(args);
|
||||
|
||||
strictSame(res, expected, 'Got expected res');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue