Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 84 additions & 1 deletion app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import to.bitkit.async.ServiceQueue
import to.bitkit.models.msatCeilOf
import to.bitkit.repositories.LightningRepo
import to.bitkit.repositories.ProbeOutcome
import to.bitkit.utils.Logger
import kotlin.time.Duration.Companion.seconds

private const val TAG = "DevToolsProvider"
private val DEV_JSON = Json { encodeDefaults = true }

class DevToolsProvider : ContentProvider() {

Expand Down Expand Up @@ -56,6 +60,7 @@ private sealed interface DevCommand {
companion object {
fun parse(method: String, arg: String?): DevCommand? = when (method) {
CreateInvoice.METHOD -> CreateInvoice.parse(arg)
ProbeInvoice.METHOD -> ProbeInvoice.parse(arg)
else -> null
}
}
Expand All @@ -80,6 +85,44 @@ private sealed interface DevCommand {
},
)
}

data class ProbeInvoice(val args: Args) : DevCommand {
companion object {
const val METHOD = "probeInvoice"
fun parse(arg: String?) = ProbeInvoice(arg.deserialize<Args>())
}

@Serializable
data class Args(
val targetName: String? = null,
val bolt11: String,
val amountMsat: ULong? = null,
val amountSats: ULong? = null,
val timeoutSeconds: Long = 90,
)

override suspend fun execute(deps: DevToolsProvider.Dependencies): DevResult {
val amountSats = args.amountSats ?: args.amountMsat?.let { msatCeilOf(it) }
val timeout = args.timeoutSeconds.coerceAtLeast(1).seconds

Logger.info(
"Sending probe for target '${args.targetName ?: "unknown"}' amountSats='${amountSats ?: "invoice"}'",
context = TAG,
)

return deps.lightningRepo().sendProbeForInvoice(args.bolt11, amountSats)
Comment thread
piotr-iohk marked this conversation as resolved.
.fold(
onSuccess = {
deps.lightningRepo().waitForProbeOutcome(it.paymentIds, timeout)
.fold(
onSuccess = { outcome -> outcome.toDevResult(it.paymentIds) },
onFailure = { error -> DevResult.ProbeFailure.from(error, it.paymentIds) },
)
},
onFailure = { DevResult.ProbeFailure.from(it) },
)
}
}
}

@Serializable
Expand All @@ -91,9 +134,49 @@ private sealed interface DevResult {

@Serializable data class Invoice(val bolt11: String) : DevResult

@Serializable
data class ProbeSuccess(
val success: Boolean = true,
val paymentId: String,
val paymentHash: String,
val paymentIds: List<String>,
) : DevResult

@Serializable
data class ProbeFailure(
val success: Boolean = false,
val message: String? = null,
val paymentId: String? = null,
val paymentHash: String? = null,
val shortChannelId: ULong? = null,
val paymentIds: List<String> = emptyList(),
) : DevResult {
companion object {
fun from(error: Throwable, paymentIds: Set<String> = emptySet()) = ProbeFailure(
message = error.message,
paymentIds = paymentIds.toList(),
)
}
}

@Serializable data class Error(val message: String? = null) : DevResult

fun toBundle() = bundleOf(KEY_RESULT to Json.encodeToString(this))
fun toBundle() = bundleOf(KEY_RESULT to DEV_JSON.encodeToString(this))
}

private fun ProbeOutcome.toDevResult(paymentIds: Set<String>): DevResult = when (this) {
is ProbeOutcome.Success -> DevResult.ProbeSuccess(
paymentId = paymentId,
paymentHash = paymentHash,
paymentIds = paymentIds.toList(),
)
is ProbeOutcome.Failure -> DevResult.ProbeFailure(
message = "Probe failed",
paymentId = paymentId,
paymentHash = paymentHash,
shortChannelId = shortChannelId,
paymentIds = paymentIds.toList(),
)
}

private inline fun <reified T> String?.deserialize(): T =
Expand Down
13 changes: 13 additions & 0 deletions app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,19 @@ class LightningRepoTest : BaseUnitTest() {
assertEquals(setOf(probePaymentA, probePaymentB), result.getOrThrow().paymentIds)
}

@Test
fun `sendProbeForInvoice delegates amount probes when sats are provided`() = test {
startNodeForTesting()
whenever(lightningService.sendProbesUsingAmount("lnbc1", 42_000uL))
.thenReturn(Result.success(setOf(probePaymentA)))

val result = sut.sendProbeForInvoice("lnbc1", amountSats = 42uL)

assertTrue(result.isSuccess)
assertEquals(setOf(probePaymentA), result.getOrThrow().paymentIds)
verify(lightningService).sendProbesUsingAmount("lnbc1", 42_000uL)
}

@Test
fun `sendProbeForNode delegates to keysend probe and returns payment IDs`() = test {
startNodeForTesting()
Expand Down
Loading