balanceofsatoshis 11.37.0 → 11.41.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 +18 -0
- package/bos +36 -2
- package/commands/constants.json +1 -0
- package/lnd/find_record.js +2 -0
- package/network/get_peers.js +14 -0
- package/network/remove_peer.js +113 -6
- package/package.json +2 -2
- package/peers/index.js +7 -1
- package/peers/limit_forwarding.js +87 -0
- package/services/advertise.js +29 -2
- package/swaps/rebalance.js +1 -2
- package/test/network/test_get_peers.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 11.41.0
|
|
4
|
+
|
|
5
|
+
- `remove-peer`: Add interactive mode to select channels to close
|
|
6
|
+
|
|
7
|
+
## 11.40.0
|
|
8
|
+
|
|
9
|
+
- `peers`: Add `DISK_USAGE_MB` filter to `--filter` formulas for est disk usage
|
|
10
|
+
- `peers`: Add `est_disk_usage_mb` to allowed `--sort` fields
|
|
11
|
+
|
|
12
|
+
## 11.39.0
|
|
13
|
+
|
|
14
|
+
- `limit-forwarding`: Add new command to limit allowed routing
|
|
15
|
+
|
|
16
|
+
## 11.38.0
|
|
17
|
+
|
|
18
|
+
- `advertise`: Add support for `--max-hops` to specify a maximum graph distance
|
|
19
|
+
- `advertise`: Add support for `--min-hops` to specify a minimum graph distance
|
|
20
|
+
|
|
3
21
|
## 11.37.0
|
|
4
22
|
|
|
5
23
|
- `advertise`: Add support for specifying a maximum ad spend with `--budget`
|
package/bos
CHANGED
|
@@ -98,10 +98,14 @@ prog
|
|
|
98
98
|
.help('use --filter conditions to limit broadcast scope: capacity > 1*m')
|
|
99
99
|
.help('--filter variables: CAPACITY/CHANNELS_COUNT/K/M')
|
|
100
100
|
.help('Default filter scope: channels_count > 9')
|
|
101
|
+
.help('To only advertise to direct peers use --max-hops 0')
|
|
102
|
+
.help('To avoid advertising to direct peers use --min-hops 1')
|
|
101
103
|
.option('--budget <budget>', 'Spending amount to allow for advertising', INT)
|
|
102
104
|
.option('--dryrun', 'Avoid actually sending advertisements')
|
|
103
105
|
.option('--filter <expression>', 'Require node match condition', REPEATABLE)
|
|
106
|
+
.option('--max-hops <max_hops>', 'Maximum number of relaying nodes', INT)
|
|
104
107
|
.option('--message <message>', 'Custom advertisement message')
|
|
108
|
+
.option('--min-hops <min_hops>', 'Minimum number of relaying nodes', INT)
|
|
105
109
|
.option('--node <node_name>', 'Advertise via saved node')
|
|
106
110
|
.action((args, options, logger) => {
|
|
107
111
|
return new Promise(async (resolve, reject) => {
|
|
@@ -113,6 +117,8 @@ prog
|
|
|
113
117
|
is_dry_run: !!options.dryrun,
|
|
114
118
|
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
115
119
|
message: options.message,
|
|
120
|
+
max_hops: options.maxHops,
|
|
121
|
+
min_hops: options.minHops,
|
|
116
122
|
});
|
|
117
123
|
} catch (err) {
|
|
118
124
|
return reject(logger.error({err}));
|
|
@@ -818,6 +824,28 @@ prog
|
|
|
818
824
|
});
|
|
819
825
|
})
|
|
820
826
|
|
|
827
|
+
// Intercept forwarding requests, enforce requirements on acceptance
|
|
828
|
+
.command('limit-forwarding', 'Enforce rules for routing payments')
|
|
829
|
+
.option('--node <node_name>', 'Saved node to enforce rules on')
|
|
830
|
+
.option('--disable-forwards', 'Disable all forwards')
|
|
831
|
+
.option('--max-hours-since-last-block <h>', 'Require fresh blocks', INT, 5)
|
|
832
|
+
.option('--max-new-pending-per-hour <h>', 'Limit held HTLCs', INT)
|
|
833
|
+
.action((args, options, logger) => {
|
|
834
|
+
return new Promise(async (resolve, reject) => {
|
|
835
|
+
try {
|
|
836
|
+
return await peers.limitForwarding({
|
|
837
|
+
logger,
|
|
838
|
+
is_disabling_all_forwards: options.disableForwards || undefined,
|
|
839
|
+
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
840
|
+
max_hours_since_last_block: options.maxHoursSinceLastBlock,
|
|
841
|
+
max_new_pending_per_hour: options.maxNewPendingPerHour,
|
|
842
|
+
});
|
|
843
|
+
} catch (err) {
|
|
844
|
+
return reject(logger.error({err}));
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
})
|
|
848
|
+
|
|
821
849
|
// Get inbound liquidity information: available inbound off-chain tokens
|
|
822
850
|
.command('inbound-liquidity', 'Get inbound liquidity size')
|
|
823
851
|
.option('--above <tokens>', 'Return amount above watermark', INT)
|
|
@@ -1140,8 +1168,11 @@ prog
|
|
|
1140
1168
|
.help('Icons: 🤢 often d/c, 💸 active HTLC, 💀 d/c, 🌚 private')
|
|
1141
1169
|
.help('Icons: 🧊 delayed coop close, ⏳ pending channel, 🚫 in disabled')
|
|
1142
1170
|
.help('Icons: 🦐 limited max htlc')
|
|
1143
|
-
.help('Filters can take formula expressions
|
|
1144
|
-
.help('Filter
|
|
1171
|
+
.help('Filters can take formula expressions to limit results')
|
|
1172
|
+
.help('Filter variable AGE: "age > 144 * 7" for peers older than a week')
|
|
1173
|
+
.help('Filter variable DISK_USAGE_MB: "disk_usage_mb > 9" for disk estimate')
|
|
1174
|
+
.help('Filter variable INBOUND_LIQUIDITY: "inbound_liquidity > 1*m"')
|
|
1175
|
+
.help('Filter variable OUTBOUND_LIQUIDITY: "outbound_liquidity > 1*m"')
|
|
1145
1176
|
.option('--active', 'Only active peer channels')
|
|
1146
1177
|
.option('--complete', 'Show complete set of records in non table view')
|
|
1147
1178
|
.option('--fee-days <past_days>', 'Include fees earned over n days', INT)
|
|
@@ -1365,6 +1396,7 @@ prog
|
|
|
1365
1396
|
.option('--outpoint', 'Only remove specific channel with funding txid:vout')
|
|
1366
1397
|
.option('--private', 'Peer is privately connected')
|
|
1367
1398
|
.option('--public', 'Peer is publicly connected')
|
|
1399
|
+
.option('--select-channels', 'Select channels to remove interactively')
|
|
1368
1400
|
.action((args, options, logger) => {
|
|
1369
1401
|
return new Promise(async (resolve, reject) => {
|
|
1370
1402
|
try {
|
|
@@ -1374,6 +1406,7 @@ prog
|
|
|
1374
1406
|
lnd,
|
|
1375
1407
|
logger,
|
|
1376
1408
|
address: options.address,
|
|
1409
|
+
ask: (n, cbk) => inquirer.prompt([n]).then(n => cbk(n)),
|
|
1377
1410
|
chain_fee_rate: options.feeRate || undefined,
|
|
1378
1411
|
fs: {getFile: readFile},
|
|
1379
1412
|
idle_days: options.idleDays,
|
|
@@ -1384,6 +1417,7 @@ prog
|
|
|
1384
1417
|
is_offline: !!options.offline,
|
|
1385
1418
|
is_private: !!options.private,
|
|
1386
1419
|
is_public: !!options.public,
|
|
1420
|
+
is_selecting_channels: options.selectChannels || undefined,
|
|
1387
1421
|
omit: flatten([options.omit].filter(n => !!n)),
|
|
1388
1422
|
outbound_liquidity_below: options.outboundBelow,
|
|
1389
1423
|
outpoints: flatten([options.outpoint].filter(n => !!n)),
|
package/commands/constants.json
CHANGED
package/lnd/find_record.js
CHANGED
|
@@ -236,6 +236,7 @@ module.exports = ({lnd, query}, cbk) => {
|
|
|
236
236
|
.map(chan => {
|
|
237
237
|
const [height] = chan.id.split('x');
|
|
238
238
|
const local = formatTokens({tokens: chan.local_balance});
|
|
239
|
+
const pending = chan.pending_payments;
|
|
239
240
|
const remote = formatTokens({tokens: chan.remote_balance});
|
|
240
241
|
|
|
241
242
|
const inbound = `in: ${balance(remote)}`;
|
|
@@ -248,6 +249,7 @@ module.exports = ({lnd, query}, cbk) => {
|
|
|
248
249
|
capacity: formatTokens({tokens: chan.capacity}).display,
|
|
249
250
|
funding: `${chan.transaction_id} ${chan.transaction_vout}`,
|
|
250
251
|
peer_created: chan.is_partner_initiated || undefined,
|
|
252
|
+
pending_payments: !!pending.length ? pending : undefined,
|
|
251
253
|
};
|
|
252
254
|
}),
|
|
253
255
|
}
|
package/network/get_peers.js
CHANGED
|
@@ -30,7 +30,9 @@ const {sortBy} = require('./../arrays');
|
|
|
30
30
|
const closedSorts = ['fee_earnings', 'first_connected'];
|
|
31
31
|
const defaultInvoicesLimit = 200;
|
|
32
32
|
const defaultSort = 'first_connected';
|
|
33
|
+
const estimateDiskFootprint = n => Math.round(n * 500 / 1e6 * 10) / 10;
|
|
33
34
|
const fromNow = epoch => !epoch ? undefined : moment(epoch * 1e3).fromNow();
|
|
35
|
+
const hasDiskFilter = filter => /disk_usage_mb/gim.test(filter);
|
|
34
36
|
const {isArray} = Array;
|
|
35
37
|
const {max} = Math;
|
|
36
38
|
const minutesPerBlock = network => network === 'ltcmainnet' ? 10 / 4 : 10;
|
|
@@ -488,6 +490,8 @@ module.exports = (args, cbk) => {
|
|
|
488
490
|
const disabled = policies.map(n => !!n.is_disabled).filter(n => !!n);
|
|
489
491
|
const feeRate = !feeRates.length ? undefined : max(...feeRates);
|
|
490
492
|
const maxHtlcSizes = policies.map(n => n.max_htlc_mtokens);
|
|
493
|
+
const pastStates = sumOf(active.map(n => n.past_states));
|
|
494
|
+
|
|
491
495
|
const totalCapacity = sumOf(active.map(n => n.capacity));
|
|
492
496
|
|
|
493
497
|
const totalMaxHtlc = mtokensAsTokens(sumOfBig(maxHtlcSizes));
|
|
@@ -503,6 +507,7 @@ module.exports = (args, cbk) => {
|
|
|
503
507
|
filters: args.filters || [],
|
|
504
508
|
variables: {
|
|
505
509
|
age: blocks,
|
|
510
|
+
disk_usage_mb: estimateDiskFootprint(pastStates),
|
|
506
511
|
fee_earnings: mtokensAsTokens(feeMtokens),
|
|
507
512
|
inbound_fee_rate: feeRate,
|
|
508
513
|
inbound_liquidity: sumOf(channels.map(n => n.remote_balance)),
|
|
@@ -539,6 +544,7 @@ module.exports = (args, cbk) => {
|
|
|
539
544
|
return {
|
|
540
545
|
alias: node.alias,
|
|
541
546
|
downtime_percentage: round(100 * (downtime / (downtime + uptime))),
|
|
547
|
+
est_disk_usage_mb: estimateDiskFootprint(pastStates),
|
|
542
548
|
fee_earnings: mtokensAsTokens(feeMtokens),
|
|
543
549
|
first_connected: moment().subtract(blocks * mpb, 'minutes').unix(),
|
|
544
550
|
inbound_fee_rate: feeRate,
|
|
@@ -590,6 +596,7 @@ module.exports = (args, cbk) => {
|
|
|
590
596
|
return {
|
|
591
597
|
alias: peer.alias,
|
|
592
598
|
downtime_percentage: peer.downtime_percentage,
|
|
599
|
+
est_disk_usage_mb: peer.est_disk_usage_mb,
|
|
593
600
|
fee_earnings: peer.fee_earnings,
|
|
594
601
|
first_connected: fromNow(peer.first_connected),
|
|
595
602
|
last_activity: fromNow(peer.last_activity),
|
|
@@ -615,6 +622,7 @@ module.exports = (args, cbk) => {
|
|
|
615
622
|
return cbk(null, {
|
|
616
623
|
peers: peers.peers.map(n => ({
|
|
617
624
|
alias: n.alias,
|
|
625
|
+
est_disk_usage_mb: n.est_disk_usage_mb || undefined,
|
|
618
626
|
fee_earnings: n.fee_earnings || undefined,
|
|
619
627
|
downtime_percentage: n.downtime_percentage || undefined,
|
|
620
628
|
first_connected: n.first_connected || undefined,
|
|
@@ -634,13 +642,18 @@ module.exports = (args, cbk) => {
|
|
|
634
642
|
});
|
|
635
643
|
}
|
|
636
644
|
|
|
645
|
+
const isDiskFilter = (args.filters || []).find(n => hasDiskFilter(n));
|
|
646
|
+
const isDiskSort = args.sort_by === 'est_disk_usage_mb';
|
|
637
647
|
const isWideSize = !size || size.get().width > wideSizeCols;
|
|
638
648
|
|
|
649
|
+
const isShowingDisk = isDiskFilter || isDiskSort;
|
|
650
|
+
|
|
639
651
|
return cbk(null, {
|
|
640
652
|
peers: peers.peers,
|
|
641
653
|
rows: []
|
|
642
654
|
.concat([notNull([
|
|
643
655
|
'Alias',
|
|
656
|
+
!!isShowingDisk ? 'Disk Mb' : null,
|
|
644
657
|
'Inbound',
|
|
645
658
|
'In Fee',
|
|
646
659
|
'Outbound',
|
|
@@ -682,6 +695,7 @@ module.exports = (args, cbk) => {
|
|
|
682
695
|
|
|
683
696
|
return notNull([
|
|
684
697
|
alias.display,
|
|
698
|
+
!!isShowingDisk ? peer.est_disk_usage_mb || ' ' : null,
|
|
685
699
|
inbound.display || ' ',
|
|
686
700
|
peer.inbound_fee_rate || ' ',
|
|
687
701
|
outbound.display || ' ',
|
package/network/remove_peer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncEachSeries = require('async/eachSeries');
|
|
3
3
|
const {closeChannel} = require('ln-service');
|
|
4
|
+
const {decodeChanId} = require('bolt07');
|
|
4
5
|
const {getChainFeeRate} = require('ln-service');
|
|
5
6
|
const {getChannels} = require('ln-service');
|
|
6
7
|
const {getNetwork} = require('ln-sync');
|
|
@@ -11,20 +12,25 @@ const getPeers = require('./get_peers');
|
|
|
11
12
|
|
|
12
13
|
const arrayWithEntries = arr => !!arr.length ? arr : undefined;
|
|
13
14
|
const asOutpoint = n => `${n.transaction_id}:${n.transaction_vout}`;
|
|
15
|
+
const estimateDisk = n => Math.round(n * 500 / 1e6 * 10) / 10;
|
|
14
16
|
const fastConf = 6;
|
|
15
17
|
const {floor} = Math;
|
|
16
18
|
const defaultDays = 365 * 2;
|
|
17
19
|
const getMempoolRetries = 10;
|
|
20
|
+
const iconDisabled = channel => !channel.is_active ? '💀 ' : '';
|
|
21
|
+
const iconPending = channel => channel.pending_payments.length ? '💸 ' : ''
|
|
18
22
|
const {isArray} = Array;
|
|
19
23
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
20
24
|
const maxMempoolSize = 2e6;
|
|
21
25
|
const regularConf = 72;
|
|
22
26
|
const slowConf = 144;
|
|
27
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
23
28
|
|
|
24
29
|
/** Close out channels with a peer and disconnect them
|
|
25
30
|
|
|
26
31
|
{
|
|
27
32
|
[address]: <Close Out Funds to On-Chain Address String>
|
|
33
|
+
ask: <Ask Function>
|
|
28
34
|
[chain_fee_rate]: <Chain Fee Per VByte Number>
|
|
29
35
|
fs: {
|
|
30
36
|
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
@@ -37,6 +43,7 @@ const slowConf = 144;
|
|
|
37
43
|
[is_offline]: <Peer Is Disconnected Bool>
|
|
38
44
|
[is_private]: <Peer is Privately Connected Bool>
|
|
39
45
|
[is_public]: <Peer is Publicly Connected Bool>
|
|
46
|
+
[is_selecting_channels]: <Interactively Select Channels to Remove Bool>
|
|
40
47
|
lnd: <Authenticated LND API Object>
|
|
41
48
|
logger: <Winston Logger Object>
|
|
42
49
|
[omit]: [<Avoid Peer With Public Key String>]
|
|
@@ -53,10 +60,18 @@ module.exports = (args, cbk) => {
|
|
|
53
60
|
return asyncAuto({
|
|
54
61
|
// Check arguments
|
|
55
62
|
validate: cbk => {
|
|
63
|
+
if (!args.ask) {
|
|
64
|
+
return cbk([400, 'ExpectedAskFunctionToRemovePeer']);
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
if (!args.fs) {
|
|
57
68
|
return cbk([400, 'ExpectedFsMethodsToRemovePeer']);
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
if (!!args.is_selecting_channels && !args.public_key) {
|
|
72
|
+
return cbk([400, 'ExpectedPeerToRemoveWhenSelectingChannels']);
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
if (!args.lnd) {
|
|
61
76
|
return cbk([400, 'LndIsRequiredToRemovePeer']);
|
|
62
77
|
}
|
|
@@ -69,6 +84,10 @@ module.exports = (args, cbk) => {
|
|
|
69
84
|
return cbk([400, 'ExpectedSpecificOutpointsToRemoveFromPeer']);
|
|
70
85
|
}
|
|
71
86
|
|
|
87
|
+
if (!!args.outpoints.length && !args.public_key) {
|
|
88
|
+
return cbk([400, 'ExpectedPeerToRemoteWhenOutpointsSpecified']);
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
if (!!args.public_key && !isPublicKey(args.public_key)) {
|
|
73
92
|
return cbk([400, 'ExpectedPublicKeyOfPeerToRemove']);
|
|
74
93
|
}
|
|
@@ -137,8 +156,94 @@ module.exports = (args, cbk) => {
|
|
|
137
156
|
cbk);
|
|
138
157
|
}],
|
|
139
158
|
|
|
159
|
+
// Determine outpoints to use
|
|
160
|
+
outpoints: ['getChannels', ({getChannels}, cbk) => {
|
|
161
|
+
// Exit early when a peer is not specified
|
|
162
|
+
if (!args.public_key) {
|
|
163
|
+
return cbk(null, []);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const channelsWithPeer = getChannels.channels
|
|
167
|
+
.filter(channel => {
|
|
168
|
+
// Ignore channels that are not the specified public key
|
|
169
|
+
if (channel.partner_public_key !== args.public_key) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//Return channels with the peer
|
|
174
|
+
return true;
|
|
175
|
+
})
|
|
176
|
+
.sort((a, b) => {
|
|
177
|
+
const heightA = decodeChanId({channel: a.id}).block_height;
|
|
178
|
+
const heightB = decodeChanId({channel: b.id}).block_height;
|
|
179
|
+
|
|
180
|
+
// Sort channels by oldest to newest
|
|
181
|
+
return heightA - heightB;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Exit early when no channels are available
|
|
185
|
+
if (!channelsWithPeer.length) {
|
|
186
|
+
return cbk([404, 'NoChannelsToCloseWithSpecifiedPeer']);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Collect any outpoints that are unable to be cooperatively closed
|
|
190
|
+
const blocked = channelsWithPeer
|
|
191
|
+
.filter(channel => {
|
|
192
|
+
// Channels that are inactive or have HTLCs cannot be coop-closed
|
|
193
|
+
return !channel.is_active || !!channel.pending_payments.length;
|
|
194
|
+
})
|
|
195
|
+
.map(channel => asOutpoint(channel));
|
|
196
|
+
|
|
197
|
+
// Find a directly referenced outpoint that is in the blocked list
|
|
198
|
+
const blockedOutpoint = args.outpoints.find(n => blocked.includes(n));
|
|
199
|
+
|
|
200
|
+
// Make sure we aren't trying to coop close a channel that can't be
|
|
201
|
+
if (!args.is_forced && !!blockedOutpoint) {
|
|
202
|
+
return cbk([400, 'CannotCoopClose', {outpoint: blockedOutpoint}]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Exit early if not selecting a channel
|
|
206
|
+
if (!args.is_selecting_channels) {
|
|
207
|
+
return cbk(null, args.outpoints);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Interactively select outpoints to close
|
|
211
|
+
return args.ask({
|
|
212
|
+
choices: channelsWithPeer.map(channel => {
|
|
213
|
+
// In closing, channels are identified by their funding outpoint
|
|
214
|
+
const value = asOutpoint(channel);
|
|
215
|
+
|
|
216
|
+
// Channels that are inactive or have HTLCs cannot be coop-closed
|
|
217
|
+
const isBlocked = blocked.includes(value);
|
|
218
|
+
|
|
219
|
+
const disk = `Est disk mb: ${estimateDisk(channel.past_states)}`;
|
|
220
|
+
const icon = iconDisabled(channel) || iconPending(channel);
|
|
221
|
+
const {id} = channel;
|
|
222
|
+
const inbound = `in: ${tokensAsBigUnit(channel.remote_balance)}`;
|
|
223
|
+
const outbound = `out: ${tokensAsBigUnit(channel.local_balance)}`;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
value,
|
|
227
|
+
checked: args.outpoints.includes(value),
|
|
228
|
+
disabled: !args.is_forced ? isBlocked : false,
|
|
229
|
+
name: `${icon}${id}: ${inbound} | ${outbound}. ${disk}.`,
|
|
230
|
+
};
|
|
231
|
+
}),
|
|
232
|
+
loop: false,
|
|
233
|
+
message: `Channels to ${!!args.is_forced ? 'force ' : ''}close?`,
|
|
234
|
+
name: 'outpoints',
|
|
235
|
+
type: 'checkbox',
|
|
236
|
+
validate: input => !!input.length,
|
|
237
|
+
},
|
|
238
|
+
({outpoints}) => cbk(null, outpoints));
|
|
239
|
+
}],
|
|
240
|
+
|
|
140
241
|
// Check channels for peer to make sure that they can be cleanly closed
|
|
141
|
-
checkChannels: [
|
|
242
|
+
checkChannels: [
|
|
243
|
+
'getChannels',
|
|
244
|
+
'outpoints',
|
|
245
|
+
({getChannels, outpoints}, cbk) =>
|
|
246
|
+
{
|
|
142
247
|
// Exit early when a peer is not specified or force closing is OK
|
|
143
248
|
if (!args.public_key || !!args.is_forced) {
|
|
144
249
|
return cbk();
|
|
@@ -151,12 +256,12 @@ module.exports = (args, cbk) => {
|
|
|
151
256
|
}
|
|
152
257
|
|
|
153
258
|
// Exit early when there are no outpoints, consider all peer channels
|
|
154
|
-
if (!
|
|
259
|
+
if (!outpoints.length) {
|
|
155
260
|
return true;
|
|
156
261
|
}
|
|
157
262
|
|
|
158
263
|
// Only include selected channels
|
|
159
|
-
return
|
|
264
|
+
return outpoints.includes(asOutpoint(channel));
|
|
160
265
|
});
|
|
161
266
|
|
|
162
267
|
const costToClose = selectedChannels
|
|
@@ -279,8 +384,9 @@ module.exports = (args, cbk) => {
|
|
|
279
384
|
'checkChannels',
|
|
280
385
|
'getChannels',
|
|
281
386
|
'getNormalFee',
|
|
387
|
+
'outpoints',
|
|
282
388
|
'selectPeer',
|
|
283
|
-
({getChannels, getNormalFee, selectPeer}, cbk) =>
|
|
389
|
+
({getChannels, getNormalFee, outpoints, selectPeer}, cbk) =>
|
|
284
390
|
{
|
|
285
391
|
// Exit early when there is no peer to close out with
|
|
286
392
|
if (!selectPeer) {
|
|
@@ -292,11 +398,12 @@ module.exports = (args, cbk) => {
|
|
|
292
398
|
const toClose = getChannels.channels
|
|
293
399
|
.filter(chan => chan.partner_public_key === selectPeer.public_key)
|
|
294
400
|
.filter(chan => {
|
|
295
|
-
|
|
401
|
+
// When no outpoints are specified, all channels should be closed
|
|
402
|
+
if (!outpoints.length) {
|
|
296
403
|
return true;
|
|
297
404
|
}
|
|
298
405
|
|
|
299
|
-
return
|
|
406
|
+
return !!outpoints.includes(asOutpoint(chan));
|
|
300
407
|
});
|
|
301
408
|
|
|
302
409
|
// Exit early when there are no channels to close
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"invoices": "2.0.3",
|
|
37
37
|
"ln-accounting": "5.0.5",
|
|
38
38
|
"ln-service": "53.6.0",
|
|
39
|
-
"ln-sync": "3.
|
|
39
|
+
"ln-sync": "3.8.0",
|
|
40
40
|
"ln-telegram": "3.12.0",
|
|
41
41
|
"moment": "2.29.1",
|
|
42
42
|
"paid-services": "3.11.0",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
|
82
82
|
"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/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"
|
|
83
83
|
},
|
|
84
|
-
"version": "11.
|
|
84
|
+
"version": "11.41.0"
|
|
85
85
|
}
|
package/peers/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
const findTagMatch = require('./find_tag_match');
|
|
2
2
|
const interceptInboundChannels = require('./intercept_inbound_channels');
|
|
3
|
+
const limitForwarding = require('./limit_forwarding');
|
|
3
4
|
const openChannels = require('./open_channels');
|
|
4
5
|
|
|
5
|
-
module.exports = {
|
|
6
|
+
module.exports = {
|
|
7
|
+
findTagMatch,
|
|
8
|
+
interceptInboundChannels,
|
|
9
|
+
limitForwarding,
|
|
10
|
+
openChannels,
|
|
11
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {enforceForwardRequestRules} = require('ln-sync');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const disableAllForwards = 0;
|
|
6
|
+
const hoursAsSeconds = hours => hours * 60 * 60;
|
|
7
|
+
|
|
8
|
+
/** Limit forwarding requests
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
12
|
+
logger: <Winston Logger Object>
|
|
13
|
+
[is_disabling_all_forwards]: <All Forwards Are Disabled Bool>
|
|
14
|
+
[max_hours_since_last_block]: options.maxHoursSinceLastBlock,
|
|
15
|
+
[max_new_pending_per_hour]: options.maxNewPendingPerHour,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@returns via cbk or Promise
|
|
19
|
+
*/
|
|
20
|
+
module.exports = (args, cbk) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
return asyncAuto({
|
|
23
|
+
// Check arguments
|
|
24
|
+
validate: cbk => {
|
|
25
|
+
if (!args.logger) {
|
|
26
|
+
return cbk([400, 'ExpectedWinstonLoggerToLimitForwarding']);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!args.lnd) {
|
|
30
|
+
return cbk([400, 'ExpectedAuthenticatedLndToLimitForwarding']);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return cbk();
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Max pending per hour
|
|
37
|
+
maxPendingPerHour: ['validate', ({}, cbk) => {
|
|
38
|
+
if (!!args.is_disabling_all_forwards) {
|
|
39
|
+
return cbk(null, disableAllForwards);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!!args.max_new_pending_per_hour) {
|
|
43
|
+
return cbk(null, args.max_new_pending_per_hour);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return cbk();
|
|
47
|
+
}],
|
|
48
|
+
|
|
49
|
+
// Max seconds allowed since the last block
|
|
50
|
+
maxSecondsSinceLastBlock: ['validate', ({}, cbk) => {
|
|
51
|
+
if (!args.max_hours_since_last_block) {
|
|
52
|
+
return cbk();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return cbk(null, hoursAsSeconds(args.max_hours_since_last_block));
|
|
56
|
+
}],
|
|
57
|
+
|
|
58
|
+
// Limit forward requests
|
|
59
|
+
limit: [
|
|
60
|
+
'maxPendingPerHour',
|
|
61
|
+
'maxSecondsSinceLastBlock',
|
|
62
|
+
({maxPendingPerHour, maxSecondsSinceLastBlock}, cbk) =>
|
|
63
|
+
{
|
|
64
|
+
args.logger.info({limiting_forwards: true});
|
|
65
|
+
|
|
66
|
+
const sub = enforceForwardRequestRules({
|
|
67
|
+
lnd: args.lnd,
|
|
68
|
+
max_new_pending_per_hour: maxPendingPerHour,
|
|
69
|
+
max_seconds_since_last_block: maxSecondsSinceLastBlock,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
sub.on('error', err => {
|
|
73
|
+
return cbk([503, 'UnexpectedErrorLimitingForwarding', {err}]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
sub.on('rejected', async rejected => {
|
|
77
|
+
const forward = `${rejected.in_channel} → ${rejected.out_channel}`;
|
|
78
|
+
|
|
79
|
+
const rejection = `${rejected.reject_reason} ${forward}`;
|
|
80
|
+
|
|
81
|
+
return args.logger.info({rejection});
|
|
82
|
+
});
|
|
83
|
+
}],
|
|
84
|
+
},
|
|
85
|
+
returnResult({reject, resolve}, cbk));
|
|
86
|
+
});
|
|
87
|
+
};
|
package/services/advertise.js
CHANGED
|
@@ -36,6 +36,7 @@ const mtokPerToken = 1e3;
|
|
|
36
36
|
const pathTimeoutMs = 1000 * 45;
|
|
37
37
|
const payTimeoutMs = 1000 * 60;
|
|
38
38
|
const probeTimeoutMs = 1000 * 60 * 2;
|
|
39
|
+
const routeDistance = route => route.hops.length - 1;
|
|
39
40
|
const sendTokens = 10;
|
|
40
41
|
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
41
42
|
const textMessageType = '34349334';
|
|
@@ -49,6 +50,8 @@ const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
|
49
50
|
lnd: <Authenticated LND API Object>
|
|
50
51
|
logger: <Winston Logger Object>
|
|
51
52
|
[message]: <Message To Send String>
|
|
53
|
+
[max_hops]: <Maxmimum Relaying Nodes Number>
|
|
54
|
+
[min_hops]: <Minimum Relaying Nodes Number>
|
|
52
55
|
}
|
|
53
56
|
*/
|
|
54
57
|
module.exports = (args, cbk) => {
|
|
@@ -75,6 +78,14 @@ module.exports = (args, cbk) => {
|
|
|
75
78
|
getGraph: ['validate', ({}, cbk) => {
|
|
76
79
|
args.logger.info({sending_to_all_graph_nodes: true});
|
|
77
80
|
|
|
81
|
+
if (args.max_hops !== undefined) {
|
|
82
|
+
args.logger.info({maximum_relay_distance_allowed: args.max_hops});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (args.min_hops !== undefined) {
|
|
86
|
+
args.logger.info({minimum_relay_distance_required: args.min_hops});
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
return getNetworkGraph({lnd: args.lnd}, cbk);
|
|
79
90
|
}],
|
|
80
91
|
|
|
@@ -154,9 +165,25 @@ module.exports = (args, cbk) => {
|
|
|
154
165
|
tokens: sendTokens,
|
|
155
166
|
},
|
|
156
167
|
(err, res) => {
|
|
157
|
-
|
|
168
|
+
// Exit early when there is a problem getting a route
|
|
169
|
+
if (!!err || !res.route) {
|
|
170
|
+
return cbk(null, false);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const relaysCount = routeDistance(res.route);
|
|
174
|
+
|
|
175
|
+
// Exit early when there are too many relaying nodes
|
|
176
|
+
if (args.max_hops !== undefined && relaysCount > args.max_hops) {
|
|
177
|
+
return cbk(null, false);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Exit early when there are too few relaying nodes
|
|
181
|
+
if (args.min_hops !== undefined && relaysCount < args.min_hops) {
|
|
182
|
+
return cbk(null, false);
|
|
183
|
+
}
|
|
158
184
|
|
|
159
|
-
|
|
185
|
+
// There exists a route to the destination within range constraints
|
|
186
|
+
return cbk(null, true);
|
|
160
187
|
});
|
|
161
188
|
},
|
|
162
189
|
cbk);
|
package/swaps/rebalance.js
CHANGED
|
@@ -43,7 +43,6 @@ const isPublicKey = n => /^[0-9A-F]{66}$/i.test(n);
|
|
|
43
43
|
const legacyMaxRebalanceTokens = 4294967;
|
|
44
44
|
const {max} = Math;
|
|
45
45
|
const maxPaymentSize = 4294967;
|
|
46
|
-
const maxRebalanceTokens = 16777215;
|
|
47
46
|
const {min} = Math;
|
|
48
47
|
const minInboundBalance = 4294967 * 2;
|
|
49
48
|
const minRebalanceAmount = 5e4;
|
|
@@ -632,7 +631,7 @@ module.exports = (args, cbk) => {
|
|
|
632
631
|
lnd,
|
|
633
632
|
cltv_delta: defaultCltvDelta,
|
|
634
633
|
description: 'Rebalance',
|
|
635
|
-
tokens:
|
|
634
|
+
tokens: findRoute.route_maximum,
|
|
636
635
|
},
|
|
637
636
|
cbk);
|
|
638
637
|
}],
|
|
@@ -170,6 +170,7 @@ const tests = [
|
|
|
170
170
|
expected: {
|
|
171
171
|
peers: [{
|
|
172
172
|
alias: 'alias',
|
|
173
|
+
est_disk_usage_mb: undefined,
|
|
173
174
|
fee_earnings: undefined,
|
|
174
175
|
downtime_percentage: undefined,
|
|
175
176
|
last_activity: undefined,
|
|
@@ -270,8 +271,9 @@ const tests = [
|
|
|
270
271
|
expected: {
|
|
271
272
|
peers: [{
|
|
272
273
|
alias: 'alias',
|
|
273
|
-
|
|
274
|
+
est_disk_usage_mb: undefined,
|
|
274
275
|
downtime_percentage: undefined,
|
|
276
|
+
fee_earnings: undefined,
|
|
275
277
|
last_activity: undefined,
|
|
276
278
|
inbound_fee_rate: undefined,
|
|
277
279
|
inbound_liquidity: 1,
|