balanceofsatoshis 22.1.7 → 23.1.0
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 +12 -0
- package/README.md +6 -3
- package/bos +5 -1
- package/display/index.js +0 -2
- package/display/is_matching_filters.js +6 -20
- package/network/execute_probe.js +5 -0
- package/network/probe_destination.js +3 -1
- package/package.json +9 -9
- package/peers/find_tag_match.js +20 -14
- package/peers/open_request_violation.js +4 -36
- package/routing/get_ignores.js +39 -63
- package/routing/parse_fee_rate_formula.js +27 -28
- package/swaps/append_failing_edge.js +108 -0
- package/swaps/manage_rebalance.js +39 -1
- package/swaps/rebalance.js +2 -0
- package/test/balances/test_is_matching_filters.js +1 -1
- package/test/peers/test_find_tag_match.js +7 -1
- package/test/routing/test_parse_fee_rate_formula.js +3 -3
- package/test/swaps/test_append_failing_edge.js +127 -0
- package/display/describe_parse_error.js +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 23.1.0
|
|
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
|
+
|
|
8
|
+
## 23.0.0
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
- Remove support for extended range of formulas, supported formulas are here:
|
|
13
|
+
https://github.com/alexbosworth/formulas#custom-functions
|
|
14
|
+
|
|
3
15
|
## 22.1.7
|
|
4
16
|
|
|
5
17
|
- Add support for LND 0.21.1
|
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.
|
|
11
|
+
- v0.20.0-beta to v0.20.2-beta
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -564,8 +564,11 @@ alias bosd="docker run -d --rm -v $HOME/.bos:/home/node/.bos alexbosworth/balanc
|
|
|
564
564
|
Some commands take formula arguments. Formulas are expressions that allow you
|
|
565
565
|
to perform functions and reference variables.
|
|
566
566
|
|
|
567
|
-
|
|
568
|
-
|
|
567
|
+
Formulas are modeled after common spreadsheet formulas, but there is not a 1:1
|
|
568
|
+
mapping of supported functions from all spreadsheets.
|
|
569
|
+
|
|
570
|
+
See supported formula construction here:
|
|
571
|
+
https://github.com/alexbosworth/formulas#custom-functions
|
|
569
572
|
|
|
570
573
|
### `--avoid`
|
|
571
574
|
|
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,
|
package/display/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const chartAliasForPeer = require('./chart_alias_for_peer');
|
|
2
2
|
const describeConfidence = require('./describe_confidence');
|
|
3
|
-
const describeParseError = require('./describe_parse_error');
|
|
4
3
|
const describeRoute = require('./describe_route');
|
|
5
4
|
const describeRoutingFailure = require('./describe_routing_failure');
|
|
6
5
|
const formatFeeRate = require('./format_fee_rate');
|
|
@@ -12,7 +11,6 @@ const sumsForSegment = require('./sums_for_segment');
|
|
|
12
11
|
module.exports = {
|
|
13
12
|
chartAliasForPeer,
|
|
14
13
|
describeConfidence,
|
|
15
|
-
describeParseError,
|
|
16
14
|
describeRoute,
|
|
17
15
|
describeRoutingFailure,
|
|
18
16
|
formatFeeRate,
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
const describeParseError = require('./describe_parse_error');
|
|
1
|
+
const {evaluateFormula} = require('@alexbosworth/formulas');
|
|
4
2
|
|
|
5
3
|
const {assign} = Object;
|
|
6
4
|
const defaultVariables = {btc: 1e8, k: 1e3, m: 1e6, mm: 1e6};
|
|
7
|
-
const {keys} = Object;
|
|
8
5
|
|
|
9
6
|
/** Determine if variables are consistent with filters
|
|
10
7
|
|
|
@@ -32,25 +29,14 @@ module.exports = ({filters, variables}) => {
|
|
|
32
29
|
|
|
33
30
|
const vars = {};
|
|
34
31
|
|
|
35
|
-
[defaultVariables, variables].forEach(
|
|
32
|
+
[defaultVariables, variables].forEach(variable => assign(vars, variable));
|
|
36
33
|
|
|
37
34
|
const filtered = filters.map(formula => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
parser.setVariable(key.toUpperCase(), vars[key]);
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const parsed = parser.parse(formula);
|
|
48
|
-
|
|
49
|
-
if (!!parsed.error) {
|
|
50
|
-
return {formula, error: describeParseError({error: parsed.error})};
|
|
35
|
+
try {
|
|
36
|
+
return !evaluateFormula({constants: vars, formula}).result;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return {formula, error: err.message};
|
|
51
39
|
}
|
|
52
|
-
|
|
53
|
-
return parsed.result === false;
|
|
54
40
|
});
|
|
55
41
|
|
|
56
42
|
const [errored] = filtered.filter(n => !!n.error);
|
package/network/execute_probe.js
CHANGED
|
@@ -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
|
|
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
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"@alexbosworth/blockchain": "3.2.0",
|
|
14
14
|
"@alexbosworth/caporal": "2.0.0",
|
|
15
15
|
"@alexbosworth/fiat": "2.0.0",
|
|
16
|
+
"@alexbosworth/formulas": "1.4.0",
|
|
16
17
|
"@alexbosworth/html2unicode": "1.1.5",
|
|
17
18
|
"@alexbosworth/node-fetch": "2.6.2",
|
|
18
19
|
"abort-controller": "3.0.0",
|
|
@@ -29,19 +30,18 @@
|
|
|
29
30
|
"colorette": "2.0.20",
|
|
30
31
|
"crypto-js": "4.2.0",
|
|
31
32
|
"csv-parse": "7.0.1",
|
|
32
|
-
"goldengate": "16.0.
|
|
33
|
-
"grammy": "1.
|
|
34
|
-
"hot-formula-parser": "4.0.0",
|
|
33
|
+
"goldengate": "16.0.4",
|
|
34
|
+
"grammy": "1.45.1",
|
|
35
35
|
"import-lazy": "4.0.0",
|
|
36
36
|
"ini": "7.0.0",
|
|
37
37
|
"inquirer": "14.0.2",
|
|
38
|
-
"ln-accounting": "10.0.
|
|
39
|
-
"ln-service": "59.2.
|
|
38
|
+
"ln-accounting": "10.0.4",
|
|
39
|
+
"ln-service": "59.2.3",
|
|
40
40
|
"ln-sync": "8.0.3",
|
|
41
|
-
"ln-telegram": "8.0.
|
|
41
|
+
"ln-telegram": "8.0.4",
|
|
42
42
|
"minimist": "1.2.8",
|
|
43
43
|
"moment": "2.30.1",
|
|
44
|
-
"paid-services": "8.0.
|
|
44
|
+
"paid-services": "8.0.4",
|
|
45
45
|
"probing": "7.0.0",
|
|
46
46
|
"qrcode-terminal": "0.12.0",
|
|
47
47
|
"sanitize-filename": "1.6.4",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"integration-tests": "node test/integration",
|
|
79
79
|
"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",
|
|
80
80
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
81
|
-
"test": "
|
|
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": "
|
|
83
|
+
"version": "23.1.0"
|
|
84
84
|
}
|
package/peers/find_tag_match.js
CHANGED
|
@@ -12,6 +12,7 @@ const tok = n => Number(BigInt(n) / BigInt(1e3));
|
|
|
12
12
|
|
|
13
13
|
{
|
|
14
14
|
channels: [{
|
|
15
|
+
capacity: <Channel Token Capacity Number>
|
|
15
16
|
id: <Standard Format Channel Id String>
|
|
16
17
|
local_balance: <Channel Local Balance Tokens Number>
|
|
17
18
|
partner_public_key: <Peer Public Key Hex String>
|
|
@@ -97,20 +98,25 @@ module.exports = ({channels, filters, policies, tags, query}) => {
|
|
|
97
98
|
const feeRates = peerPolicies.filter(n => n.fee_rate !== undefined);
|
|
98
99
|
const pendingPayments = withPeer.map(n => n.pending_payments.length);
|
|
99
100
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
const maxBaseFee = max(...feeRates.map(n => tok(n.base_fee_mtokens)));
|
|
102
|
+
const maxFeeRate = max(...feeRates.map(n => n.fee_rate));
|
|
103
|
+
|
|
104
|
+
const variables = {
|
|
105
|
+
capacity: sumOf(withPeer.map(n => n.capacity)),
|
|
106
|
+
heights: withPeer.map(n => {
|
|
107
|
+
return decodeChanId({channel: n.id}).block_height;
|
|
108
|
+
}),
|
|
109
|
+
inbound_liquidity: sumOf(withPeer.map(n => n.remote_balance)),
|
|
110
|
+
outbound_liquidity: sumOf(withPeer.map(n => n.local_balance)),
|
|
111
|
+
pending_payments: sumOf(pendingPayments),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (!!feeRates.length) {
|
|
115
|
+
variables.inbound_base_fee = maxBaseFee;
|
|
116
|
+
variables.inbound_fee_rate = maxFeeRate;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const matching = isMatchingFilters({variables, filters: filters || []});
|
|
114
120
|
|
|
115
121
|
if (!!matching.failure) {
|
|
116
122
|
return matching;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {evaluateFormula} = require('@alexbosworth/formulas');
|
|
2
2
|
|
|
3
3
|
const {isArray} = Array;
|
|
4
4
|
const {keys} = Object;
|
|
@@ -72,7 +72,7 @@ module.exports = args => {
|
|
|
72
72
|
throw new Error('ExpectedTorStatusToCheckOpenRequestRules');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const constants = {
|
|
76
76
|
btc: 1e8,
|
|
77
77
|
capacities: args.capacities,
|
|
78
78
|
capacity: args.capacity,
|
|
@@ -84,46 +84,14 @@ module.exports = args => {
|
|
|
84
84
|
local_balance: args.local_balance,
|
|
85
85
|
m: 1e6,
|
|
86
86
|
mm: 1e6,
|
|
87
|
-
obsolete: args.is_obsolete,
|
|
87
|
+
obsolete: !!args.is_obsolete,
|
|
88
88
|
private: args.is_private,
|
|
89
89
|
public_key: args.public_key,
|
|
90
90
|
tor: args.is_tor,
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
const parser = new Parser();
|
|
94
|
-
|
|
95
|
-
// Add the variables to the parser
|
|
96
|
-
keys(variables).forEach(key => {
|
|
97
|
-
parser.setVariable(key.toLowerCase(), variables[key]);
|
|
98
|
-
parser.setVariable(key.toUpperCase(), variables[key]);
|
|
99
|
-
|
|
100
|
-
return;
|
|
101
|
-
});
|
|
102
|
-
|
|
103
93
|
const violation = args.rules.find(rule => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
switch (parsed.error) {
|
|
107
|
-
case '#DIV/0!':
|
|
108
|
-
throw new Error('CannotDivideByZeroInOpenRequestRule');
|
|
109
|
-
|
|
110
|
-
case '#ERROR!':
|
|
111
|
-
throw new Error('FailedToParseSpecifiedOpenRequestRule');
|
|
112
|
-
|
|
113
|
-
case '#N/A':
|
|
114
|
-
case '#NAME?':
|
|
115
|
-
throw new Error('UnrecognizedVariableOrFunctionInRequestRule');
|
|
116
|
-
|
|
117
|
-
case '#NUM':
|
|
118
|
-
throw new Error('InvalidNumberFoundInRequestRule');
|
|
119
|
-
|
|
120
|
-
case '#VALUE!':
|
|
121
|
-
throw new Error('UnexpectedValueTypeInRequestRule');
|
|
122
|
-
|
|
123
|
-
default:
|
|
124
|
-
// Rules must evaluate as truthy
|
|
125
|
-
return !parsed.result;
|
|
126
|
-
}
|
|
94
|
+
return !evaluateFormula({constants, formula: rule}).result;
|
|
127
95
|
});
|
|
128
96
|
|
|
129
97
|
return {rule: violation};
|
package/routing/get_ignores.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncMap = require('async/map');
|
|
3
|
+
const {evaluateFormula} = require('@alexbosworth/formulas');
|
|
3
4
|
const {findKey} = require('ln-sync');
|
|
4
5
|
const {getChannel} = require('ln-service');
|
|
5
6
|
const {getHeight} = require('ln-service');
|
|
6
7
|
const {getNode} = require('ln-service');
|
|
7
|
-
const {Parser} = require('hot-formula-parser');
|
|
8
8
|
const {returnResult} = require('asyncjs-util');
|
|
9
9
|
|
|
10
|
-
const {describeParseError} = require('./../display');
|
|
11
|
-
|
|
12
10
|
const amountVariables = {btc: 1e8, k: 1e3, m: 1e6, mm: 1e6};
|
|
13
11
|
const asFormula = n => ({formula: n.slice(0, n.length-67), key: n.slice(-66)});
|
|
14
12
|
const asOutFilter = n => ({out_filter: n.slice(67), key: n.slice(0, 66)});
|
|
15
|
-
const {assign} = Object;
|
|
16
13
|
const channelFromEdge = edge => edge.slice(0, -2);
|
|
17
14
|
const decodePair = n => n.split('/');
|
|
18
15
|
const flatten = arr => [].concat(...arr);
|
|
@@ -25,7 +22,6 @@ const isFormula = n => /(.*)\/0[2-3][0-9A-F]{64}$/gim.test(n);
|
|
|
25
22
|
const isOutFilter = n => /^0[2-3][0-9A-F]{64}\/(.*)/gim.test(n);
|
|
26
23
|
const isPair = n => !!n && /^0[2-3][0-9A-F]{64}\/0[2-3][0-9A-F]{64}$/i.test(n);
|
|
27
24
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/gim.test(n);
|
|
28
|
-
const {keys} = Object;
|
|
29
25
|
const pairAsIgnore = (a, b) => ({from_public_key: a, to_public_key: b});
|
|
30
26
|
const uniq = arr => Array.from(new Set(arr));
|
|
31
27
|
|
|
@@ -255,35 +251,25 @@ module.exports = (args, cbk) => {
|
|
|
255
251
|
return;
|
|
256
252
|
}
|
|
257
253
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return;
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
const parsed = parser.parse(formula);
|
|
280
|
-
|
|
281
|
-
if (!!parsed.error) {
|
|
282
|
-
return {error: describeParseError({error: parsed.error})};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (parsed.result === false) {
|
|
286
|
-
return;
|
|
254
|
+
try {
|
|
255
|
+
const parsed = evaluateFormula({
|
|
256
|
+
formula,
|
|
257
|
+
constants: {
|
|
258
|
+
...amountVariables,
|
|
259
|
+
capacity,
|
|
260
|
+
height,
|
|
261
|
+
age: getHeight.current_block_height - height,
|
|
262
|
+
base_fee: Number(outPolicy.base_fee_mtokens) || Number(),
|
|
263
|
+
fee_rate: outPolicy.fee_rate || Number(),
|
|
264
|
+
opposite_fee_rate: peerPolicy.fee_rate || Number(),
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!parsed.result) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
return {error: err.message};
|
|
287
273
|
}
|
|
288
274
|
|
|
289
275
|
return {
|
|
@@ -333,35 +319,25 @@ module.exports = (args, cbk) => {
|
|
|
333
319
|
return;
|
|
334
320
|
}
|
|
335
321
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return;
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const parsed = parser.parse(formula);
|
|
358
|
-
|
|
359
|
-
if (!!parsed.error) {
|
|
360
|
-
return {error: describeParseError({error: parsed.error})};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (parsed.result === false) {
|
|
364
|
-
return;
|
|
322
|
+
try {
|
|
323
|
+
const parsed = evaluateFormula({
|
|
324
|
+
formula,
|
|
325
|
+
constants: {
|
|
326
|
+
...amountVariables,
|
|
327
|
+
capacity,
|
|
328
|
+
height,
|
|
329
|
+
age: getHeight.current_block_height - height,
|
|
330
|
+
base_fee: Number(inPolicy.base_fee_mtokens) || Number(),
|
|
331
|
+
fee_rate: inPolicy.fee_rate || Number(),
|
|
332
|
+
opposite_fee_rate: outPolicy.fee_rate || Number(),
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (!parsed.result) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
return {error: err.message};
|
|
365
341
|
}
|
|
366
342
|
|
|
367
343
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {evaluateFormula} = require('@alexbosworth/formulas');
|
|
2
2
|
|
|
3
3
|
const bipsAsPpm = bips => bips * 1e2;
|
|
4
4
|
const {ceil} = Math;
|
|
@@ -12,7 +12,7 @@ const percentAsPpm = percent => percent * 1e4;
|
|
|
12
12
|
inbound_liquidity: <Inbound Tokens Number>
|
|
13
13
|
outbound_liquidity: <Outbound Tokens Number>
|
|
14
14
|
node_rates: [{
|
|
15
|
-
key: <Node Key String>
|
|
15
|
+
key: <Label Prefixed Node Key String>
|
|
16
16
|
rate: <Node PPM Rate Number>
|
|
17
17
|
}]
|
|
18
18
|
}
|
|
@@ -28,31 +28,30 @@ module.exports = args => {
|
|
|
28
28
|
return {};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
const rates = args.node_rates.reduce((sum, n) => {
|
|
32
|
+
sum[n.key] = n.rate;
|
|
33
|
+
|
|
34
|
+
return sum;
|
|
35
|
+
},
|
|
36
|
+
{});
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const {result} = evaluateFormula({
|
|
40
|
+
constants: {
|
|
41
|
+
...rates,
|
|
42
|
+
inbound: args.inbound_liquidity,
|
|
43
|
+
inbound_fee_rate: args.inbound_fee_rate,
|
|
44
|
+
outbound: args.outbound_liquidity,
|
|
45
|
+
},
|
|
46
|
+
formula: args.fee_rate,
|
|
47
|
+
functions: {
|
|
48
|
+
bips: bipsAsPpm,
|
|
49
|
+
percent: percentAsPpm,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {rate: ceil(result)};
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return {failure: err.message};
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
-
return {rate: ceil(parsedRate.result)};
|
|
58
57
|
};
|
|
@@ -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,
|
package/swaps/rebalance.js
CHANGED
|
@@ -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,
|
|
@@ -7,6 +7,7 @@ const {findTagMatch} = require('./../../peers');
|
|
|
7
7
|
const makeArgs = overrides => {
|
|
8
8
|
const args = {
|
|
9
9
|
channels: [{
|
|
10
|
+
capacity: 3,
|
|
10
11
|
id: '1x1x1',
|
|
11
12
|
local_balance: 1,
|
|
12
13
|
partner_public_key: Buffer.alloc(33, 3).toString('hex'),
|
|
@@ -36,13 +37,17 @@ const tests = [
|
|
|
36
37
|
args: makeArgs({filters: ['invalid formula']}),
|
|
37
38
|
description: 'A failed formula is provided',
|
|
38
39
|
expected: {
|
|
39
|
-
failure: {
|
|
40
|
+
failure: {
|
|
41
|
+
error: 'UnexpectedTrailingTokenForFormulaParsing',
|
|
42
|
+
formula: 'invalid formula',
|
|
43
|
+
},
|
|
40
44
|
},
|
|
41
45
|
},
|
|
42
46
|
{
|
|
43
47
|
args: makeArgs({
|
|
44
48
|
channels: [
|
|
45
49
|
{
|
|
50
|
+
capacity: 2e8,
|
|
46
51
|
id: '1x1x1',
|
|
47
52
|
local_balance: 1e3,
|
|
48
53
|
partner_public_key: Buffer.alloc(33, 3).toString('hex'),
|
|
@@ -50,6 +55,7 @@ const tests = [
|
|
|
50
55
|
remote_balance: 1,
|
|
51
56
|
},
|
|
52
57
|
{
|
|
58
|
+
capacity: 2e8,
|
|
53
59
|
id: '2x2x2',
|
|
54
60
|
local_balance: 1e8,
|
|
55
61
|
partner_public_key: Buffer.alloc(33, 2).toString('hex'),
|
|
@@ -42,17 +42,17 @@ const tests = [
|
|
|
42
42
|
{
|
|
43
43
|
args: makeArgs({fee_rate: '1/0'}),
|
|
44
44
|
description: 'Cannot divide by zero',
|
|
45
|
-
expected: {failure: '
|
|
45
|
+
expected: {failure: 'ExpectedNonZeroDivisorForFormulaEvaluation'},
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
args: makeArgs({fee_rate: '/'}),
|
|
49
49
|
description: 'Formula must be valid',
|
|
50
|
-
expected: {failure: '
|
|
50
|
+
expected: {failure: 'UnexpectedPrimaryTokenForFormulaParsing'},
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
args: makeArgs({fee_rate: 'fee_rate'}),
|
|
54
54
|
description: 'Formula must be valid',
|
|
55
|
-
expected: {failure: '
|
|
55
|
+
expected: {failure: 'ExpectedAllKnownConstantsInFormulaToEvaluate'},
|
|
56
56
|
},
|
|
57
57
|
];
|
|
58
58
|
|
|
@@ -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
|
+
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const {parseErrors} = require('./constants');
|
|
2
|
-
|
|
3
|
-
const defaultError = '#ERROR!';
|
|
4
|
-
|
|
5
|
-
/** Describe a hot formula parser error
|
|
6
|
-
|
|
7
|
-
{
|
|
8
|
-
error: <Error Type String>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
@returns
|
|
12
|
-
<Display String>
|
|
13
|
-
*/
|
|
14
|
-
module.exports = ({error}) => {
|
|
15
|
-
return parseErrors[error] || parseErrors[defaultError];
|
|
16
|
-
};
|