balanceofsatoshis 23.0.0 → 23.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Versions
2
2
 
3
+ ## 23.1.1
4
+
5
+ - Add `--avoid-append` to add failing edges to an avoid list based on formula
6
+ - Add support for LND 0.20.2
7
+
3
8
  ## 23.0.0
4
9
 
5
10
  ### Breaking Changes
package/README.md CHANGED
@@ -8,7 +8,7 @@ Commands for working with LND balances.
8
8
  Supported LND versions:
9
9
 
10
10
  - v0.21.0-beta to v0.21.1-beta
11
- - v0.20.0-beta to v0.20.1-beta
11
+ - v0.20.0-beta to v0.20.2-beta
12
12
 
13
13
  ## Install
14
14
 
package/bos CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ const {appendFile} = require('fs');
2
3
  const {lstat} = require('fs');
3
4
  const {mkdir} = require('fs');
4
5
  const {readdir} = require('fs');
@@ -1719,6 +1720,7 @@ prog
1719
1720
  .help('--avoid can take a FORMULA/public_key to avoid inbound peers')
1720
1721
  .help('--avoid can take a public_key/FORMULA to avoid outbound peers')
1721
1722
  .help('--avoid FORMULA variables: FEE_RATE, BASE_FEE, HEIGHT, AGE')
1723
+ .help('--avoid-append vars: FAILURE_REASON, FAILURE_INDEX, ROUTE_HOPS_COUNT')
1722
1724
  .help('--avoid-list allows passing a file of newline separated avoids')
1723
1725
  .help('--avoid-list will delete edges from the file unknown to the node')
1724
1726
  .help('--in decreases the inbound liquidity with a specific peer/tag')
@@ -1728,6 +1730,7 @@ prog
1728
1730
  .help('--out increases the inbound liquidity with a specific peer/tag')
1729
1731
  .option('--amount <amount>', 'Maximum amount to rebalance')
1730
1732
  .option('--avoid <pubkey_or_chanid>', 'Avoid forwarding through', REPEATABLE)
1733
+ .option('--avoid-append', 'Formula for appending failing edges to list')
1731
1734
  .option('--avoid-high-fee-routes', 'Avoid trying routes above max-fee-rate')
1732
1735
  .option('--avoid-list', 'Path to file with newline separated list of avoids')
1733
1736
  .option('--in <pubkey_or_alias>', 'Route in through a specific peer')
@@ -1747,8 +1750,9 @@ prog
1747
1750
  return swaps.manageRebalance({
1748
1751
  logger,
1749
1752
  avoid: flatten([options.avoid].filter(n => !!n)),
1753
+ avoid_append: options.avoidAppend || undefined,
1750
1754
  avoid_list: options.avoidList || undefined,
1751
- fs: {writeFile, getFile: readFile},
1755
+ fs: {appendFile, writeFile, getFile: readFile},
1752
1756
  in_filters: flatten([options.inFilter].filter(n => !!n)),
1753
1757
  in_outbound: options.inTargetOutbound || undefined,
1754
1758
  in_through: options.in || undefined,
@@ -29,6 +29,7 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
29
29
  [in_through]: <In Through Public Key Hex String>
30
30
  [is_strict_max_fee]: <Avoid Probing Too-High Fee Routes Bool>
31
31
  lnd: <Authenticated LND API Object>
32
+ [log_failure]: <Failure Details Log Callback Function>
32
33
  logger: <Winston Logger Object>
33
34
  [max_fee]: <Maximum Fee Tokens Number>
34
35
  [max_fee_mtokens]: <Maximum Fee Millitokens Number String>
@@ -180,6 +181,10 @@ module.exports = (args, cbk) => {
180
181
 
181
182
  // Log failures encountered while trying to find a route
182
183
  sub.on('routing_failure', async failure => {
184
+ if (!!args.log_failure) {
185
+ args.log_failure(null, failure);
186
+ }
187
+
183
188
  const {description} = await describeRoutingFailure({
184
189
  index: failure.index,
185
190
  lnd: args.lnd,
@@ -62,7 +62,8 @@ const tokAsMtok = tokens => (BigInt(tokens || 0) * BigInt(1e3)).toString();
62
62
  [is_push]: <Is Push Payment Bool>
63
63
  [is_real_payment]: <Pay the Request after Probing Bool> // default: false
64
64
  [is_strict_max_fee]: <Avoid Probing Too-High Fee Routes Bool>
65
- lnd: <Authenticated LND gRPC API Object>
65
+ lnd: <Authenticated LND API Object>
66
+ [log_failure]: <Failure Details Log Callback Function>
66
67
  logger: <Winston Logger Object>
67
68
  [max_fee]: <Maximum Fee Tokens Number>
68
69
  [max_fee_mtokens]: <Maximum Fee Millitokens Number>
@@ -411,6 +412,7 @@ module.exports = (args, cbk) => {
411
412
  in_through: args.in_through,
412
413
  is_strict_max_fee: args.is_strict_max_fee,
413
414
  lnd: args.lnd,
415
+ log_failure: args.log_failure,
414
416
  logger: args.logger,
415
417
  max_fee: args.max_fee,
416
418
  max_fee_mtokens: args.max_fee_mtokens,
package/package.json CHANGED
@@ -36,7 +36,7 @@
36
36
  "ini": "7.0.0",
37
37
  "inquirer": "14.0.2",
38
38
  "ln-accounting": "10.0.4",
39
- "ln-service": "59.2.2",
39
+ "ln-service": "59.2.3",
40
40
  "ln-sync": "8.0.3",
41
41
  "ln-telegram": "8.0.4",
42
42
  "minimist": "1.2.8",
@@ -80,5 +80,5 @@
80
80
  "postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
81
81
  "test": "node --experimental-test-coverage --test test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.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/telegram/*.js test/wallets/*.js"
82
82
  },
83
- "version": "23.0.0"
83
+ "version": "23.1.1"
84
84
  }
@@ -0,0 +1,108 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {evaluateFormula} = require('@alexbosworth/formulas');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const pairEdgeIndex = (pair, key) => `x${Number(!pair.indexOf(key))}`;
6
+
7
+ /** Append failing edge based on formula match
8
+
9
+ {
10
+ avoid: <Formula For Edge To Avoid String>
11
+ failure: {
12
+ index: <Failure Hop Index Number>
13
+ reason: <Failure Reason String>
14
+ route: [{
15
+ hops: [{
16
+ channel: <Standard Format Channel Id String>
17
+ public_key: <Node Public Key Hex String>
18
+ }]
19
+ }]
20
+ }
21
+ fs: {
22
+ appendFile: <Append to File Function> (path, content, cbk) => {}
23
+ }
24
+ list: <File Path To Avoid List String>
25
+ }
26
+
27
+ @returns via cbk
28
+ {
29
+ edge: <Appended Failing Edge Id String>
30
+ }
31
+ */
32
+ module.exports = ({avoid, failure, fs, list}, cbk) => {
33
+ return new Promise((resolve, reject) => {
34
+ return asyncAuto({
35
+ // Check arguments
36
+ validate: cbk => {
37
+ if (!avoid) {
38
+ return cbk([400, 'ExpectedAvoidanceFormulaToAppendFailingEdge']);
39
+ }
40
+
41
+ if (!failure) {
42
+ return cbk([400, 'ExpectedFailureDetailsToAppendFailingEdge']);
43
+ }
44
+
45
+ if (!fs) {
46
+ return cbk([400, 'ExpectedFilesystemMethodsToAppendFailingEdge']);
47
+ }
48
+
49
+ if (!list) {
50
+ return cbk([400, 'ExpectedPathToAvoidListToAppendFailingEdge']);
51
+ }
52
+
53
+ return cbk();
54
+ },
55
+
56
+ // Derive the set of edges
57
+ edges: ['validate', ({}, cbk) => {
58
+ return cbk(null, failure.route.hops.map((hop, i, hops) => {
59
+ const pair = [(hops[i - 1] || {}).public_key, hop.public_key].sort();
60
+
61
+ return `${hop.channel}${pairEdgeIndex(pair, hop.public_key)}`;
62
+ }));
63
+ }],
64
+
65
+ // Determine if there is a formula matching edge
66
+ failing: ['edges', ({edges}, cbk) => {
67
+ const edge = edges[failure.index];
68
+
69
+ try {
70
+ const {result} = evaluateFormula({
71
+ constants: {
72
+ FAILURE_INDEX: failure.index,
73
+ FAILURE_REASON: failure.reason,
74
+ ROUTE_HOPS_COUNT: failure.route.hops.length,
75
+ },
76
+ formula: avoid,
77
+ });
78
+
79
+ // Exit early when there is no append edge formula match
80
+ if (!result) {
81
+ return cbk(null, {});
82
+ }
83
+
84
+ return cbk(null, {edge});
85
+ } catch (err) {
86
+ return cbk([400, 'ExpectedValidAppendEdgeFormula', err.message]);
87
+ }
88
+ }],
89
+
90
+ // Append the failing edge to the avoid list
91
+ append: ['failing', ({failing}, cbk) => {
92
+ // Exit early when there is no relevant failing edge to append
93
+ if (!failing.edge) {
94
+ return cbk();
95
+ }
96
+
97
+ return fs.appendFile(list, `\n${failing.edge}`, err => {
98
+ if (!!err) {
99
+ return cbk([500, 'UnexpectedErrorAppendingFailEdge', err.message]);
100
+ }
101
+
102
+ return cbk()
103
+ });
104
+ }],
105
+ },
106
+ returnResult({reject, resolve, of: 'failing'}, cbk));
107
+ });
108
+ };
@@ -4,6 +4,7 @@ const asyncRetry = require('async/retry');
4
4
  const {getChannel} = require('ln-service');
5
5
  const {returnResult} = require('asyncjs-util');
6
6
 
7
+ const appendFailingEdge = require('./append_failing_edge');
7
8
  const rebalance = require('./rebalance');
8
9
 
9
10
  const channelFromEdge = edge => edge.slice(0, -2);
@@ -18,7 +19,10 @@ const uniq = arr => Array.from(new Set(arr));
18
19
 
19
20
  {
20
21
  [avoid]: [<Avoid Forwarding Through Node With Public Key Hex String>]
22
+ [avoid_append]: <Append Avoid Edges To Avoid List Matching Formula String>
23
+ [avoid_list]: <Use Avoid Directives From File At Path String>
21
24
  fs: {
25
+ appendFile: <Append to File Function> (path, content, cbk) => {}
22
26
  getFile: <Read File Contents Function> (path, cbk) => {}
23
27
  writeFile: <Write File Contents Function> (path, contents, cbk) => {}
24
28
  }
@@ -45,6 +49,10 @@ module.exports = (args, cbk) => {
45
49
  return asyncAuto({
46
50
  // Check arguments
47
51
  validate: cbk => {
52
+ if (!!args.avoid_append && !args.avoid_list) {
53
+ return cbk([400, 'ExpectedAvoidListToAppendAvoidsTo']);
54
+ }
55
+
48
56
  if (!args.fs) {
49
57
  return cbk([400, 'ExpectedFsToManageRebalance']);
50
58
  }
@@ -95,6 +103,35 @@ module.exports = (args, cbk) => {
95
103
  });
96
104
  }],
97
105
 
106
+ // Create failing edge logger for avoid appending
107
+ logFail: ['validate', ({}, cbk) => {
108
+ // Exit early when there is no avoid appending
109
+ if (!args.avoid_append) {
110
+ return cbk();
111
+ }
112
+
113
+ return cbk(null, (err, failure) => {
114
+ return appendFailingEdge({
115
+ failure,
116
+ avoid: args.avoid_append,
117
+ fs: args.fs,
118
+ list: args.avoid_list
119
+ },
120
+ (err, res) => {
121
+ if (!!err) {
122
+ return args.logger.error({append_failure_error: err});
123
+ }
124
+
125
+ // Exit early when there was no append
126
+ if (!res.edge) {
127
+ return;
128
+ }
129
+
130
+ return args.logger.info({appended_failing_edge: res.edge});
131
+ });
132
+ });
133
+ }],
134
+
98
135
  // Look at all of the lines in the file and clean them up
99
136
  getCleanAvoids: ['getAvoids', ({getAvoids}, cbk) => {
100
137
  return asyncFilter(getAvoids.file, (line, cbk) => {
@@ -119,7 +156,7 @@ module.exports = (args, cbk) => {
119
156
  }],
120
157
 
121
158
  // Run the rebalance
122
- rebalance: ['getAvoids', ({getAvoids}, cbk) => {
159
+ rebalance: ['getAvoids', 'logFail', ({getAvoids, logFail}, cbk) => {
123
160
  const start = new Date().toISOString();
124
161
 
125
162
  return asyncRetry({
@@ -156,6 +193,7 @@ module.exports = (args, cbk) => {
156
193
  in_through: args.in_through,
157
194
  is_strict_max_fee_rate: args.is_strict_max_fee_rate,
158
195
  lnd: args.lnd,
196
+ log_failure: logFail || undefined,
159
197
  logger: args.logger,
160
198
  max_fee: Number(args.max_fee) || undefined,
161
199
  max_fee_rate: Number(args.max_fee_rate) || undefined,
@@ -77,6 +77,7 @@ const uniq = arr => Array.from(new Set(arr));
77
77
  [in_through]: <Pay In Through Peer String>
78
78
  [is_strict_max_fee_rate]: <Avoid Probing Too-High Fee Rate Routes Bool>
79
79
  lnd: <Authenticated LND API Object>
80
+ [log_failure]: <Failure Details Log Callback Function>
80
81
  logger: <Winston Logger Object>
81
82
  [max_fee]: <Maximum Fee Tokens Number>
82
83
  [max_fee_rate]: <Max Fee Rate Tokens Per Million Number>
@@ -643,6 +644,7 @@ module.exports = (args, cbk) => {
643
644
  is_strict_max_fee: args.is_strict_max_fee_rate || undefined,
644
645
  logger: args.logger,
645
646
  lnd: args.lnd,
647
+ log_failure: args.log_failure,
646
648
  max_fee_mtokens: maxFeeMtokens,
647
649
  out_through: getOutbound.public_key,
648
650
  timeout_minutes: args.timeout_minutes,
@@ -0,0 +1,127 @@
1
+ const {deepEqual} = require('node:assert').strict;
2
+ const {rejects} = require('node:assert').strict;
3
+ const test = require('node:test');
4
+
5
+ const appendFailingEdge = require('./../../swaps/append_failing_edge.js');
6
+
7
+ const makeFailure = override => {
8
+ const failure = {
9
+ index: 1,
10
+ reason: 'TemporaryChannelFailure',
11
+ route: {
12
+ hops: [
13
+ {
14
+ channel: '1x1x1',
15
+ public_key: '02',
16
+ },
17
+ {
18
+ channel: '2x2x2',
19
+ public_key: '03',
20
+ },
21
+ ],
22
+ },
23
+ };
24
+
25
+ Object.keys(override).forEach(key => failure[key] = override[key]);
26
+
27
+ return failure;
28
+ };
29
+
30
+ const makeFs = ({err} = {}) => {
31
+ const fs = {
32
+ calls: [],
33
+ appendFile: (path, content, cbk) => {
34
+ fs.calls.push({content, path});
35
+
36
+ return cbk(err);
37
+ },
38
+ };
39
+
40
+ return fs;
41
+ };
42
+
43
+ const makeArgs = override => {
44
+ const args = {
45
+ avoid: [
46
+ 'AND(',
47
+ 'FAILURE_INDEX = 1,',
48
+ 'FAILURE_REASON = "TemporaryChannelFailure",',
49
+ 'ROUTE_HOPS_COUNT = 2',
50
+ ')',
51
+ ].join(''),
52
+ failure: makeFailure({}),
53
+ fs: makeFs({}),
54
+ list: '/tmp/avoid',
55
+ };
56
+
57
+ Object.keys(override).forEach(key => args[key] = override[key]);
58
+
59
+ return args;
60
+ };
61
+
62
+ const tests = [
63
+ {
64
+ args: makeArgs({avoid: undefined}),
65
+ description: 'An avoidance formula is required',
66
+ error: [400, 'ExpectedAvoidanceFormulaToAppendFailingEdge'],
67
+ },
68
+ {
69
+ args: makeArgs({failure: undefined}),
70
+ description: 'Failure details are required',
71
+ error: [400, 'ExpectedFailureDetailsToAppendFailingEdge'],
72
+ },
73
+ {
74
+ args: makeArgs({fs: undefined}),
75
+ description: 'Filesystem methods are required',
76
+ error: [400, 'ExpectedFilesystemMethodsToAppendFailingEdge'],
77
+ },
78
+ {
79
+ args: makeArgs({list: undefined}),
80
+ description: 'An avoid list path is required',
81
+ error: [400, 'ExpectedPathToAvoidListToAppendFailingEdge'],
82
+ },
83
+ {
84
+ args: makeArgs({avoid: '('}),
85
+ description: 'A valid avoidance formula is required',
86
+ error: [400, 'ExpectedValidAppendEdgeFormula'],
87
+ },
88
+ {
89
+ appended: [],
90
+ args: makeArgs({avoid: 'FALSE'}),
91
+ description: 'An edge is not appended when the formula does not match',
92
+ expected: {},
93
+ },
94
+ {
95
+ appended: [
96
+ {
97
+ content: '\n2x2x2x0',
98
+ path: '/tmp/avoid',
99
+ },
100
+ ],
101
+ args: makeArgs({}),
102
+ description: 'A failing edge is appended',
103
+ expected: {edge: '2x2x2x0'},
104
+ },
105
+ {
106
+ args: makeArgs({
107
+ fs: makeFs({err: new Error('Failed to append')}),
108
+ }),
109
+ description: 'An append error is returned',
110
+ error: [500, 'UnexpectedErrorAppendingFailEdge'],
111
+ },
112
+ ];
113
+
114
+ tests.forEach(({appended, args, description, error, expected}) => {
115
+ return test(description, async () => {
116
+ if (!!error) {
117
+ await rejects(appendFailingEdge(args), error, 'Got expected error');
118
+ } else {
119
+ const result = await appendFailingEdge(args);
120
+
121
+ deepEqual(result, expected, 'Got expected result');
122
+ deepEqual(args.fs.calls, appended, 'Got expected appended data');
123
+ }
124
+
125
+ return;
126
+ });
127
+ });