Skip to content

Commit e76c672

Browse files
committed
feat: show probe route fee
1 parent bda21e5 commit e76c672

8 files changed

Lines changed: 87 additions & 26 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.iml
22
.gradle
33
.idea
4+
.vscode
45
!.idea/codeStyles/
56
.kotlin
67
.DS_Store

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -578,8 +578,13 @@ class LightningRepo @Inject constructor(
578578

579579
private suspend fun recordProbeOutcome(event: Event) {
580580
val outcome = when (event) {
581-
is Event.ProbeSuccessful -> ProbeOutcome.Success(event.paymentId, event.paymentHash)
582-
is Event.ProbeFailed -> ProbeOutcome.Failure(event.paymentId, event.paymentHash, event.shortChannelId)
581+
is Event.ProbeSuccessful -> ProbeOutcome.Success(event.paymentId, event.paymentHash, event.routeFeeMsat)
582+
is Event.ProbeFailed -> ProbeOutcome.Failure(
583+
event.paymentId,
584+
event.paymentHash,
585+
event.shortChannelId?.toString(),
586+
event.routeFeeMsat,
587+
)
583588
else -> return
584589
}
585590

@@ -1802,11 +1807,13 @@ sealed interface ProbeOutcome {
18021807
data class Success(
18031808
override val paymentId: PaymentId,
18041809
override val paymentHash: PaymentHash,
1810+
val routeFeeMsat: ULong?,
18051811
) : ProbeOutcome
18061812

18071813
data class Failure(
18081814
override val paymentId: PaymentId,
18091815
override val paymentHash: PaymentHash,
1810-
val shortChannelId: ULong?,
1816+
val shortChannelId: String?,
1817+
val routeFeeMsat: ULong?,
18111818
) : ProbeOutcome
18121819
}

app/src/main/java/to/bitkit/ui/screens/settings/ProbingToolScreen.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,11 @@ private fun ProbingToolContent(
186186
iconRes = R.drawable.ic_clock,
187187
value = "${result.durationMs} ms",
188188
)
189-
result.estimatedFeeSats?.let { fee ->
189+
result.routeFeeMsat?.let { fee ->
190190
SettingsTextButtonRow(
191-
title = "Estimated Fee",
191+
title = "Route Fee",
192192
iconRes = R.drawable.ic_coins,
193-
value = "$fee sats",
193+
value = "$fee msat",
194194
)
195195
}
196196
result.errorMessage?.let { error ->
@@ -216,7 +216,7 @@ private fun Preview() {
216216
probeResult = ProbeResult(
217217
success = true,
218218
durationMs = 342,
219-
estimatedFeeSats = 5uL,
219+
routeFeeMsat = 5_000uL,
220220
),
221221
)
222222
)

app/src/main/java/to/bitkit/viewmodels/ProbingToolViewModel.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class ProbingToolViewModel @Inject constructor(
161161
dispatch
162162
.onSuccess { probe ->
163163
lightningRepo.waitForProbeOutcome(probe.paymentIds)
164-
.onSuccess { handleProbeOutcome(startTime, it, bolt11, amountSats) }
164+
.onSuccess { handleProbeOutcome(startTime, it) }
165165
.onFailure { handleProbeFailure(startTime, it) }
166166
}
167167
.onFailure { handleProbeFailure(startTime, it) }
@@ -257,8 +257,6 @@ class ProbingToolViewModel @Inject constructor(
257257
private suspend fun handleProbeOutcome(
258258
startTime: Long,
259259
outcome: ProbeOutcome,
260-
invoice: String?,
261-
amountSats: ULong?,
262260
) {
263261
val durationMs = Clock.System.nowMs() - startTime
264262
when (outcome) {
@@ -268,13 +266,12 @@ class ProbingToolViewModel @Inject constructor(
268266
context = TAG,
269267
)
270268

271-
val estimatedFee = invoice?.let { getEstimatedFee(it, amountSats) }
272269
_uiState.update {
273270
it.copy(
274271
probeResult = ProbeResult(
275272
success = true,
276273
durationMs = durationMs,
277-
estimatedFeeSats = estimatedFee,
274+
routeFeeMsat = outcome.routeFeeMsat,
278275
)
279276
)
280277
}
@@ -294,6 +291,7 @@ class ProbingToolViewModel @Inject constructor(
294291
probeResult = ProbeResult(
295292
success = false,
296293
durationMs = durationMs,
294+
routeFeeMsat = outcome.routeFeeMsat,
297295
errorMessage = message,
298296
)
299297
)
@@ -379,6 +377,6 @@ data class ProbingToolUiState(
379377
data class ProbeResult(
380378
val success: Boolean,
381379
val durationMs: Long,
382-
val estimatedFeeSats: ULong? = null,
380+
val routeFeeMsat: ULong? = null,
383381
val errorMessage: String? = null,
384382
)

app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,67 +1348,113 @@ class LightningRepoTest : BaseUnitTest() {
13481348
val onEvent = startNodeAndCaptureEvents()
13491349

13501350
val result = async { sut.waitForProbeOutcome(setOf(probePaymentA)) }
1351-
onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA))
1351+
onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA, routeFeeMsat = 123uL))
13521352

13531353
val outcome = result.await().getOrThrow()
13541354
assertIs<ProbeOutcome.Success>(outcome)
13551355
assertEquals(probePaymentA, outcome.paymentId)
13561356
assertEquals(probeHashA, outcome.paymentHash)
1357+
assertEquals(123uL, outcome.routeFeeMsat)
13571358
}
13581359

13591360
@Test
13601361
fun `waitForProbeOutcome returns cached success when event arrives before wait`() = test {
13611362
val onEvent = startNodeAndCaptureEvents()
1362-
onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA))
1363+
onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA, routeFeeMsat = 123uL))
13631364

13641365
val outcome = sut.waitForProbeOutcome(setOf(probePaymentA)).getOrThrow()
13651366

13661367
assertIs<ProbeOutcome.Success>(outcome)
13671368
assertEquals(probePaymentA, outcome.paymentId)
13681369
assertEquals(probeHashA, outcome.paymentHash)
1370+
assertEquals(123uL, outcome.routeFeeMsat)
13691371
}
13701372

13711373
@Test
13721374
fun `waitForProbeOutcome returns last failure only after all tracked probes fail`() = test {
13731375
val onEvent = startNodeAndCaptureEvents()
13741376
val result = async { sut.waitForProbeOutcome(setOf(probePaymentA, probePaymentB)) }
13751377

1376-
onEvent(Event.ProbeFailed(paymentId = probePaymentA, paymentHash = probeHashA, shortChannelId = 1uL))
1377-
onEvent(Event.ProbeFailed(paymentId = probePaymentB, paymentHash = probeHashB, shortChannelId = 2uL))
1378+
onEvent(
1379+
Event.ProbeFailed(
1380+
paymentId = probePaymentA,
1381+
paymentHash = probeHashA,
1382+
shortChannelId = 1uL,
1383+
routeFeeMsat = 123uL,
1384+
)
1385+
)
1386+
onEvent(
1387+
Event.ProbeFailed(
1388+
paymentId = probePaymentB,
1389+
paymentHash = probeHashB,
1390+
shortChannelId = 990_718_250_873_192_449uL,
1391+
routeFeeMsat = 456uL,
1392+
)
1393+
)
13781394

13791395
val outcome = result.await().getOrThrow()
13801396
assertIs<ProbeOutcome.Failure>(outcome)
13811397
assertEquals(probePaymentB, outcome.paymentId)
13821398
assertEquals(probeHashB, outcome.paymentHash)
1383-
assertEquals(2uL, outcome.shortChannelId)
1399+
assertEquals("990718250873192449", outcome.shortChannelId)
1400+
assertEquals(456uL, outcome.routeFeeMsat)
13841401
}
13851402

13861403
@Test
13871404
fun `waitForProbeOutcome returns first success even when another path already failed`() = test {
13881405
val onEvent = startNodeAndCaptureEvents()
13891406
val result = async { sut.waitForProbeOutcome(setOf(probePaymentA, probePaymentB)) }
13901407

1391-
onEvent(Event.ProbeFailed(paymentId = probePaymentA, paymentHash = probeHashA, shortChannelId = 1uL))
1392-
onEvent(Event.ProbeSuccessful(paymentId = probePaymentB, paymentHash = probeHashB))
1408+
onEvent(
1409+
Event.ProbeFailed(
1410+
paymentId = probePaymentA,
1411+
paymentHash = probeHashA,
1412+
shortChannelId = 1uL,
1413+
routeFeeMsat = 123uL,
1414+
)
1415+
)
1416+
onEvent(
1417+
Event.ProbeSuccessful(
1418+
paymentId = probePaymentB,
1419+
paymentHash = probeHashB,
1420+
routeFeeMsat = 456uL,
1421+
)
1422+
)
13931423

13941424
val outcome = result.await().getOrThrow()
13951425
assertIs<ProbeOutcome.Success>(outcome)
13961426
assertEquals(probePaymentB, outcome.paymentId)
13971427
assertEquals(probeHashB, outcome.paymentHash)
1428+
assertEquals(456uL, outcome.routeFeeMsat)
13981429
}
13991430

14001431
@Test
14011432
fun `waitForProbeOutcome does not hang on partial cached failures`() = test {
14021433
val onEvent = startNodeAndCaptureEvents()
1403-
onEvent(Event.ProbeFailed(paymentId = probePaymentA, paymentHash = probeHashA, shortChannelId = 1uL))
1434+
onEvent(
1435+
Event.ProbeFailed(
1436+
paymentId = probePaymentA,
1437+
paymentHash = probeHashA,
1438+
shortChannelId = 1uL,
1439+
routeFeeMsat = 123uL,
1440+
)
1441+
)
14041442

14051443
val result = async { sut.waitForProbeOutcome(setOf(probePaymentA, probePaymentB)) }
1406-
onEvent(Event.ProbeFailed(paymentId = probePaymentB, paymentHash = probeHashB, shortChannelId = 2uL))
1444+
onEvent(
1445+
Event.ProbeFailed(
1446+
paymentId = probePaymentB,
1447+
paymentHash = probeHashB,
1448+
shortChannelId = 2uL,
1449+
routeFeeMsat = 456uL,
1450+
)
1451+
)
14071452

14081453
val outcome = result.await().getOrThrow()
14091454
assertIs<ProbeOutcome.Failure>(outcome)
14101455
assertEquals(probePaymentB, outcome.paymentId)
1411-
assertEquals(2uL, outcome.shortChannelId)
1456+
assertEquals("2", outcome.shortChannelId)
1457+
assertEquals(456uL, outcome.routeFeeMsat)
14121458
}
14131459

14141460
@Test
@@ -1425,7 +1471,7 @@ class LightningRepoTest : BaseUnitTest() {
14251471
fun `stop clears probe cache`() = test {
14261472
val onEvent = startNodeAndCaptureEvents()
14271473
whenever(lightningService.stop()).thenReturn(Unit)
1428-
onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA))
1474+
onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA, routeFeeMsat = null))
14291475

14301476
sut.stop()
14311477
val result = sut.waitForProbeOutcome(setOf(probePaymentA), timeout = 1.seconds)

app/src/test/java/to/bitkit/viewmodels/ProbingToolViewModelTest.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ class ProbingToolViewModelTest : BaseUnitTest() {
4747
whenever(lightningRepo.sendProbeForNode(nodeId, 42uL))
4848
.thenReturn(Result.success(ProbeDispatch(paymentIds = setOf(paymentId))))
4949
whenever(lightningRepo.waitForProbeOutcome(setOf(paymentId)))
50-
.thenReturn(Result.success(ProbeOutcome.Success(paymentId = paymentId, paymentHash = paymentHash)))
50+
.thenReturn(
51+
Result.success(
52+
ProbeOutcome.Success(
53+
paymentId = paymentId,
54+
paymentHash = paymentHash,
55+
routeFeeMsat = null,
56+
)
57+
)
58+
)
5159

5260
sut.updateInvoice(nodeUri)
5361
sut.updateAmountSats("42")

changelog.d/next/1052.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved Lightning payment route selection reliability.

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
6464
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
6565
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
6666
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
67-
ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.51" }
67+
ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.52" }
6868
lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" }
6969
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
7070
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }

0 commit comments

Comments
 (0)