balanceofsatoshis 24.2.1 → 25.0.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 +9 -1
- package/README.md +2 -2
- package/bos +20 -21
- package/commands/interrogate.js +15 -1
- package/network/get_node_pending.js +329 -0
- package/network/get_pending.js +81 -0
- package/network/get_pending_paths.js +96 -0
- package/network/index.js +2 -0
- package/network/pending_payments.js +105 -0
- package/network/pending_summary.js +204 -0
- package/network/probe_destination.js +19 -14
- package/package.json +10 -11
- package/services/accept_balanced_channel.js +45 -44
- package/services/balanced_open_accept_details.js +1 -2
- package/services/initiate_balanced_channel.js +70 -53
- package/services/open_balanced_channel.js +0 -1
- package/services/recover_transit_funds.js +27 -20
- package/swaps/swap_out.js +2 -2
- package/telegram/get_telegram_bot.js +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 25.0.0
|
|
4
|
+
|
|
5
|
+
- `pending`: Add command to show pending channels and in-flight HTLCs
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Remove `price` command
|
|
10
|
+
|
|
11
|
+
## 24.2.2
|
|
4
12
|
|
|
5
13
|
- `open`: Adhere to BIP 69 when ordering funding outputs with equal amounts
|
|
6
14
|
|
package/README.md
CHANGED
|
@@ -147,8 +147,8 @@ bos pay "payment_request"
|
|
|
147
147
|
# Show channel-connected peers
|
|
148
148
|
bos peers
|
|
149
149
|
|
|
150
|
-
#
|
|
151
|
-
bos
|
|
150
|
+
# Show pending channels and in-flight HTLCs
|
|
151
|
+
bos pending
|
|
152
152
|
|
|
153
153
|
# Test if funds can be sent to a destination
|
|
154
154
|
bos probe "payment_request/public_key"
|
package/bos
CHANGED
|
@@ -14,7 +14,6 @@ const {writeFile} = require('fs');
|
|
|
14
14
|
const importLazy = require('import-lazy')(require);
|
|
15
15
|
|
|
16
16
|
const accounting = importLazy('ln-accounting');
|
|
17
|
-
const fiat = importLazy('@alexbosworth/fiat');
|
|
18
17
|
const lnService = importLazy('ln-service');
|
|
19
18
|
const lnSync = importLazy('ln-sync');
|
|
20
19
|
const paidServices = importLazy('paid-services');
|
|
@@ -1613,28 +1612,28 @@ prog
|
|
|
1613
1612
|
});
|
|
1614
1613
|
})
|
|
1615
1614
|
|
|
1616
|
-
// Get
|
|
1617
|
-
.command('
|
|
1618
|
-
.help('
|
|
1619
|
-
.help('
|
|
1620
|
-
.
|
|
1621
|
-
.option('--
|
|
1622
|
-
.option('--from <provider>', 'Rate provider', priceProviders, 'coindesk')
|
|
1615
|
+
// Get pending channels and in-flight HTLCs
|
|
1616
|
+
.command('pending', 'Show pending channels and in-flight HTLCs')
|
|
1617
|
+
.help('Channels waiting to open or close, forwarding and probing HTLCs')
|
|
1618
|
+
.help('Multiple --node arguments are supported to show items across nodes')
|
|
1619
|
+
.help('Use --verbose to also show the raw details of the pending items')
|
|
1620
|
+
.option('--show-relays <count>', 'Relaying peers to show in paths', INT, 0)
|
|
1623
1621
|
.option('--no-color', 'Mute all colors')
|
|
1622
|
+
.option('--node <node_name>', 'Node to get pending items for', REPEATABLE)
|
|
1624
1623
|
.action((args, options, logger) => {
|
|
1625
|
-
return new Promise((resolve, reject) => {
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
resolve
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
}
|
|
1624
|
+
return new Promise(async (resolve, reject) => {
|
|
1625
|
+
try {
|
|
1626
|
+
return network.getPending({
|
|
1627
|
+
logger,
|
|
1628
|
+
fs: {getFile: readFile},
|
|
1629
|
+
is_monochrome: !!options.noColor,
|
|
1630
|
+
lnds: (await lnd.getLnds({logger, nodes: options.node})).lnds,
|
|
1631
|
+
max_relays: options.showRelays,
|
|
1632
|
+
},
|
|
1633
|
+
responses.returnOutput({logger, reject, resolve}));
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
return logger.error({err}) && reject();
|
|
1636
|
+
}
|
|
1638
1637
|
});
|
|
1639
1638
|
})
|
|
1640
1639
|
|
package/commands/interrogate.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const exitCodeSigint = 130;
|
|
2
|
+
const isForceClose = err => !!err && err.name === 'ExitPromptError';
|
|
3
|
+
|
|
1
4
|
/** Get ask function
|
|
2
5
|
|
|
3
6
|
{}
|
|
@@ -9,6 +12,17 @@ module.exports = ({}) => {
|
|
|
9
12
|
return new Promise(async resolve => {
|
|
10
13
|
const inquirer = (await import('inquirer')).default;
|
|
11
14
|
|
|
12
|
-
return resolve((n, cbk) =>
|
|
15
|
+
return resolve((n, cbk) => {
|
|
16
|
+
return inquirer.prompt([n])
|
|
17
|
+
.then(res => cbk(res))
|
|
18
|
+
.catch(err => {
|
|
19
|
+
// Exit quietly when the user presses ctrl+c
|
|
20
|
+
if (isForceClose(err)) {
|
|
21
|
+
return process.exit(exitCodeSigint);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw err;
|
|
25
|
+
});
|
|
26
|
+
});
|
|
13
27
|
});
|
|
14
28
|
};
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncMap = require('async/map');
|
|
3
|
+
const {getChannels} = require('ln-service');
|
|
4
|
+
const {getNodeAlias} = require('ln-sync');
|
|
5
|
+
const {getPendingChannels} = require('ln-service');
|
|
6
|
+
const {getWalletInfo} = require('ln-service');
|
|
7
|
+
const {returnResult} = require('asyncjs-util');
|
|
8
|
+
|
|
9
|
+
const getPendingPaths = require('./get_pending_paths');
|
|
10
|
+
const pendingPayments = require('./pending_payments');
|
|
11
|
+
const {sortBy} = require('./../arrays');
|
|
12
|
+
|
|
13
|
+
const flatten = arr => [].concat(...arr);
|
|
14
|
+
const lookupTimeoutMs = 1000 * 10;
|
|
15
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
16
|
+
|
|
17
|
+
/** Get pending channels and in-flight HTLCs for a node
|
|
18
|
+
|
|
19
|
+
Items are sorted oldest first, by their creation time when it is known and
|
|
20
|
+
otherwise by the blocks remaining until they resolve
|
|
21
|
+
|
|
22
|
+
Debug details of pending channels, HTLCs and payments are logged in verbose
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
lnd: <Authenticated LND API Object>
|
|
26
|
+
logger: <Winston Logger Object>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@returns via cbk or Promise
|
|
30
|
+
{
|
|
31
|
+
closing: [{
|
|
32
|
+
[blocks_until_recovery]: <Blocks Until Pending Balance Recovers Number>
|
|
33
|
+
[close_transaction_id]: <Closing Transaction Id Hex String>
|
|
34
|
+
[peer_alias]: <Peer Alias String>
|
|
35
|
+
peer_public_key: <Peer Public Key Hex String>
|
|
36
|
+
[pending_balance]: <Pending Recovery Tokens Number>
|
|
37
|
+
}]
|
|
38
|
+
forwarding: [{
|
|
39
|
+
fee: <Routing Fee Tokens Number>
|
|
40
|
+
[in_peer_alias]: <Inbound Peer Alias String>
|
|
41
|
+
in_peer_public_key: <Inbound Peer Public Key Hex String>
|
|
42
|
+
[out_peer_alias]: <Outbound Peer Alias String>
|
|
43
|
+
out_peer_public_key: <Outbound Peer Public Key Hex String>
|
|
44
|
+
tokens: <Forwarding Tokens Number>
|
|
45
|
+
}]
|
|
46
|
+
opening: [{
|
|
47
|
+
capacity: <Channel Capacity Tokens Number>
|
|
48
|
+
is_partner_initiated: <Peer Is Opening The Channel Bool>
|
|
49
|
+
[peer_alias]: <Peer Alias String>
|
|
50
|
+
peer_public_key: <Peer Public Key Hex String>
|
|
51
|
+
transaction_id: <Funding Transaction Id Hex String>
|
|
52
|
+
}]
|
|
53
|
+
sending: [{
|
|
54
|
+
[fee]: <Routing Fee Tokens Number>
|
|
55
|
+
hops: [{
|
|
56
|
+
[alias]: <Hop Node Alias String>
|
|
57
|
+
public_key: <Hop Node Public Key Hex String>
|
|
58
|
+
}]
|
|
59
|
+
is_rebalance: <Payment Is Going Back To The Node Itself Bool>
|
|
60
|
+
tokens: <Sending Tokens Number>
|
|
61
|
+
}]
|
|
62
|
+
[alias]: <Node Alias String>
|
|
63
|
+
public_key: <Node Identity Public Key Hex String>
|
|
64
|
+
}
|
|
65
|
+
*/
|
|
66
|
+
module.exports = ({lnd, logger}, cbk) => {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
return asyncAuto({
|
|
69
|
+
// Check arguments
|
|
70
|
+
validate: cbk => {
|
|
71
|
+
if (!lnd) {
|
|
72
|
+
return cbk([400, 'ExpectedAuthenticatedLndToGetNodePendingItems']);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!logger) {
|
|
76
|
+
return cbk([400, 'ExpectedLoggerToGetNodePendingItems']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return cbk();
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Get channels to look for in-flight HTLCs
|
|
83
|
+
getChannels: ['validate', ({}, cbk) => getChannels({lnd}, cbk)],
|
|
84
|
+
|
|
85
|
+
// Get the node identity
|
|
86
|
+
getInfo: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
|
87
|
+
|
|
88
|
+
// Get pending channels
|
|
89
|
+
getPending: ['validate', ({}, cbk) => getPendingChannels({lnd}, cbk)],
|
|
90
|
+
|
|
91
|
+
// Derive the HTLCs that are forwarding or sending
|
|
92
|
+
htlcs: ['getChannels', ({getChannels}, cbk) => {
|
|
93
|
+
return cbk(null, pendingPayments({channels: getChannels.channels}));
|
|
94
|
+
}],
|
|
95
|
+
|
|
96
|
+
// Look up the in-flight payments behind the outgoing HTLCs
|
|
97
|
+
getPayments: ['htlcs', ({htlcs}, cbk) => {
|
|
98
|
+
return asyncMap(uniq(htlcs.sending.map(n => n.id)), (id, cbk) => {
|
|
99
|
+
return getPendingPaths({id, lnd, timeout_ms: lookupTimeoutMs}, cbk);
|
|
100
|
+
},
|
|
101
|
+
cbk);
|
|
102
|
+
}],
|
|
103
|
+
|
|
104
|
+
// Log the raw details behind the pending items for debugging
|
|
105
|
+
debug: [
|
|
106
|
+
'getPayments',
|
|
107
|
+
'getPending',
|
|
108
|
+
'htlcs',
|
|
109
|
+
({getPayments, getPending, htlcs}, cbk) =>
|
|
110
|
+
{
|
|
111
|
+
const hasHtlcs = !!htlcs.forwarding.length || !!htlcs.sending.length;
|
|
112
|
+
|
|
113
|
+
// Exit early when there is nothing pending to debug
|
|
114
|
+
if (!hasHtlcs && !getPending.pending_channels.length) {
|
|
115
|
+
return cbk();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.debug({
|
|
119
|
+
forwarding_htlcs: htlcs.forwarding,
|
|
120
|
+
outgoing_htlcs: htlcs.sending,
|
|
121
|
+
payments: getPayments.map(payment => ({
|
|
122
|
+
id: payment.id,
|
|
123
|
+
created_at: payment.created_at,
|
|
124
|
+
paths: payment.paths.map(path => ({
|
|
125
|
+
hops: path.hops.map(hop => `${hop.channel} ${hop.public_key}`),
|
|
126
|
+
timeout: path.timeout,
|
|
127
|
+
tokens: path.tokens,
|
|
128
|
+
})),
|
|
129
|
+
})),
|
|
130
|
+
pending_channels: getPending.pending_channels.map(channel => ({
|
|
131
|
+
blocks_until_expiry: channel.blocks_until_expiry,
|
|
132
|
+
capacity: channel.capacity,
|
|
133
|
+
close_transaction_id: channel.close_transaction_id,
|
|
134
|
+
is_closing: channel.is_closing,
|
|
135
|
+
is_opening: channel.is_opening,
|
|
136
|
+
is_partner_initiated: channel.is_partner_initiated,
|
|
137
|
+
is_timelocked: channel.is_timelocked,
|
|
138
|
+
local_balance: channel.local_balance,
|
|
139
|
+
partner_public_key: channel.partner_public_key,
|
|
140
|
+
pending_balance: channel.pending_balance,
|
|
141
|
+
pending_payments: channel.pending_payments,
|
|
142
|
+
remote_balance: channel.remote_balance,
|
|
143
|
+
timelock_blocks: channel.timelock_blocks,
|
|
144
|
+
timelock_expiration: channel.timelock_expiration,
|
|
145
|
+
transaction_id: channel.transaction_id,
|
|
146
|
+
transaction_vout: channel.transaction_vout,
|
|
147
|
+
})),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return cbk();
|
|
151
|
+
}],
|
|
152
|
+
|
|
153
|
+
// Outgoing HTLCs are matched to the paths of their in-flight payments
|
|
154
|
+
routes: ['getPayments', 'htlcs', ({getPayments, htlcs}, cbk) => {
|
|
155
|
+
const paths = flatten(getPayments.map(payment => {
|
|
156
|
+
return payment.paths.map(path => ({
|
|
157
|
+
path,
|
|
158
|
+
created_at: payment.created_at,
|
|
159
|
+
id: payment.id,
|
|
160
|
+
}));
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
// Every path can only be the route of a single outgoing HTLC
|
|
164
|
+
const {routes} = htlcs.sending.reduce((sum, htlc) => {
|
|
165
|
+
// An HTLC has the hash, first hop, amount and expiry of its route
|
|
166
|
+
const match = sum.unmatched.find(({id, path}) => {
|
|
167
|
+
const [firstHop] = path.hops;
|
|
168
|
+
|
|
169
|
+
const isSameAmount = path.tokens === htlc.tokens;
|
|
170
|
+
const isSameExpiry = path.timeout === htlc.timeout;
|
|
171
|
+
const isSameHash = id === htlc.id;
|
|
172
|
+
const isSameOutPeer = firstHop.public_key === htlc.out_peer;
|
|
173
|
+
|
|
174
|
+
return isSameHash && isSameAmount && isSameExpiry && isSameOutPeer;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Exit early when there are no payment details for the HTLC
|
|
178
|
+
if (!match) {
|
|
179
|
+
const hop = {forward: htlc.tokens, public_key: htlc.out_peer};
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
routes: sum.routes.concat({hops: [hop]}),
|
|
183
|
+
unmatched: sum.unmatched,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const route = {
|
|
188
|
+
created_at: match.created_at,
|
|
189
|
+
fee: match.path.fee,
|
|
190
|
+
hops: match.path.hops,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
routes: sum.routes.concat(route),
|
|
195
|
+
unmatched: sum.unmatched.filter(n => n !== match),
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
{routes: [], unmatched: paths});
|
|
199
|
+
|
|
200
|
+
return cbk(null, routes);
|
|
201
|
+
}],
|
|
202
|
+
|
|
203
|
+
// Get the aliases of the peers involved in pending items
|
|
204
|
+
getAliases: [
|
|
205
|
+
'getPending',
|
|
206
|
+
'htlcs',
|
|
207
|
+
'routes',
|
|
208
|
+
({getPending, htlcs, routes}, cbk) =>
|
|
209
|
+
{
|
|
210
|
+
const hops = flatten(routes.map(n => n.hops));
|
|
211
|
+
|
|
212
|
+
const peers = []
|
|
213
|
+
.concat(getPending.pending_channels.map(n => n.partner_public_key))
|
|
214
|
+
.concat(htlcs.forwarding.map(n => n.in_peer))
|
|
215
|
+
.concat(htlcs.forwarding.map(n => n.out_peer))
|
|
216
|
+
.concat(hops.map(n => n.public_key));
|
|
217
|
+
|
|
218
|
+
return asyncMap(uniq(peers), (id, cbk) => {
|
|
219
|
+
return getNodeAlias({id, lnd}, cbk);
|
|
220
|
+
},
|
|
221
|
+
cbk);
|
|
222
|
+
}],
|
|
223
|
+
|
|
224
|
+
// Assemble the pending items
|
|
225
|
+
pending: [
|
|
226
|
+
'getAliases',
|
|
227
|
+
'getInfo',
|
|
228
|
+
'getPending',
|
|
229
|
+
'htlcs',
|
|
230
|
+
'routes',
|
|
231
|
+
({getAliases, getInfo, getPending, htlcs, routes}, cbk) =>
|
|
232
|
+
{
|
|
233
|
+
const alias = id => {
|
|
234
|
+
return getAliases.find(n => n.id === id).alias || undefined;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const pending = getPending.pending_channels;
|
|
238
|
+
|
|
239
|
+
// Closes waiting on a timelock are ordered by their remaining blocks
|
|
240
|
+
const timelocked = sortBy({
|
|
241
|
+
array: pending.filter(n => !!n.is_closing && !!n.is_timelocked),
|
|
242
|
+
attribute: 'timelock_blocks',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Closes that are not timelocked yet are the newest closes
|
|
246
|
+
const waiting = pending.filter(n => n.is_closing && !n.is_timelocked);
|
|
247
|
+
|
|
248
|
+
const closes = [].concat(timelocked.sorted).concat(waiting);
|
|
249
|
+
|
|
250
|
+
// Channels that are closing, which may be waiting on funds recovery
|
|
251
|
+
const closing = closes.map(channel => {
|
|
252
|
+
// A timelock may hold the funds for some blocks before recovery
|
|
253
|
+
const blocks = channel.timelock_blocks || Number();
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
blocks_until_recovery: blocks > Number() ? blocks : undefined,
|
|
257
|
+
close_transaction_id: channel.close_transaction_id,
|
|
258
|
+
peer_alias: alias(channel.partner_public_key),
|
|
259
|
+
peer_public_key: channel.partner_public_key,
|
|
260
|
+
pending_balance: channel.pending_balance || undefined,
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// HTLCs that are being relayed from one peer to another
|
|
265
|
+
const forwards = sortBy({
|
|
266
|
+
array: htlcs.forwarding,
|
|
267
|
+
attribute: 'timeout',
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const forwarding = forwards.sorted.map(forward => ({
|
|
271
|
+
fee: forward.fee,
|
|
272
|
+
in_peer_alias: alias(forward.in_peer),
|
|
273
|
+
in_peer_public_key: forward.in_peer,
|
|
274
|
+
out_peer_alias: alias(forward.out_peer),
|
|
275
|
+
out_peer_public_key: forward.out_peer,
|
|
276
|
+
tokens: forward.tokens,
|
|
277
|
+
}));
|
|
278
|
+
|
|
279
|
+
// Channels that are waiting for funding confirmation
|
|
280
|
+
const opens = sortBy({
|
|
281
|
+
array: pending.filter(n => !!n.is_opening),
|
|
282
|
+
attribute: 'blocks_until_expiry',
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const opening = opens.sorted.map(channel => ({
|
|
286
|
+
capacity: channel.capacity,
|
|
287
|
+
is_partner_initiated: channel.is_partner_initiated,
|
|
288
|
+
peer_alias: alias(channel.partner_public_key),
|
|
289
|
+
peer_public_key: channel.partner_public_key,
|
|
290
|
+
transaction_id: channel.transaction_id,
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
// Payments with known creation dates are ordered by their dates
|
|
294
|
+
const dated = sortBy({
|
|
295
|
+
array: routes.filter(n => !!n.created_at),
|
|
296
|
+
attribute: 'created_at',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// HTLCs that have no payment details are the newest payments
|
|
300
|
+
const undated = routes.filter(n => !n.created_at);
|
|
301
|
+
|
|
302
|
+
// Payments or probes that are going out through peers
|
|
303
|
+
const sending = [].concat(dated.sorted).concat(undated).map(route => {
|
|
304
|
+
const [finalHop] = route.hops.slice().reverse();
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
fee: route.fee,
|
|
308
|
+
hops: route.hops.map(hop => ({
|
|
309
|
+
alias: alias(hop.public_key),
|
|
310
|
+
public_key: hop.public_key,
|
|
311
|
+
})),
|
|
312
|
+
is_rebalance: finalHop.public_key === getInfo.public_key,
|
|
313
|
+
tokens: finalHop.forward,
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return cbk(null, {
|
|
318
|
+
closing,
|
|
319
|
+
forwarding,
|
|
320
|
+
opening,
|
|
321
|
+
sending,
|
|
322
|
+
alias: getInfo.alias || undefined,
|
|
323
|
+
public_key: getInfo.public_key,
|
|
324
|
+
});
|
|
325
|
+
}],
|
|
326
|
+
},
|
|
327
|
+
returnResult({reject, resolve, of: 'pending'}, cbk));
|
|
328
|
+
});
|
|
329
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncMap = require('async/map');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const {getIcons} = require('./../display');
|
|
6
|
+
const getNodePending = require('./get_node_pending');
|
|
7
|
+
const pendingSummary = require('./pending_summary');
|
|
8
|
+
|
|
9
|
+
const {isArray} = Array;
|
|
10
|
+
|
|
11
|
+
/** Get a summary of pending channels and in-flight HTLCs
|
|
12
|
+
|
|
13
|
+
When there are multiple nodes, pending items are grouped by node
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
fs: {
|
|
17
|
+
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
18
|
+
}
|
|
19
|
+
[is_monochrome]: <Mute Colors Bool>
|
|
20
|
+
lnds: [<Authenticated LND API Object>]
|
|
21
|
+
logger: <Winston Logger Object>
|
|
22
|
+
[max_relays]: <Maximum Relaying Peers of a Path to Show Number>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@returns via cbk or Promise
|
|
26
|
+
<Pending Items Summary String>
|
|
27
|
+
*/
|
|
28
|
+
module.exports = (args, cbk) => {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
return asyncAuto({
|
|
31
|
+
// Check arguments
|
|
32
|
+
validate: cbk => {
|
|
33
|
+
if (!args.fs) {
|
|
34
|
+
return cbk([400, 'ExpectedFileSystemMethodsToGetPendingItems']);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!isArray(args.lnds)) {
|
|
38
|
+
return cbk([400, 'ExpectedArrayOfLndsToGetPendingItems']);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!args.lnds.length) {
|
|
42
|
+
return cbk([400, 'ExpectedAnLndToGetPendingItems']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!args.logger) {
|
|
46
|
+
return cbk([400, 'ExpectedLoggerToGetPendingItems']);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (args.max_relays !== undefined && args.max_relays < 0) {
|
|
50
|
+
return cbk([400, 'ExpectedNonNegativeMaxRelaysCountToShow']);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return cbk();
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Get the tag icons for nodes
|
|
57
|
+
getIcons: ['validate', ({}, cbk) => getIcons({fs: args.fs}, cbk)],
|
|
58
|
+
|
|
59
|
+
// Get the pending items for every node
|
|
60
|
+
getNodes: ['validate', ({}, cbk) => {
|
|
61
|
+
return asyncMap(args.lnds, (lnd, cbk) => {
|
|
62
|
+
return getNodePending({lnd, logger: args.logger}, cbk);
|
|
63
|
+
},
|
|
64
|
+
cbk);
|
|
65
|
+
}],
|
|
66
|
+
|
|
67
|
+
// Summarize the pending items
|
|
68
|
+
summary: ['getIcons', 'getNodes', ({getIcons, getNodes}, cbk) => {
|
|
69
|
+
const {summary} = pendingSummary({
|
|
70
|
+
is_monochrome: args.is_monochrome,
|
|
71
|
+
max_relays: args.max_relays,
|
|
72
|
+
nodes: getNodes,
|
|
73
|
+
tagged: getIcons.nodes,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return cbk(null, summary);
|
|
77
|
+
}],
|
|
78
|
+
},
|
|
79
|
+
returnResult({reject, resolve, of: 'summary'}, cbk));
|
|
80
|
+
});
|
|
81
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
const {subscribeToPastPayment} = require('ln-service');
|
|
4
|
+
|
|
5
|
+
const isHash = n => !!n && /^[0-9A-F]{64}$/i.test(n);
|
|
6
|
+
const notFound = 404;
|
|
7
|
+
|
|
8
|
+
/** Get the pending paths of an in-flight payment
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
id: <Payment Hash Hex String>
|
|
12
|
+
lnd: <Authenticated LND API Object>
|
|
13
|
+
timeout_ms: <Give Up Waiting For Payment Details After Milliseconds Number>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns via cbk or Promise
|
|
17
|
+
{
|
|
18
|
+
[created_at]: <Payment Created At ISO 8601 Date String>
|
|
19
|
+
id: <Payment Hash Hex String>
|
|
20
|
+
paths: [{
|
|
21
|
+
fee: <Total Fee Tokens Pending Number>
|
|
22
|
+
hops: [{
|
|
23
|
+
channel: <Standard Format Channel Id String>
|
|
24
|
+
forward: <Forward Tokens Number>
|
|
25
|
+
public_key: <Public Key Hex String>
|
|
26
|
+
}]
|
|
27
|
+
timeout: <Expiration Block Height Number>
|
|
28
|
+
tokens: <Total Tokens Pending Number>
|
|
29
|
+
}]
|
|
30
|
+
}
|
|
31
|
+
*/
|
|
32
|
+
module.exports = ({id, lnd, timeout_ms}, cbk) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
return asyncAuto({
|
|
35
|
+
// Check arguments
|
|
36
|
+
validate: cbk => {
|
|
37
|
+
if (!isHash(id)) {
|
|
38
|
+
return cbk([400, 'ExpectedPaymentHashToGetPendingPaths']);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!lnd) {
|
|
42
|
+
return cbk([400, 'ExpectedAuthenticatedLndToGetPendingPaths']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!timeout_ms) {
|
|
46
|
+
return cbk([400, 'ExpectedTimeoutMillisecondsToGetPendingPaths']);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return cbk();
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Subscribe to the payment to get its current state
|
|
53
|
+
getPaths: ['validate', ({}, cbk) => {
|
|
54
|
+
const sub = subscribeToPastPayment({id, lnd});
|
|
55
|
+
|
|
56
|
+
// Removing the listeners cancels the subscription to the payment
|
|
57
|
+
const finished = (err, res) => {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
|
|
60
|
+
sub.removeAllListeners();
|
|
61
|
+
|
|
62
|
+
return cbk(err, res);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Payments that are not in flight have no pending paths
|
|
66
|
+
const noPaths = () => finished(null, {id, paths: []});
|
|
67
|
+
|
|
68
|
+
const timeout = setTimeout(noPaths, timeout_ms);
|
|
69
|
+
|
|
70
|
+
sub.once('confirmed', noPaths);
|
|
71
|
+
sub.once('end', noPaths);
|
|
72
|
+
sub.once('failed', noPaths);
|
|
73
|
+
|
|
74
|
+
sub.once('error', err => {
|
|
75
|
+
// Exit early when the payment is not a known payment
|
|
76
|
+
if (err[0] === notFound) {
|
|
77
|
+
return noPaths();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return finished(err);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
sub.once('paying', pending => {
|
|
84
|
+
return finished(null, {
|
|
85
|
+
id,
|
|
86
|
+
created_at: pending.created_at,
|
|
87
|
+
paths: pending.paths,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return;
|
|
92
|
+
}],
|
|
93
|
+
},
|
|
94
|
+
returnResult({reject, resolve, of: 'getPaths'}, cbk));
|
|
95
|
+
});
|
|
96
|
+
};
|
package/network/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const executeProbe = require('./execute_probe');
|
|
|
3
3
|
const getForwards = require('./get_forwards');
|
|
4
4
|
const getGraphEntry = require('./get_graph_entry');
|
|
5
5
|
const getPeers = require('./get_peers');
|
|
6
|
+
const getPending = require('./get_pending');
|
|
6
7
|
const multiPathPayment = require('./multi_path_payment');
|
|
7
8
|
const multiPathProbe = require('./multi_path_probe');
|
|
8
9
|
const networks = require('./networks');
|
|
@@ -24,6 +25,7 @@ module.exports = {
|
|
|
24
25
|
getForwards,
|
|
25
26
|
getGraphEntry,
|
|
26
27
|
getPeers,
|
|
28
|
+
getPending,
|
|
27
29
|
multiPathPayment,
|
|
28
30
|
multiPathProbe,
|
|
29
31
|
networks,
|