-
Notifications
You must be signed in to change notification settings - Fork 3
/
monitorPeers.js
212 lines (183 loc) Β· 7.1 KB
/
monitorPeers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// logs peer disconnects/connects and graph policy updates (fee rates n stuff) in our channels
// also logs forwarding successes and failures w/ reason if provided
import fs from 'fs'
import bos from './bos.js'
const { lnService } = bos
// --- settings ---
const LOG_FILE_PATH = 'events.log'
// this filters what htlc fail events will be shown
const WHEN_LOG_FAILED_HTLCS = forward =>
// must have reason or no point displaying
(forward.external_failure || forward.internal_failure) &&
// potential probe? so canceled rather than fails
forward.internal_failure !== 'UNKNOWN_INVOICE'
// potential probe? so canceled rather than fails
// forward.internal_failure !== 'INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS'
// shows forwards that confirm
const LOG_SUCCESSFUL_FORWARDS = false
// sometimes just timestamp is updated, this ignores those gossip updates
const IGNORE_GOSSIP_UPDATES_WITHOUT_SETTING_CHANGES = true
// --- end of settings ---
const run = async () => {
const lnd = await bos.initializeAuth()
const { public_key } = await lnService.getIdentity({ lnd })
const publicKeyToAlias = await bos.getPublicKeyToAliasTable()
const idToAlias = await bos.getIdToAliasTable()
let lastPolicies = await bos.getNodeChannels()
// subscriptions
const graphEvents = await lnService.subscribeToGraph({ lnd })
const peerEvents = await lnService.subscribeToPeers({ lnd })
const forwardEvents = await lnService.subscribeToForwards({ lnd })
// what to do on events for graph (that includes my node)
graphEvents.on('channel_updated', async update => {
if (!update.public_keys.includes(public_key)) return null
const remote_key = update.public_keys.find(v => v !== public_key)
const [announcing_key] = update.public_keys // first key announces
const whoUpdated = announcing_key === public_key ? 'local' : 'remote'
// get this side's last state
const before = lastPolicies[update.id]?.[whoUpdated]
// summarize changes
// https://github.com/alexbosworth/ln-service#subscribetograph
const updates = []
const changes = {}
for (const prop of [
'base_fee_mtokens',
'cltv_delta',
'fee_rate',
'is_disabled',
'max_htlc_mtokens',
'min_htlc_mtokens',
'updated_at'
]) {
if (before?.[prop] !== update[prop]) {
updates.push(`${prop}: ${pretty(before?.[prop])} -> ${pretty(update[prop])}`)
changes[prop] = true
}
}
if (IGNORE_GOSSIP_UPDATES_WITHOUT_SETTING_CHANGES && updates.length <= 1) return null // just updated timestamp changed
// check if peer is online if disable status changed
const peer = changes.is_disabled ? (await bos.peers({}))?.find(p => p.public_key === remote_key) : null
const offlineStatus = !peer ? '' : peer.is_offline ? '(offline)' : '(online)'
log(
`π£ ${whoUpdated} update for peer`,
publicKeyToAlias[remote_key],
remote_key,
offlineStatus,
'\n ',
updates.join('\n ')
)
// update policy data
lastPolicies = await bos.getNodeChannels()
})
graphEvents.on('error', () => {
log('peer events error')
process.exit(1)
})
// what to do on events for peers
peerEvents.on('connected', update => {
log(`π connected to ${publicKeyToAlias[update.public_key] ?? 'unknown'}`, update.public_key)
})
peerEvents.on('disconnected', update => {
log(`β disconnected from ${publicKeyToAlias[update.public_key] ?? 'unknown'}`, update.public_key)
})
peerEvents.on('error', () => {
log('peer events error')
process.exit(1)
})
// what to do for forwards
const pastForwardEvents = {}
forwardEvents.on('forward', f => {
// have to store forwarding events amounts under unique id
// if just starting to listen, likely will be missing past payment information
const fid = `${f.in_channel} ${f.in_payment} ${f.out_channel} ${f.out_payment}`
if (!pastForwardEvents[fid]) pastForwardEvents[fid] = {}
if (f.mtokens) pastForwardEvents[fid].mtokens = f.mtokens
if (f.fee_mtokens) pastForwardEvents[fid].fee_mtokens = f.fee_mtokens
// try to get amount from previous events bc geniuses didn't include that every time
const mtokens = f.mtokens ?? pastForwardEvents[fid]?.mtokens
const fee_mtokens = f.fee_mtokens ?? pastForwardEvents[fid]?.fee_mtokens
const from = idToAlias[f.in_channel] ?? f.in_channel ?? 'n/a'
const to = idToAlias[f.out_channel] ?? f.out_channel ?? 'n/a'
const amt = mtokens !== undefined ? `${pretty(+mtokens / 1000, 3)} sats` : 'n/a'
const fee = fee_mtokens !== undefined ? `${pretty(+fee_mtokens / 1000, 3)} sats fee` : 'n/a'
// done: failures
if (f.is_failed) {
// this checks rule on which forwards to show
if (WHEN_LOG_FAILED_HTLCS(f)) {
const msg = [`π¨ forward failure: ${from} -> ${to} of ${amt} for ${fee}`]
if (f.internal_failure && f.internal_failure !== 'NO_DETAIL') {
msg.push(` π© internal failure: ${f.internal_failure}`)
}
if (f.external_failure && f.external_failure !== 'NO_DETAIL') {
msg.push(` π€‘ external failure: ${f.external_failure}`)
}
log(msg.join('\n'))
}
delete pastForwardEvents[fid] // clear up memory
return null
}
// done: success
if (f.is_confirmed) {
if (LOG_SUCCESSFUL_FORWARDS) {
log(`β‘ forward success: ${from} -> ${to} of ${amt} for ${fee}`)
}
delete pastForwardEvents[fid] // clear up memory
return null
}
// unresolved forwards with defined path
if (f.in_channel && f.out_channel) {
log(`π forward pending: ${from} -> ${to} of ${amt} for ${fee}`)
// console.log(f)
}
// just in case too many fids in memory clean it all up above some limit
if (Object.keys(pastForwardEvents).length >= 555) {
Object.keys(pastForwardEvents).forEach(key => delete pastForwardEvents[key])
log('forward id number limit hit, cleaning up RAM')
}
})
forwardEvents.on('error', () => {
log('forward events error')
process.exit(1)
})
log('listening for events...')
}
const log = (...args) =>
setImmediate(() => {
const msg = [getDate(), ...args].join(' ') // , '\n'
console.log(msg)
fs.appendFileSync(LOG_FILE_PATH, msg + '\n')
})
const getDate = () => new Date().toISOString().replace('T', ' ').replace('Z', '')
const pretty = (n, L = 0) => {
if (isNaN(n)) return n
return String((+n || 0).toFixed(L)).replace(/\B(?=(\d{3})+\b)/g, '_')
}
run()
/*
https://github.com/lightningnetwork/lnd/blob/master/lnrpc/routerrpc/router.proto#L651
enum FailureDetail {
UNKNOWN = 0;
NO_DETAIL = 1;
ONION_DECODE = 2;
LINK_NOT_ELIGIBLE = 3;
ON_CHAIN_TIMEOUT = 4;
HTLC_EXCEEDS_MAX = 5;
INSUFFICIENT_BALANCE = 6;
INCOMPLETE_FORWARD = 7;
HTLC_ADD_FAILED = 8;
FORWARDS_DISABLED = 9;
INVOICE_CANCELED = 10;
INVOICE_UNDERPAID = 11;
INVOICE_EXPIRY_TOO_SOON = 12;
INVOICE_NOT_OPEN = 13;
MPP_INVOICE_TIMEOUT = 14;
ADDRESS_MISMATCH = 15;
SET_TOTAL_MISMATCH = 16;
SET_TOTAL_TOO_LOW = 17;
SET_OVERPAID = 18;
UNKNOWN_INVOICE = 19;
INVALID_KEYSEND = 20;
MPP_IN_PROGRESS = 21;
CIRCULAR_ROUTE = 22;
}
*/