balanceofsatoshis 23.1.8 → 23.1.10
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 +9 -0
- package/README.md +2 -2
- package/bos +2 -2
- package/package.json +8 -8
- package/peers/find_peer_match.js +109 -0
- package/peers/find_tag_match.js +22 -78
- package/swaps/find_rebalance_peer.js +149 -0
- package/swaps/rebalance.js +20 -73
- package/test/peers/test_find_tag_match.js +8 -1
- package/test/swaps/test_find_rebalance_peer.js +329 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
package/bos
CHANGED
|
@@ -1735,7 +1735,7 @@ prog
|
|
|
1735
1735
|
.option('--avoid-high-fee-routes', 'Avoid trying routes above max-fee-rate')
|
|
1736
1736
|
.option('--avoid-list', 'Path to file with newline separated list of avoids')
|
|
1737
1737
|
.option('--in <pubkey_or_alias>', 'Route in through a specific peer')
|
|
1738
|
-
.option('--in-filter <in_filter>', 'Filter inbound tag
|
|
1738
|
+
.option('--in-filter <in_filter>', 'Filter inbound peer/tag', REPEATABLE)
|
|
1739
1739
|
.option('--in-target-outbound <amt>', 'Balance up to outbound amount')
|
|
1740
1740
|
.option('--max-fee <max_fee>', 'Maximum fee to pay')
|
|
1741
1741
|
.option('--max-fee-rate <max_fee_rate>', 'Max fee rate to pay')
|
|
@@ -1743,7 +1743,7 @@ prog
|
|
|
1743
1743
|
.option('--no-color', 'Mute all colors')
|
|
1744
1744
|
.option('--node <node_name>', 'Saved node to use for rebalance')
|
|
1745
1745
|
.option('--out <pubkey_or_alias>', 'Route out through a specific peer')
|
|
1746
|
-
.option('--out-filter <out_filter>', 'Filter outbound tag
|
|
1746
|
+
.option('--out-filter <out_filter>', 'Filter outbound peer/tag', REPEATABLE)
|
|
1747
1747
|
.option('--out-target-inbound <amount>', 'Balance up to inbound amount')
|
|
1748
1748
|
.action((args, options, logger) => {
|
|
1749
1749
|
return new Promise(async (resolve, reject) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"allowScripts": {
|
|
3
|
-
"protobufjs@7.6.
|
|
3
|
+
"protobufjs@7.6.6": true,
|
|
4
4
|
"ssh2@1.17.0": true,
|
|
5
5
|
"cpu-features@0.0.10": true
|
|
6
6
|
},
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"url": "https://github.com/alexbosworth/balanceofsatoshis/issues"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@alexbosworth/blockchain": "4.
|
|
18
|
+
"@alexbosworth/blockchain": "4.7.2",
|
|
19
19
|
"@alexbosworth/caporal": "2.0.0",
|
|
20
20
|
"@alexbosworth/fiat": "2.0.0",
|
|
21
21
|
"@alexbosworth/formulas": "1.4.0",
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"colorette": "2.0.20",
|
|
36
36
|
"csv-parse": "7.0.2",
|
|
37
37
|
"goldengate": "16.0.6",
|
|
38
|
-
"grammy": "1.
|
|
38
|
+
"grammy": "1.46.0",
|
|
39
39
|
"import-lazy": "4.0.0",
|
|
40
40
|
"ini": "7.0.0",
|
|
41
|
-
"inquirer": "14.
|
|
41
|
+
"inquirer": "14.2.0",
|
|
42
42
|
"ln-accounting": "10.0.5",
|
|
43
|
-
"ln-service": "59.3.
|
|
44
|
-
"ln-sync": "8.0.
|
|
43
|
+
"ln-service": "59.3.7",
|
|
44
|
+
"ln-sync": "8.0.6",
|
|
45
45
|
"ln-telegram": "8.0.5",
|
|
46
46
|
"moment": "2.30.1",
|
|
47
47
|
"paid-services": "8.0.6",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"description": "Lightning balance CLI",
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"invoices": "6.1.
|
|
58
|
+
"invoices": "6.1.4",
|
|
59
59
|
"ln-docker-daemons": "8.0.4"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
84
84
|
"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"
|
|
85
85
|
},
|
|
86
|
-
"version": "23.1.
|
|
86
|
+
"version": "23.1.10"
|
|
87
87
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const {decodeChanId} = require('bolt07');
|
|
2
|
+
|
|
3
|
+
const {shuffle} = require('./../arrays');
|
|
4
|
+
const {isMatchingFilters} = require('./../display');
|
|
5
|
+
|
|
6
|
+
const channelHeight = channel => decodeChanId({channel}).block_height;
|
|
7
|
+
const {max} = Math;
|
|
8
|
+
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
9
|
+
const tok = n => Number(BigInt(n) / BigInt(1e3));
|
|
10
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
11
|
+
|
|
12
|
+
/** Find a peer that matches filters
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
channels: [{
|
|
16
|
+
capacity: <Channel Token Capacity Number>
|
|
17
|
+
id: <Standard Format Channel Id String>
|
|
18
|
+
local_balance: <Channel Local Balance Tokens Number>
|
|
19
|
+
partner_public_key: <Peer Public Key Hex String>
|
|
20
|
+
pending_payments: [<Pending Payment Object>]
|
|
21
|
+
remote_balance: <Channel Remote Balance Tokens Number>
|
|
22
|
+
}]
|
|
23
|
+
[filters]: [<Filter Expression String>]
|
|
24
|
+
policies: [{
|
|
25
|
+
[base_fee_mtokens]: <Remote Base Fee Charged In Millitokens Number>
|
|
26
|
+
[fee_rate]: <Remote Fees Charged in Millitokens Per Million Number>
|
|
27
|
+
[is_disabled]: <Remote Channel Forwarding Is Disabled Bool>
|
|
28
|
+
public_key: <Remote Public Key Hex String>
|
|
29
|
+
}]
|
|
30
|
+
nodes: [<Candidate Peer Public Key Hex String>]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@returns
|
|
34
|
+
{
|
|
35
|
+
[failure]: {
|
|
36
|
+
error: <Error String>
|
|
37
|
+
formula: <Errored Formula String>
|
|
38
|
+
}
|
|
39
|
+
[match]: <Matching Peer Public Key Hex String>
|
|
40
|
+
}
|
|
41
|
+
*/
|
|
42
|
+
module.exports = ({channels, filters, nodes, policies}) => {
|
|
43
|
+
const disabled = policies.filter(n => n.is_disabled).map(n => n.public_key);
|
|
44
|
+
|
|
45
|
+
const peers = channels.map(n => n.partner_public_key)
|
|
46
|
+
.filter(n => !disabled.includes(n));
|
|
47
|
+
|
|
48
|
+
const candidates = uniq((nodes || [])
|
|
49
|
+
.filter(n => peers.includes(n)));
|
|
50
|
+
|
|
51
|
+
// Filter out peers that do not fulfill the supplied criteria
|
|
52
|
+
const candidateResults = candidates
|
|
53
|
+
.map(key => {
|
|
54
|
+
if (!filters || !filters.length) {
|
|
55
|
+
return {match: key};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const peerPolicies = policies.filter(n => n.public_key === key);
|
|
59
|
+
const withPeer = channels.filter(n => n.partner_public_key === key);
|
|
60
|
+
|
|
61
|
+
const feeRates = peerPolicies.filter(n => n.fee_rate !== undefined);
|
|
62
|
+
const pendingPayments = withPeer.map(n => n.pending_payments.length);
|
|
63
|
+
|
|
64
|
+
const maxBaseFee = max(...feeRates.map(n => tok(n.base_fee_mtokens)));
|
|
65
|
+
const maxFeeRate = max(...feeRates.map(n => n.fee_rate));
|
|
66
|
+
|
|
67
|
+
const variables = {
|
|
68
|
+
capacity: sumOf(withPeer.map(n => n.capacity)),
|
|
69
|
+
heights: withPeer.map(n => channelHeight(n.id)),
|
|
70
|
+
inbound_liquidity: sumOf(withPeer.map(n => n.remote_balance)),
|
|
71
|
+
outbound_liquidity: sumOf(withPeer.map(n => n.local_balance)),
|
|
72
|
+
pending_payments: sumOf(pendingPayments),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (!!feeRates.length) {
|
|
76
|
+
variables.inbound_base_fee = maxBaseFee;
|
|
77
|
+
variables.inbound_fee_rate = maxFeeRate;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const matching = isMatchingFilters({variables, filters: filters || []});
|
|
81
|
+
|
|
82
|
+
if (!!matching.failure) {
|
|
83
|
+
return matching;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!matching.is_matching) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {match: key};
|
|
91
|
+
})
|
|
92
|
+
.filter(n => !!n);
|
|
93
|
+
|
|
94
|
+
// Exit early when there is no match
|
|
95
|
+
if (!candidateResults.length) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Exit early when there is a failure in a candidate
|
|
100
|
+
if (!!candidateResults.find(n => !!n.failure)) {
|
|
101
|
+
return candidateResults.find(n => !!n.failure);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const {shuffled} = shuffle({array: candidateResults});
|
|
105
|
+
|
|
106
|
+
const [{match}] = shuffled;
|
|
107
|
+
|
|
108
|
+
return {match};
|
|
109
|
+
};
|
package/peers/find_tag_match.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const flatten = arr => [].concat(...arr);
|
|
4
|
-
const {shuffle} = require('./../arrays');
|
|
5
|
-
const {isMatchingFilters} = require('./../display');
|
|
6
|
-
|
|
7
|
-
const {max} = Math;
|
|
8
|
-
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
9
|
-
const tok = n => Number(BigInt(n) / BigInt(1e3));
|
|
1
|
+
const findPeerMatch = require('./find_peer_match');
|
|
10
2
|
|
|
11
3
|
/** Find a node for a tag query
|
|
12
4
|
|
|
@@ -40,6 +32,7 @@ const tok = n => Number(BigInt(n) / BigInt(1e3));
|
|
|
40
32
|
formula: <Errored Formula String>
|
|
41
33
|
}
|
|
42
34
|
[match]: <Matching Node Public Key Hex String>
|
|
35
|
+
[is_tag_filtered]: <Tag Matched But All Nodes Failed Filters Bool>
|
|
43
36
|
[matches]: [{
|
|
44
37
|
[alias]: <Tag Alias String>
|
|
45
38
|
id: <Tag Id Hex String>
|
|
@@ -54,14 +47,8 @@ module.exports = ({channels, filters, policies, tags, query}) => {
|
|
|
54
47
|
.map(n => n.partner_public_key)
|
|
55
48
|
.filter(n => !disabled.includes(n));
|
|
56
49
|
|
|
57
|
-
// Find tags that match on id or on alias
|
|
58
|
-
const
|
|
59
|
-
const nodes = (tag.nodes || []).filter(n => peerKeys.includes(n));
|
|
60
|
-
|
|
61
|
-
if (!nodes.length) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
50
|
+
// Find tags that match on id or on alias
|
|
51
|
+
const tagMatches = tags.filter(tag => {
|
|
65
52
|
const alias = tag.alias || String();
|
|
66
53
|
|
|
67
54
|
const isAliasMatch = alias.toLowerCase() === (query || '').toLowerCase();
|
|
@@ -70,9 +57,18 @@ module.exports = ({channels, filters, policies, tags, query}) => {
|
|
|
70
57
|
return isAliasMatch || isIdMatch;
|
|
71
58
|
});
|
|
72
59
|
|
|
60
|
+
// Limit tag matches to tags with relevant peers
|
|
61
|
+
const matches = tagMatches.filter(tag => {
|
|
62
|
+
return (tag.nodes || []).some(n => peerKeys.includes(n));
|
|
63
|
+
});
|
|
64
|
+
|
|
73
65
|
const [tagMatch, ...otherTagMatches] = matches;
|
|
74
66
|
|
|
75
67
|
// Exit early when there are no matches at all
|
|
68
|
+
if (!tagMatch && !!tagMatches.length && !!filters && !!filters.length) {
|
|
69
|
+
return {is_tag_filtered: true};
|
|
70
|
+
}
|
|
71
|
+
|
|
76
72
|
if (!tagMatch) {
|
|
77
73
|
return {};
|
|
78
74
|
}
|
|
@@ -82,68 +78,16 @@ module.exports = ({channels, filters, policies, tags, query}) => {
|
|
|
82
78
|
return {matches};
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (!filters || !filters.length) {
|
|
92
|
-
return {match: key};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const peerPolicies = policies.filter(n => n.public_key === key);
|
|
96
|
-
const withPeer = channels.filter(n => n.partner_public_key === key);
|
|
97
|
-
|
|
98
|
-
const feeRates = peerPolicies.filter(n => n.fee_rate !== undefined);
|
|
99
|
-
const pendingPayments = withPeer.map(n => n.pending_payments.length);
|
|
100
|
-
|
|
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 || []});
|
|
120
|
-
|
|
121
|
-
if (!!matching.failure) {
|
|
122
|
-
return matching;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!matching.is_matching) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {match: key};
|
|
130
|
-
})
|
|
131
|
-
.filter(n => !!n);
|
|
132
|
-
|
|
133
|
-
// Exit early when there is no match
|
|
134
|
-
if (!array.length) {
|
|
135
|
-
return {};
|
|
136
|
-
}
|
|
81
|
+
const {failure, match} = findPeerMatch({
|
|
82
|
+
channels,
|
|
83
|
+
filters,
|
|
84
|
+
policies,
|
|
85
|
+
nodes: tagMatch.nodes.filter(n => peerKeys.includes(n)),
|
|
86
|
+
});
|
|
137
87
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return array.find(n => !!n.failure);
|
|
88
|
+
if (!failure && !match && !!filters && !!filters.length) {
|
|
89
|
+
return {is_tag_filtered: true};
|
|
141
90
|
}
|
|
142
91
|
|
|
143
|
-
|
|
144
|
-
const {shuffled} = shuffle({array});
|
|
145
|
-
|
|
146
|
-
const [{match}] = shuffled;
|
|
147
|
-
|
|
148
|
-
return {match};
|
|
92
|
+
return {failure, match};
|
|
149
93
|
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {findKey} = require('ln-sync');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const findPeerMatch = require('./../peers/find_peer_match');
|
|
6
|
+
const findTagMatch = require('./../peers/find_tag_match');
|
|
7
|
+
|
|
8
|
+
/** Find a peer for a rebalance
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
active_channels: [{
|
|
12
|
+
capacity: <Channel Token Capacity Number>
|
|
13
|
+
id: <Standard Format Channel Id String>
|
|
14
|
+
local_balance: <Channel Local Balance Tokens Number>
|
|
15
|
+
partner_public_key: <Peer Public Key Hex String>
|
|
16
|
+
pending_payments: [<Pending Payment Object>]
|
|
17
|
+
remote_balance: <Channel Remote Balance Tokens Number>
|
|
18
|
+
}]
|
|
19
|
+
channels: [{
|
|
20
|
+
partner_public_key: <Peer Public Key Hex String>
|
|
21
|
+
}]
|
|
22
|
+
direction: <In or Out Direction String>
|
|
23
|
+
[filters]: [<Peer Filter Formula String>]
|
|
24
|
+
lnd: <Authenticated LND API Object>
|
|
25
|
+
policies: [{
|
|
26
|
+
[base_fee_mtokens]: <Remote Base Fee Charged In Millitokens Number>
|
|
27
|
+
[fee_rate]: <Remote Fees Charged in Millitokens Per Million Number>
|
|
28
|
+
[is_disabled]: <Remote Channel Forwarding Is Disabled Bool>
|
|
29
|
+
public_key: <Remote Public Key Hex String>
|
|
30
|
+
}]
|
|
31
|
+
[query]: <Tag, Public Key, or Alias String>
|
|
32
|
+
tags: [{
|
|
33
|
+
[alias]: <Tag Alias String>
|
|
34
|
+
id: <Tag Id Hex String>
|
|
35
|
+
[nodes]: [<Public Key Hex String>]
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@returns via cbk or Promise
|
|
40
|
+
{
|
|
41
|
+
[public_key]: <Peer Public Key Hex String>
|
|
42
|
+
}
|
|
43
|
+
*/
|
|
44
|
+
module.exports = (args, cbk) => {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
return asyncAuto({
|
|
47
|
+
// Check arguments
|
|
48
|
+
validate: cbk => {
|
|
49
|
+
if (args.direction !== 'in' && args.direction !== 'out') {
|
|
50
|
+
return cbk([400, 'ExpectedInOrOutDirectionToFindRebalancePeer']);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return cbk();
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Find a tag, preserving tag precedence over peer aliases
|
|
57
|
+
tag: ['validate', ({}, cbk) => {
|
|
58
|
+
const filters = args.filters || [];
|
|
59
|
+
|
|
60
|
+
if (!args.query && !!filters.length) {
|
|
61
|
+
if (args.direction === 'in') {
|
|
62
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyInboundFilter']);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const {failure, is_tag_filtered, match, matches} = findTagMatch({
|
|
69
|
+
filters,
|
|
70
|
+
channels: args.active_channels,
|
|
71
|
+
policies: args.policies,
|
|
72
|
+
query: args.query,
|
|
73
|
+
tags: args.tags,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!!failure) {
|
|
77
|
+
return cbk([400, 'FailedToParseFilter', failure]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!!matches) {
|
|
81
|
+
if (args.direction === 'in') {
|
|
82
|
+
return cbk([400, 'MultipleTagMatchesFoundForInPeer', {matches}]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return cbk([400, 'MultipleTagMatchesFoundForOutPeer', {matches}]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!!is_tag_filtered && args.direction === 'in') {
|
|
89
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyInboundFilter']);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!!is_tag_filtered) {
|
|
93
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!!match) {
|
|
97
|
+
return cbk(null, {public_key: match});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return cbk();
|
|
101
|
+
}],
|
|
102
|
+
|
|
103
|
+
// Resolve a public key or node alias when the query was not a tag
|
|
104
|
+
key: ['tag', ({tag}, cbk) => {
|
|
105
|
+
if (!!tag) {
|
|
106
|
+
return cbk(null, tag);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return findKey({
|
|
110
|
+
lnd: args.lnd,
|
|
111
|
+
channels: args.channels,
|
|
112
|
+
query: args.query,
|
|
113
|
+
},
|
|
114
|
+
cbk);
|
|
115
|
+
}],
|
|
116
|
+
|
|
117
|
+
// Apply filters to the single explicitly resolved peer
|
|
118
|
+
peer: ['key', 'tag', ({key, tag}, cbk) => {
|
|
119
|
+
const filters = args.filters || [];
|
|
120
|
+
|
|
121
|
+
if (!!tag || !filters.length) {
|
|
122
|
+
return cbk(null, key);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const {failure, match} = findPeerMatch({
|
|
126
|
+
filters,
|
|
127
|
+
channels: args.active_channels,
|
|
128
|
+
nodes: [key.public_key.toLowerCase()],
|
|
129
|
+
policies: args.policies,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (!!failure) {
|
|
133
|
+
return cbk([400, 'FailedToParseFilter', failure]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!match && args.direction === 'in') {
|
|
137
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyInboundFilter']);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!match) {
|
|
141
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return cbk(null, {public_key: match});
|
|
145
|
+
}],
|
|
146
|
+
},
|
|
147
|
+
returnResult({reject, resolve, of: 'peer'}, cbk));
|
|
148
|
+
});
|
|
149
|
+
};
|
package/swaps/rebalance.js
CHANGED
|
@@ -3,7 +3,6 @@ const asyncMap = require('async/map');
|
|
|
3
3
|
const asyncMapSeries = require('async/mapSeries');
|
|
4
4
|
const asyncRetry = require('async/retry');
|
|
5
5
|
const {createInvoice} = require('ln-service');
|
|
6
|
-
const {findKey} = require('ln-sync');
|
|
7
6
|
const {getChannel} = require('ln-service');
|
|
8
7
|
const {getChannels} = require('ln-service');
|
|
9
8
|
const {getHeight} = require('ln-service');
|
|
@@ -17,13 +16,14 @@ const {payViaRoutes} = require('ln-service');
|
|
|
17
16
|
const {returnResult} = require('asyncjs-util');
|
|
18
17
|
const {routeFromChannels} = require('ln-service');
|
|
19
18
|
|
|
20
|
-
const
|
|
19
|
+
const findRebalancePeer = require('./find_rebalance_peer');
|
|
21
20
|
const {formatFeeRate} = require('./../display');
|
|
22
21
|
const {getIgnores} = require('./../routing');
|
|
23
22
|
const {homePath} = require('../storage');
|
|
24
23
|
const {probeDestination} = require('./../network');
|
|
25
24
|
const {sortBy} = require('./../arrays');
|
|
26
25
|
|
|
26
|
+
const activeFilter = channels => channels.filter(n => !!n.is_active);
|
|
27
27
|
const asRate = (fee, tokens) => ({rate: Math.ceil(fee / tokens * 1e6)});
|
|
28
28
|
const {ceil} = Math;
|
|
29
29
|
const channelMatch = /^\d*x\d*x\d*$/;
|
|
@@ -216,18 +216,22 @@ module.exports = (args, cbk) => {
|
|
|
216
216
|
return getNode({lnd, public_key: getPublicKey.public_key}, cbk);
|
|
217
217
|
}],
|
|
218
218
|
|
|
219
|
-
// Find inbound
|
|
220
|
-
|
|
219
|
+
// Find inbound peer key
|
|
220
|
+
findInKey: [
|
|
221
221
|
'getFees',
|
|
222
222
|
'getInitialLiquidity',
|
|
223
223
|
'getPublicKey',
|
|
224
224
|
'getTags',
|
|
225
|
-
|
|
225
|
+
'lnd',
|
|
226
|
+
({getFees, getInitialLiquidity, getPublicKey, getTags, lnd}, cbk) =>
|
|
226
227
|
{
|
|
227
228
|
const id = getPublicKey.public_key;
|
|
228
229
|
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
return findRebalancePeer({
|
|
231
|
+
lnd,
|
|
232
|
+
active_channels: activeFilter(getInitialLiquidity.channels),
|
|
233
|
+
channels: getInitialLiquidity.channels,
|
|
234
|
+
direction: 'in',
|
|
231
235
|
filters: args.in_filters,
|
|
232
236
|
policies: getFees.channels.map(channel => {
|
|
233
237
|
const policy = channel.policies.find(n => n.public_key !== id);
|
|
@@ -239,41 +243,8 @@ module.exports = (args, cbk) => {
|
|
|
239
243
|
public_key: policy.public_key,
|
|
240
244
|
};
|
|
241
245
|
}),
|
|
242
|
-
tags: getTags.tags,
|
|
243
|
-
query: args.in_through,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// Exit early when there is a filter error
|
|
247
|
-
if (!!failure) {
|
|
248
|
-
return cbk([400, 'FailedToParseFilter', failure]);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!!matches) {
|
|
252
|
-
return cbk([400, 'MultipleTagMatchesFoundForInPeer', {matches}]);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!match && !!args.in_filters && !!args.in_filters.length) {
|
|
256
|
-
return cbk([400, 'NoPeerMatchesFoundToSatisfyInboundFilter']);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return cbk(null, match);
|
|
260
|
-
}],
|
|
261
|
-
|
|
262
|
-
// Find inbound peer key if a name is specified
|
|
263
|
-
findInKey: [
|
|
264
|
-
'findInTag',
|
|
265
|
-
'getInitialLiquidity',
|
|
266
|
-
'lnd',
|
|
267
|
-
({findInTag, getInitialLiquidity, lnd}, cbk) =>
|
|
268
|
-
{
|
|
269
|
-
if (!!findInTag) {
|
|
270
|
-
return cbk(null, findInTag);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return findKey({
|
|
274
|
-
lnd,
|
|
275
|
-
channels: getInitialLiquidity.channels,
|
|
276
246
|
query: args.in_through,
|
|
247
|
+
tags: getTags.tags,
|
|
277
248
|
},
|
|
278
249
|
(err, res) => {
|
|
279
250
|
if (!!err) {
|
|
@@ -286,14 +257,12 @@ module.exports = (args, cbk) => {
|
|
|
286
257
|
|
|
287
258
|
// Find outbound peer key if a name is specified
|
|
288
259
|
findOutKey: [
|
|
289
|
-
'findInTag',
|
|
290
260
|
'getFees',
|
|
291
261
|
'getInitialLiquidity',
|
|
292
262
|
'getPublicKey',
|
|
293
263
|
'getTags',
|
|
294
264
|
'lnd',
|
|
295
265
|
({
|
|
296
|
-
findInTag,
|
|
297
266
|
getFees,
|
|
298
267
|
getInitialLiquidity,
|
|
299
268
|
getPublicKey,
|
|
@@ -304,8 +273,11 @@ module.exports = (args, cbk) => {
|
|
|
304
273
|
{
|
|
305
274
|
const id = getPublicKey.public_key;
|
|
306
275
|
|
|
307
|
-
|
|
308
|
-
|
|
276
|
+
return findRebalancePeer({
|
|
277
|
+
lnd,
|
|
278
|
+
active_channels: activeFilter(getInitialLiquidity.channels),
|
|
279
|
+
channels: getInitialLiquidity.channels,
|
|
280
|
+
direction: 'out',
|
|
309
281
|
filters: args.out_filters,
|
|
310
282
|
policies: getFees.channels.map(channel => {
|
|
311
283
|
const policy = channel.policies.find(n => n.public_key !== id);
|
|
@@ -316,31 +288,8 @@ module.exports = (args, cbk) => {
|
|
|
316
288
|
public_key: policy.public_key,
|
|
317
289
|
};
|
|
318
290
|
}),
|
|
319
|
-
tags: getTags.tags,
|
|
320
|
-
query: args.out_through,
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// Exit early when there is a filter error
|
|
324
|
-
if (!!failure) {
|
|
325
|
-
return cbk([400, 'FailedToParseFilter', failure]);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (!!matches) {
|
|
329
|
-
return cbk([400, 'MultipleTagMatchesFoundForOutPeer', {matches}]);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (match) {
|
|
333
|
-
return cbk(null, match);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (!match && !!args.out_filters && !!args.out_filters.length) {
|
|
337
|
-
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return findKey({
|
|
341
|
-
lnd,
|
|
342
|
-
channels: getInitialLiquidity.channels,
|
|
343
291
|
query: args.out_through,
|
|
292
|
+
tags: getTags.tags,
|
|
344
293
|
},
|
|
345
294
|
(err, res) => {
|
|
346
295
|
if (!!err) {
|
|
@@ -371,8 +320,7 @@ module.exports = (args, cbk) => {
|
|
|
371
320
|
{
|
|
372
321
|
const ban = ignore.filter(n => !n.channel).map(n => n.from_public_key);
|
|
373
322
|
|
|
374
|
-
const active = getInitialLiquidity.channels
|
|
375
|
-
.filter(n => !!n.is_active)
|
|
323
|
+
const active = activeFilter(getInitialLiquidity.channels)
|
|
376
324
|
.filter(n => !ban.includes(n.partner_public_key));
|
|
377
325
|
|
|
378
326
|
const channels = active
|
|
@@ -435,8 +383,7 @@ module.exports = (args, cbk) => {
|
|
|
435
383
|
const ban = ignore.filter(n => !n.channel).map(n => n.from_public_key);
|
|
436
384
|
const hasInThrough = !!args.in_through;
|
|
437
385
|
|
|
438
|
-
const activeChannels = getInitialLiquidity.channels
|
|
439
|
-
.filter(n => !!n.is_active)
|
|
386
|
+
const activeChannels = activeFilter(getInitialLiquidity.channels)
|
|
440
387
|
.filter(n => n.partner_public_key !== getOutbound.public_key)
|
|
441
388
|
.filter(n => !ban.includes(n.partner_public_key));
|
|
442
389
|
|
|
@@ -31,7 +31,7 @@ const tests = [
|
|
|
31
31
|
{
|
|
32
32
|
args: makeArgs({}),
|
|
33
33
|
description: 'A tagged node is found',
|
|
34
|
-
expected: {match: Buffer.alloc(33, 3).toString('hex')},
|
|
34
|
+
expected: {failure: undefined, match: Buffer.alloc(33, 3).toString('hex')},
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
args: makeArgs({filters: ['invalid formula']}),
|
|
@@ -41,8 +41,14 @@ const tests = [
|
|
|
41
41
|
error: 'UnexpectedTrailingTokenForFormulaParsing',
|
|
42
42
|
formula: 'invalid formula',
|
|
43
43
|
},
|
|
44
|
+
match: undefined,
|
|
44
45
|
},
|
|
45
46
|
},
|
|
47
|
+
{
|
|
48
|
+
args: makeArgs({filters: ['outbound_liquidity > 1']}),
|
|
49
|
+
description: 'A tag with no peers satisfying a filter is identified',
|
|
50
|
+
expected: {is_tag_filtered: true},
|
|
51
|
+
},
|
|
46
52
|
{
|
|
47
53
|
args: makeArgs({
|
|
48
54
|
channels: [
|
|
@@ -74,6 +80,7 @@ const tests = [
|
|
|
74
80
|
}),
|
|
75
81
|
description: 'A tagged node is found which complies with a filter',
|
|
76
82
|
expected: {
|
|
83
|
+
failure: undefined,
|
|
77
84
|
match: Buffer.alloc(33, 2).toString('hex'),
|
|
78
85
|
},
|
|
79
86
|
},
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
const {deepEqual} = require('node:assert').strict;
|
|
2
|
+
const {rejects} = require('node:assert').strict;
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
|
|
5
|
+
const findRebalancePeer = require('./../../swaps/find_rebalance_peer');
|
|
6
|
+
|
|
7
|
+
const highKey = Buffer.alloc(33, 2).toString('hex');
|
|
8
|
+
const lowKey = Buffer.alloc(33, 3).toString('hex');
|
|
9
|
+
const inactiveKey = Buffer.alloc(33, 4).toString('hex');
|
|
10
|
+
const unknownKey = Buffer.alloc(33, 5).toString('hex');
|
|
11
|
+
const inboundFilters = ['inbound_liquidity > 10'];
|
|
12
|
+
|
|
13
|
+
const channels = [
|
|
14
|
+
{
|
|
15
|
+
capacity: 10,
|
|
16
|
+
id: '1x1x1',
|
|
17
|
+
local_balance: 4,
|
|
18
|
+
partner_public_key: highKey,
|
|
19
|
+
pending_payments: [],
|
|
20
|
+
remote_balance: 6,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
capacity: 10,
|
|
24
|
+
id: '2x2x2',
|
|
25
|
+
local_balance: 4,
|
|
26
|
+
partner_public_key: highKey,
|
|
27
|
+
pending_payments: [],
|
|
28
|
+
remote_balance: 6,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
capacity: 10,
|
|
32
|
+
id: '3x3x3',
|
|
33
|
+
local_balance: 2,
|
|
34
|
+
partner_public_key: lowKey,
|
|
35
|
+
pending_payments: [],
|
|
36
|
+
remote_balance: 8,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const makeLnd = aliases => ({
|
|
41
|
+
default: {
|
|
42
|
+
getNodeInfo: (args, cbk) => cbk(null, {
|
|
43
|
+
channels: [],
|
|
44
|
+
node: {
|
|
45
|
+
addresses: [],
|
|
46
|
+
alias: aliases[args.pub_key] || String(),
|
|
47
|
+
color: '#000000',
|
|
48
|
+
features: {},
|
|
49
|
+
last_update: 0,
|
|
50
|
+
pub_key: args.pub_key,
|
|
51
|
+
},
|
|
52
|
+
num_channels: 0,
|
|
53
|
+
total_capacity: '1',
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const makeArgs = overrides => {
|
|
59
|
+
const args = {
|
|
60
|
+
channels,
|
|
61
|
+
active_channels: channels,
|
|
62
|
+
direction: 'out',
|
|
63
|
+
filters: ['outbound_liquidity > 5'],
|
|
64
|
+
lnd: makeLnd({[highKey]: 'alpha', [lowKey]: 'beta'}),
|
|
65
|
+
policies: [],
|
|
66
|
+
query: highKey,
|
|
67
|
+
tags: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
Object.keys(overrides).forEach(key => args[key] = overrides[key]);
|
|
71
|
+
|
|
72
|
+
return args;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const tests = [
|
|
76
|
+
{
|
|
77
|
+
args: makeArgs({direction: undefined}),
|
|
78
|
+
description: 'A known rebalance direction is required',
|
|
79
|
+
error: [400, 'ExpectedInOrOutDirectionToFindRebalancePeer'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
args: makeArgs({}),
|
|
83
|
+
description: 'An explicit public key satisfying the filter is selected',
|
|
84
|
+
expected: {public_key: highKey},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
args: makeArgs({query: highKey.toUpperCase()}),
|
|
88
|
+
description: 'An uppercase public key is matched to the active peer',
|
|
89
|
+
expected: {public_key: highKey},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
args: makeArgs({query: lowKey}),
|
|
93
|
+
description: 'An explicit public key failing the filter is rejected',
|
|
94
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
args: makeArgs({query: 'alpha'}),
|
|
98
|
+
description: 'An explicit peer alias satisfying the filter is selected',
|
|
99
|
+
expected: {public_key: highKey},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
args: makeArgs({query: 'beta'}),
|
|
103
|
+
description: 'Only the explicitly aliased peer is tested by the filter',
|
|
104
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
args: makeArgs({
|
|
108
|
+
query: 'group',
|
|
109
|
+
tags: [{alias: 'group', id: '00', nodes: [highKey, lowKey]}],
|
|
110
|
+
}),
|
|
111
|
+
description: 'An outbound tag continues to select a filtered peer',
|
|
112
|
+
expected: {public_key: highKey},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
args: makeArgs({
|
|
116
|
+
lnd: makeLnd({[highKey]: 'group'}),
|
|
117
|
+
query: 'group',
|
|
118
|
+
tags: [{alias: 'group', id: '00', nodes: [lowKey]}],
|
|
119
|
+
}),
|
|
120
|
+
description: 'A filtered-out tag does not fall through to a peer alias',
|
|
121
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
args: makeArgs({
|
|
125
|
+
channels: channels.concat({partner_public_key: inactiveKey}),
|
|
126
|
+
lnd: makeLnd({[highKey]: 'inactive-tag'}),
|
|
127
|
+
query: 'inactive-tag',
|
|
128
|
+
tags: [{alias: 'inactive-tag', id: '00', nodes: [inactiveKey]}],
|
|
129
|
+
}),
|
|
130
|
+
description: 'A tag with no active peers does not fall through to an alias',
|
|
131
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
args: makeArgs({
|
|
135
|
+
query: 'group',
|
|
136
|
+
tags: [
|
|
137
|
+
{alias: 'group', id: '00', nodes: [highKey]},
|
|
138
|
+
{alias: 'group', id: '11', nodes: [lowKey]},
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
description: 'Ambiguous outbound tags remain rejected',
|
|
142
|
+
error: [
|
|
143
|
+
400,
|
|
144
|
+
'MultipleTagMatchesFoundForOutPeer',
|
|
145
|
+
{
|
|
146
|
+
matches: [
|
|
147
|
+
{alias: 'group', id: '00', nodes: [highKey]},
|
|
148
|
+
{alias: 'group', id: '11', nodes: [lowKey]},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
args: makeArgs({query: unknownKey}),
|
|
155
|
+
description: 'An unknown explicit public key does not satisfy a filter',
|
|
156
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
args: makeArgs({
|
|
160
|
+
channels: channels.concat({partner_public_key: inactiveKey}),
|
|
161
|
+
lnd: makeLnd({[inactiveKey]: 'inactive'}),
|
|
162
|
+
query: 'inactive',
|
|
163
|
+
}),
|
|
164
|
+
description: 'An inactive aliased peer does not satisfy a filter',
|
|
165
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
args: makeArgs({
|
|
169
|
+
filters: ['invalid formula'],
|
|
170
|
+
query: 'alpha',
|
|
171
|
+
}),
|
|
172
|
+
description: 'An invalid filter on an explicit alias is rejected',
|
|
173
|
+
error: [
|
|
174
|
+
400,
|
|
175
|
+
'FailedToParseFilter',
|
|
176
|
+
{
|
|
177
|
+
error: 'UnexpectedTrailingTokenForFormulaParsing',
|
|
178
|
+
formula: 'invalid formula',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
args: makeArgs({filters: [], query: 'beta'}),
|
|
184
|
+
description: 'An explicit alias is preserved when there are no filters',
|
|
185
|
+
expected: {public_key: lowKey},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
args: makeArgs({
|
|
189
|
+
channels: channels.concat({partner_public_key: inactiveKey}),
|
|
190
|
+
filters: [],
|
|
191
|
+
lnd: makeLnd({[inactiveKey]: 'inactive'}),
|
|
192
|
+
query: 'inactive',
|
|
193
|
+
}),
|
|
194
|
+
description: 'An inactive alias still resolves when there are no filters',
|
|
195
|
+
expected: {public_key: inactiveKey},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
args: makeArgs({query: undefined}),
|
|
199
|
+
description: 'An outbound filter still requires an outbound selector',
|
|
200
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyOutboundFilter'],
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
args: makeArgs({filters: [], query: undefined}),
|
|
204
|
+
description: 'No selector and no filter preserves automatic selection',
|
|
205
|
+
expected: {public_key: undefined},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
args: makeArgs({
|
|
209
|
+
direction: 'in',
|
|
210
|
+
filters: inboundFilters,
|
|
211
|
+
query: highKey,
|
|
212
|
+
}),
|
|
213
|
+
description: 'An inbound public key satisfying the filter is selected',
|
|
214
|
+
expected: {public_key: highKey},
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
args: makeArgs({
|
|
218
|
+
direction: 'in',
|
|
219
|
+
filters: inboundFilters,
|
|
220
|
+
query: lowKey,
|
|
221
|
+
}),
|
|
222
|
+
description: 'An inbound public key failing the filter is rejected',
|
|
223
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyInboundFilter'],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
args: makeArgs({
|
|
227
|
+
direction: 'in',
|
|
228
|
+
filters: inboundFilters,
|
|
229
|
+
query: 'alpha',
|
|
230
|
+
}),
|
|
231
|
+
description: 'An inbound alias satisfying the filter is selected',
|
|
232
|
+
expected: {public_key: highKey},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
args: makeArgs({
|
|
236
|
+
direction: 'in',
|
|
237
|
+
filters: inboundFilters,
|
|
238
|
+
query: 'beta',
|
|
239
|
+
}),
|
|
240
|
+
description: 'Only the explicitly aliased inbound peer is filtered',
|
|
241
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyInboundFilter'],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
args: makeArgs({
|
|
245
|
+
direction: 'in',
|
|
246
|
+
filters: inboundFilters,
|
|
247
|
+
query: 'group',
|
|
248
|
+
tags: [{alias: 'group', id: '00', nodes: [highKey, lowKey]}],
|
|
249
|
+
}),
|
|
250
|
+
description: 'An inbound tag continues to select a filtered peer',
|
|
251
|
+
expected: {public_key: highKey},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
args: makeArgs({
|
|
255
|
+
direction: 'in',
|
|
256
|
+
filters: inboundFilters,
|
|
257
|
+
lnd: makeLnd({[highKey]: 'group'}),
|
|
258
|
+
query: 'group',
|
|
259
|
+
tags: [{alias: 'group', id: '00', nodes: [lowKey]}],
|
|
260
|
+
}),
|
|
261
|
+
description: 'A filtered inbound tag does not fall through to an alias',
|
|
262
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyInboundFilter'],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
args: makeArgs({
|
|
266
|
+
direction: 'in',
|
|
267
|
+
filters: inboundFilters,
|
|
268
|
+
policies: [{is_disabled: true, public_key: highKey}],
|
|
269
|
+
query: highKey,
|
|
270
|
+
}),
|
|
271
|
+
description: 'An inbound-disabled explicit peer fails the filter',
|
|
272
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyInboundFilter'],
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
args: makeArgs({
|
|
276
|
+
direction: 'in',
|
|
277
|
+
filters: [],
|
|
278
|
+
policies: [{is_disabled: true, public_key: highKey}],
|
|
279
|
+
query: highKey,
|
|
280
|
+
}),
|
|
281
|
+
description: 'An inbound-disabled explicit peer resolves without filters',
|
|
282
|
+
expected: {public_key: highKey},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
args: makeArgs({
|
|
286
|
+
direction: 'in',
|
|
287
|
+
filters: inboundFilters,
|
|
288
|
+
query: 'group',
|
|
289
|
+
tags: [
|
|
290
|
+
{alias: 'group', id: '00', nodes: [highKey]},
|
|
291
|
+
{alias: 'group', id: '11', nodes: [lowKey]},
|
|
292
|
+
],
|
|
293
|
+
}),
|
|
294
|
+
description: 'Ambiguous inbound tags remain rejected',
|
|
295
|
+
error: [
|
|
296
|
+
400,
|
|
297
|
+
'MultipleTagMatchesFoundForInPeer',
|
|
298
|
+
{
|
|
299
|
+
matches: [
|
|
300
|
+
{alias: 'group', id: '00', nodes: [highKey]},
|
|
301
|
+
{alias: 'group', id: '11', nodes: [lowKey]},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
args: makeArgs({
|
|
308
|
+
direction: 'in',
|
|
309
|
+
filters: inboundFilters,
|
|
310
|
+
query: undefined,
|
|
311
|
+
}),
|
|
312
|
+
description: 'An inbound filter still requires an inbound selector',
|
|
313
|
+
error: [400, 'NoPeerMatchesFoundToSatisfyInboundFilter'],
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
318
|
+
return test(description, async () => {
|
|
319
|
+
if (!!error) {
|
|
320
|
+
await rejects(findRebalancePeer(args), error, 'Got expected error');
|
|
321
|
+
} else {
|
|
322
|
+
const res = await findRebalancePeer(args);
|
|
323
|
+
|
|
324
|
+
deepEqual(res, expected, 'Got expected rebalance peer');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return;
|
|
328
|
+
});
|
|
329
|
+
});
|