Skip to content

Commit 2aadbad

Browse files
committed
skip real probing to workaround ldk bug
1 parent 10a5777 commit 2aadbad

File tree

1 file changed

+84
-37
lines changed

1 file changed

+84
-37
lines changed

api/lnd/index.js

Lines changed: 84 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { cachedFetcher } from '@/lib/fetch'
22
import { toPositiveNumber } from '@/lib/format'
33
import { authenticatedLndGrpc } from '@/lib/lnd'
4+
import { randomBytes } from 'crypto'
5+
import { chanNumber } from 'bolt07'
46
import { getIdentity, getHeight, getWalletInfo, getNode, getPayment, parsePaymentRequest } from 'ln-service'
57

68
const lnd = global.lnd || authenticatedLndGrpc({
@@ -20,6 +22,59 @@ getWalletInfo({ lnd }, (err, result) => {
2022
console.log('LND GRPC connection successful')
2123
})
2224

25+
// we don't use this because it doesn't solve https://github.com/lightningnetwork/lnd/discussions/10427
26+
// due to this bug, real probes cause the channel to be marked as unusable and the real payment fails
27+
// this should be useful in the future though when need more control over probing
28+
export async function rawProbePayment ({ lnd, request, maxFeeMsat, timeoutSeconds, maxCltvDelta }) {
29+
if (!request) {
30+
throw new Error('Payment request is required')
31+
}
32+
const inv = parsePaymentRequest({ request })
33+
const params = {
34+
allow_self_payment: true,
35+
amt_msat: inv.mtokens,
36+
cancelable: true,
37+
cltv_limit: maxCltvDelta,
38+
dest: Buffer.from(inv.destination, 'hex'),
39+
dest_custom_records: undefined,
40+
dest_features: inv.features.map(n => n.bit),
41+
fee_limit_msat: maxFeeMsat,
42+
final_cltv_delta: inv.cltv_delta,
43+
last_hop_pubkey: undefined,
44+
max_parts: 1,
45+
max_shard_size_msat: undefined,
46+
no_inflight_updates: true,
47+
outgoing_chan_id: undefined,
48+
outgoing_chan_ids: [],
49+
payment_addr: undefined,
50+
payment_hash: randomBytes(32),
51+
payment_request: undefined,
52+
route_hints: inv.routes?.map(r => ({
53+
hop_hints: r.slice(1).map((h, i) => ({
54+
fee_base_msat: h.base_fee_mtokens,
55+
fee_proportional_millionths: h.fee_rate,
56+
chan_id: chanNumber({ channel: h.channel }).number,
57+
cltv_expiry_delta: h.cltv_delta,
58+
node_id: r[i].public_key
59+
}))
60+
})) ?? [],
61+
time_pref: 1,
62+
timeout_seconds: timeoutSeconds
63+
}
64+
return await new Promise((resolve, reject) => {
65+
const sub = lnd.router.sendPaymentV2(params)
66+
sub.on('data', (res) => {
67+
resolve(res)
68+
})
69+
sub.on('error', (err) => {
70+
reject(err)
71+
})
72+
sub.on('end', () => {
73+
reject(new Error('Payment timed out'))
74+
})
75+
})
76+
}
77+
2378
export async function estimateRouteFee ({ lnd, destination, tokens, mtokens, request, timeout }) {
2479
// if the payment request includes us as route hint, we needd to use the destination and amount
2580
// otherwise, this will fail with a self-payment error
@@ -39,50 +94,42 @@ export async function estimateRouteFee ({ lnd, destination, tokens, mtokens, req
3994
}
4095
}
4196
}
97+
// XXX we don't use the payment request anymore because it causes the channel to be marked as unusable
98+
// and the real payment fails see: https://github.com/lightningnetwork/lnd/discussions/10427
99+
// without the request, the estimate is a statistical estimate based on past payments
100+
request = false
42101
}
43102

44-
let failureReason
45-
try {
46-
return await new Promise((resolve, reject) => {
47-
const params = {}
48-
49-
if (request) {
50-
console.log('estimateRouteFee using payment request')
51-
params.payment_request = request
52-
} else {
53-
console.log('estimateRouteFee using destination and amount')
54-
params.dest = Buffer.from(destination, 'hex')
55-
params.amt_sat = tokens ? toPositiveNumber(tokens) : toPositiveNumber(BigInt(mtokens) / BigInt(1e3))
56-
}
103+
return await new Promise((resolve, reject) => {
104+
const params = {}
57105

58-
lnd.router.estimateRouteFee({
59-
...params,
60-
timeout
61-
}, (err, res) => {
62-
if (err) {
63-
return reject(err)
64-
}
106+
if (request) {
107+
console.log('estimateRouteFee using payment request')
108+
params.payment_request = request
109+
} else {
110+
console.log('estimateRouteFee using destination and amount')
111+
params.dest = Buffer.from(destination, 'hex')
112+
params.amt_sat = tokens ? toPositiveNumber(tokens) : toPositiveNumber(BigInt(mtokens) / BigInt(1e3))
113+
}
65114

66-
if (res.failure_reason !== 'FAILURE_REASON_NONE' || res.routing_fee_msat < 0 || res.time_lock_delay <= 0) {
67-
failureReason = res.failure_reason
68-
return reject(new Error(`Unable to estimate route: ${failureReason}`))
69-
}
115+
lnd.router.estimateRouteFee({
116+
...params,
117+
timeout
118+
}, (err, res) => {
119+
if (err) {
120+
return reject(err)
121+
}
70122

71-
resolve({
72-
routingFeeMsat: toPositiveNumber(res.routing_fee_msat),
73-
timeLockDelay: toPositiveNumber(res.time_lock_delay)
74-
})
123+
if (res.failure_reason !== 'FAILURE_REASON_NONE' || res.routing_fee_msat < 0 || res.time_lock_delay <= 0) {
124+
return reject(new Error(`Unable to estimate route: ${res.failure_reason}`))
125+
}
126+
127+
resolve({
128+
routingFeeMsat: toPositiveNumber(res.routing_fee_msat),
129+
timeLockDelay: toPositiveNumber(res.time_lock_delay)
75130
})
76131
})
77-
} catch (err) {
78-
if (request && failureReason === 'FAILURE_REASON_ERROR') {
79-
// try again without the payment request
80-
// there appears to be a compatibility bug when probing ldk nodes with payment requests
81-
// https://github.com/lightningnetwork/lnd/discussions/10427
82-
return await estimateRouteFee({ lnd, destination, tokens, mtokens, timeout })
83-
}
84-
throw err
85-
}
132+
})
86133
}
87134

88135
// created_height is the accepted_height, timeout is the expiry height

0 commit comments

Comments
 (0)