Skip to content

Commit a940c73

Browse files
committed
use real probe when doing wrap fee estimation
1 parent ec840c8 commit a940c73

File tree

2 files changed

+28
-25
lines changed

2 files changed

+28
-25
lines changed

api/lnd/index.js

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { toPositiveNumber } from '@/lib/format'
33
import { authenticatedLndGrpc } from '@/lib/lnd'
44
import { randomBytes } from 'crypto'
55
import { chanNumber } from 'bolt07'
6+
import { once } from 'events'
67
import { getIdentity, getHeight, getWalletInfo, getNode, getPayment, parsePaymentRequest } from 'ln-service'
78

89
const lnd = global.lnd || authenticatedLndGrpc({
@@ -22,10 +23,8 @@ getWalletInfo({ lnd }, (err, result) => {
2223
console.log('LND GRPC connection successful')
2324
})
2425

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 }) {
26+
// we create our own probe because estimateRouteFee is busted https://github.com/lightningnetwork/lnd/discussions/10427
27+
export async function estimateRouteFeeProbe ({ lnd, request, maxFeeMsat, timeoutSeconds, maxCltvDelta }) {
2928
if (!request) {
3029
throw new Error('Payment request is required')
3130
}
@@ -34,11 +33,11 @@ export async function rawProbePayment ({ lnd, request, maxFeeMsat, timeoutSecond
3433
allow_self_payment: true,
3534
amt_msat: inv.mtokens,
3635
cancelable: true,
37-
cltv_limit: maxCltvDelta,
36+
cltv_limit: maxCltvDelta ? toPositiveNumber(maxCltvDelta) : undefined,
3837
dest: Buffer.from(inv.destination, 'hex'),
3938
dest_custom_records: undefined,
4039
dest_features: inv.features.map(n => n.bit),
41-
fee_limit_msat: maxFeeMsat,
40+
fee_limit_msat: maxFeeMsat ? toPositiveNumber(maxFeeMsat) : undefined,
4241
final_cltv_delta: inv.cltv_delta,
4342
last_hop_pubkey: undefined,
4443
max_parts: 1,
@@ -47,7 +46,7 @@ export async function rawProbePayment ({ lnd, request, maxFeeMsat, timeoutSecond
4746
outgoing_chan_id: undefined,
4847
outgoing_chan_ids: [],
4948
payment_addr: Buffer.from(inv.payment, 'hex'),
50-
payment_hash: randomBytes(32),
49+
payment_hash: randomBytes(32), // this is what makes it a probe
5150
payment_request: undefined,
5251
route_hints: inv.routes?.map(r => ({
5352
hop_hints: r.slice(1).map((h, i) => ({
@@ -59,20 +58,23 @@ export async function rawProbePayment ({ lnd, request, maxFeeMsat, timeoutSecond
5958
}))
6059
})) ?? [],
6160
time_pref: 1,
62-
timeout_seconds: timeoutSeconds
61+
timeout_seconds: timeoutSeconds ? toPositiveNumber(timeoutSeconds) : undefined
6362
}
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-
})
63+
const sub = lnd.router.sendPaymentV2(params)
64+
const [probe] = await once(sub, 'data')
65+
66+
// a successful probe returns FAILURE_REASON_INCORRECT_PAYMENT_DETAILS
67+
if (probe.failure_reason === 'FAILURE_REASON_INCORRECT_PAYMENT_DETAILS') {
68+
// there's only one htlc in the probe, because max_parts is 1
69+
const { htlcs: [{ route: { total_fees_msat: routingFeeMsat, total_time_lock: timeLockDelay } }] } = probe
70+
return {
71+
routingFeeMsat: toPositiveNumber(routingFeeMsat),
72+
// because we are simulating estimateRouteFee's probe, we remove the final hop's cltv_delta
73+
timeLockDelay: toPositiveNumber(timeLockDelay - inv.cltv_delta)
74+
}
75+
}
76+
77+
throw new Error(`Unable to estimate route: ${probe.failure_reason || 'unknown reason'}`)
7678
}
7779

7880
export async function estimateRouteFee ({ lnd, destination, tokens, mtokens, request, timeout }) {
@@ -97,6 +99,7 @@ export async function estimateRouteFee ({ lnd, destination, tokens, mtokens, req
9799
// XXX we don't use the payment request anymore because it causes the channel to be marked as unusable
98100
// and the real payment fails see: https://github.com/lightningnetwork/lnd/discussions/10427
99101
// without the request, the estimate is a statistical estimate based on past payments
102+
// NOTE: this should be fixed in v0.20.1-beta
100103
request = false
101104
}
102105

wallets/server/wrap.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createHodlInvoice, parsePaymentRequest } from 'ln-service'
2-
import lnd, { estimateRouteFee, getBlockHeight } from '@/api/lnd'
2+
import lnd, { estimateRouteFeeProbe, getBlockHeight } from '@/api/lnd'
33
import { toPositiveBigInt, toPositiveNumber } from '@/lib/format'
44
import { PayInFailureReasonError } from '@/api/payIn/errors'
55

@@ -145,12 +145,12 @@ async function wrapBolt11Params ({ msats, bolt11, maxRoutingFeeMsats, hideInvoic
145145

146146
// get routing estimates
147147
const { routingFeeMsat, timeLockDelay } =
148-
await estimateRouteFee({
148+
await estimateRouteFeeProbe({
149149
lnd,
150-
destination: inv.destination,
151-
mtokens: inv.mtokens,
152150
request: bolt11,
153-
timeout: FEE_ESTIMATE_TIMEOUT_SECS
151+
maxFeeMsat: maxRoutingFeeMsats,
152+
timeoutSeconds: FEE_ESTIMATE_TIMEOUT_SECS,
153+
maxCltvDelta: MAX_OUTGOING_CLTV_DELTA
154154
})
155155

156156
const blockHeight = await getBlockHeight({ lnd })

0 commit comments

Comments
 (0)