Skip to content

Commit 965b4d4

Browse files
committed
fix: tune lightning probe routing
1 parent 4deacd5 commit 965b4d4

10 files changed

Lines changed: 307 additions & 36 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ google-services.json
2626
!debug.keystore
2727
keystore.*
2828
!keystore.properties.template
29+
.vscode

app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.net.Uri
66
import android.os.Binder
77
import android.os.Bundle
88
import android.os.Process
9+
import android.os.SystemClock
910
import androidx.core.os.bundleOf
1011
import dagger.hilt.EntryPoint
1112
import dagger.hilt.InstallIn
@@ -114,16 +115,19 @@ private sealed interface DevCommand {
114115
context = TAG,
115116
)
116117

118+
val startedAt = SystemClock.elapsedRealtime()
117119
return deps.lightningRepo().sendProbeForInvoice(args.bolt11, amountSats)
118120
.fold(
119121
onSuccess = {
120122
deps.lightningRepo().waitForProbeOutcome(it.paymentIds, timeout)
121123
.fold(
122-
onSuccess = { outcome -> outcome.toDevResult(it.paymentIds) },
123-
onFailure = { error -> DevResult.ProbeFailure.from(error, it.paymentIds) },
124+
onSuccess = { outcome -> outcome.toDevResult(it.paymentIds, startedAt.durationMs()) },
125+
onFailure = { error ->
126+
DevResult.ProbeFailure.from(error, it.paymentIds, startedAt.durationMs())
127+
},
124128
)
125129
},
126-
onFailure = { DevResult.ProbeFailure.from(it) },
130+
onFailure = { DevResult.ProbeFailure.from(it, durationMs = startedAt.durationMs()) },
127131
)
128132
}
129133
}
@@ -149,20 +153,24 @@ private sealed interface DevCommand {
149153
val timeout = args.timeoutSeconds.coerceAtLeast(1).seconds
150154

151155
Logger.info(
152-
"Sending keysend probe for target '${args.targetName ?: "unknown"}' nodeId='${args.nodeId}' amountSats='$amountSats'",
156+
"Sending keysend probe for target '${args.targetName ?: "unknown"}' " +
157+
"nodeId='${args.nodeId}' amountSats='$amountSats'",
153158
context = TAG,
154159
)
155160

161+
val startedAt = SystemClock.elapsedRealtime()
156162
return deps.lightningRepo().sendProbeForNode(args.nodeId, amountSats)
157163
.fold(
158164
onSuccess = {
159165
deps.lightningRepo().waitForProbeOutcome(it.paymentIds, timeout)
160166
.fold(
161-
onSuccess = { outcome -> outcome.toDevResult(it.paymentIds) },
162-
onFailure = { error -> DevResult.ProbeFailure.from(error, it.paymentIds) },
167+
onSuccess = { outcome -> outcome.toDevResult(it.paymentIds, startedAt.durationMs()) },
168+
onFailure = { error ->
169+
DevResult.ProbeFailure.from(error, it.paymentIds, startedAt.durationMs())
170+
},
163171
)
164172
},
165-
onFailure = { DevResult.ProbeFailure.from(it) },
173+
onFailure = { DevResult.ProbeFailure.from(it, durationMs = startedAt.durationMs()) },
166174
)
167175
}
168176
}
@@ -206,6 +214,8 @@ private sealed interface DevResult {
206214
val success: Boolean = true,
207215
val paymentId: String,
208216
val paymentHash: String,
217+
val durationMs: Long,
218+
val routeFeeMsat: ULong?,
209219
val paymentIds: List<String>,
210220
) : DevResult
211221

@@ -215,12 +225,19 @@ private sealed interface DevResult {
215225
val message: String? = null,
216226
val paymentId: String? = null,
217227
val paymentHash: String? = null,
218-
val shortChannelId: ULong? = null,
228+
val shortChannelId: String? = null,
229+
val durationMs: Long,
230+
val routeFeeMsat: ULong? = null,
219231
val paymentIds: List<String> = emptyList(),
220232
) : DevResult {
221233
companion object {
222-
fun from(error: Throwable, paymentIds: Set<String> = emptySet()) = ProbeFailure(
234+
fun from(
235+
error: Throwable,
236+
paymentIds: Set<String> = emptySet(),
237+
durationMs: Long,
238+
) = ProbeFailure(
223239
message = error.message,
240+
durationMs = durationMs,
224241
paymentIds = paymentIds.toList(),
225242
)
226243
}
@@ -243,6 +260,7 @@ private sealed interface DevResult {
243260
val graphChannelCount: Int? = null,
244261
val latestRgsSyncTimestamp: ULong? = null,
245262
val latestPathfindingScoresSyncTimestamp: ULong? = null,
263+
val probeRuntimeConfig: ProbeRuntimeConfig,
246264
) : DevResult {
247265
companion object {
248266
fun from(readiness: NodeProbeReadiness) = ProbeReadiness(
@@ -261,29 +279,91 @@ private sealed interface DevResult {
261279
graphChannelCount = readiness.graphChannelCount,
262280
latestRgsSyncTimestamp = readiness.latestRgsSyncTimestamp,
263281
latestPathfindingScoresSyncTimestamp = readiness.latestPathfindingScoresSyncTimestamp,
282+
probeRuntimeConfig = ProbeRuntimeConfig.from(readiness.probeRuntimeConfig),
264283
)
265284
}
266285
}
267286

287+
@Serializable
288+
data class ProbeRuntimeConfig(
289+
val sampleAmountMsat: ULong,
290+
val route: ProbeRouteConfig,
291+
val scoring: ProbeScoringConfig,
292+
) {
293+
companion object {
294+
fun from(config: to.bitkit.services.ProbeRuntimeConfig) = ProbeRuntimeConfig(
295+
sampleAmountMsat = config.sampleAmountMsat,
296+
route = ProbeRouteConfig(
297+
maxTotalRoutingFeeMsat = config.route.maxTotalRoutingFeeMsat,
298+
maxTotalCltvExpiryDelta = config.route.maxTotalCltvExpiryDelta,
299+
maxPathCount = config.route.maxPathCount,
300+
maxChannelSaturationPowerOfHalf = config.route.maxChannelSaturationPowerOfHalf,
301+
),
302+
scoring = ProbeScoringConfig(
303+
basePenaltyMsat = config.scoring.basePenaltyMsat,
304+
basePenaltyAmountMultiplierMsat = config.scoring.basePenaltyAmountMultiplierMsat,
305+
liquidityPenaltyMultiplierMsat = config.scoring.liquidityPenaltyMultiplierMsat,
306+
liquidityPenaltyAmountMultiplierMsat = config.scoring.liquidityPenaltyAmountMultiplierMsat,
307+
historicalLiquidityPenaltyMultiplierMsat =
308+
config.scoring.historicalLiquidityPenaltyMultiplierMsat,
309+
historicalLiquidityPenaltyAmountMultiplierMsat =
310+
config.scoring.historicalLiquidityPenaltyAmountMultiplierMsat,
311+
antiProbingPenaltyMsat = config.scoring.antiProbingPenaltyMsat,
312+
consideredImpossiblePenaltyMsat = config.scoring.consideredImpossiblePenaltyMsat,
313+
linearSuccessProbability = config.scoring.linearSuccessProbability,
314+
probingDiversityPenaltyMsat = config.scoring.probingDiversityPenaltyMsat,
315+
),
316+
)
317+
}
318+
}
319+
320+
@Serializable
321+
data class ProbeRouteConfig(
322+
val maxTotalRoutingFeeMsat: ULong?,
323+
val maxTotalCltvExpiryDelta: UInt,
324+
val maxPathCount: Int,
325+
val maxChannelSaturationPowerOfHalf: Int,
326+
)
327+
328+
@Serializable
329+
data class ProbeScoringConfig(
330+
val basePenaltyMsat: ULong,
331+
val basePenaltyAmountMultiplierMsat: ULong,
332+
val liquidityPenaltyMultiplierMsat: ULong,
333+
val liquidityPenaltyAmountMultiplierMsat: ULong,
334+
val historicalLiquidityPenaltyMultiplierMsat: ULong,
335+
val historicalLiquidityPenaltyAmountMultiplierMsat: ULong,
336+
val antiProbingPenaltyMsat: ULong,
337+
val consideredImpossiblePenaltyMsat: ULong,
338+
val linearSuccessProbability: Boolean,
339+
val probingDiversityPenaltyMsat: ULong,
340+
)
341+
268342
@Serializable data class Error(val message: String? = null) : DevResult
269343

270344
fun toBundle() = bundleOf(KEY_RESULT to DEV_JSON.encodeToString(this))
271345
}
272346

273-
private fun ProbeOutcome.toDevResult(paymentIds: Set<String>): DevResult = when (this) {
347+
private fun ProbeOutcome.toDevResult(paymentIds: Set<String>, durationMs: Long): DevResult = when (this) {
274348
is ProbeOutcome.Success -> DevResult.ProbeSuccess(
275349
paymentId = paymentId,
276350
paymentHash = paymentHash,
351+
durationMs = durationMs,
352+
routeFeeMsat = routeFeeMsat,
277353
paymentIds = paymentIds.toList(),
278354
)
279355
is ProbeOutcome.Failure -> DevResult.ProbeFailure(
280356
message = "Probe failed",
281357
paymentId = paymentId,
282358
paymentHash = paymentHash,
283-
shortChannelId = shortChannelId,
359+
shortChannelId = shortChannelId?.toString(),
360+
durationMs = durationMs,
361+
routeFeeMsat = routeFeeMsat,
284362
paymentIds = paymentIds.toList(),
285363
)
286364
}
287365

288366
private inline fun <reified T> String?.deserialize(): T =
289367
if (isNullOrBlank()) Json.decodeFromString("{}") else Json.decodeFromString(this)
368+
369+
private fun Long.durationMs() = SystemClock.elapsedRealtime() - this

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import to.bitkit.services.LnurlService
8585
import to.bitkit.services.LnurlWithdrawResponse
8686
import to.bitkit.services.LspNotificationsService
8787
import to.bitkit.services.NodeEventHandler
88+
import to.bitkit.services.ProbeRuntimeConfig
8889
import to.bitkit.utils.AppError
8990
import to.bitkit.utils.Logger
9091
import to.bitkit.utils.ServiceError
@@ -571,8 +572,13 @@ class LightningRepo @Inject constructor(
571572

572573
private suspend fun recordProbeOutcome(event: Event) {
573574
val outcome = when (event) {
574-
is Event.ProbeSuccessful -> ProbeOutcome.Success(event.paymentId, event.paymentHash)
575-
is Event.ProbeFailed -> ProbeOutcome.Failure(event.paymentId, event.paymentHash, event.shortChannelId)
575+
is Event.ProbeSuccessful -> ProbeOutcome.Success(event.paymentId, event.paymentHash, event.routeFeeMsat)
576+
is Event.ProbeFailed -> ProbeOutcome.Failure(
577+
event.paymentId,
578+
event.paymentHash,
579+
event.shortChannelId,
580+
event.routeFeeMsat,
581+
)
576582
else -> return
577583
}
578584

@@ -1637,6 +1643,7 @@ class LightningRepo @Inject constructor(
16371643
graphChannelCount = graph?.channelCount,
16381644
latestRgsSyncTimestamp = graph?.latestRgsSyncTimestamp,
16391645
latestPathfindingScoresSyncTimestamp = state.nodeStatus?.latestPathfindingScoresSyncTimestamp,
1646+
probeRuntimeConfig = lightningService.probeRuntimeConfig(),
16401647
syncHealthy = state.isSyncHealthy,
16411648
)
16421649
}
@@ -1761,6 +1768,7 @@ data class ProbeReadiness(
17611768
val graphChannelCount: Int?,
17621769
val latestRgsSyncTimestamp: ULong?,
17631770
val latestPathfindingScoresSyncTimestamp: ULong?,
1771+
val probeRuntimeConfig: ProbeRuntimeConfig,
17641772
val syncHealthy: Boolean,
17651773
) {
17661774
val ready: Boolean
@@ -1779,11 +1787,13 @@ sealed interface ProbeOutcome {
17791787
data class Success(
17801788
override val paymentId: PaymentId,
17811789
override val paymentHash: PaymentHash,
1790+
val routeFeeMsat: ULong?,
17821791
) : ProbeOutcome
17831792

17841793
data class Failure(
17851794
override val paymentId: PaymentId,
17861795
override val paymentHash: PaymentHash,
17871796
val shortChannelId: ULong?,
1797+
val routeFeeMsat: ULong?,
17881798
) : ProbeOutcome
17891799
}

0 commit comments

Comments
 (0)