Skip to content
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/env/Env.kt
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ internal object Env {
@Suppress("ConstPropertyName")
object Defaults {
/** Default Bolt11 invoice expiry in seconds. */
const val bolt11InvoiceExpirySeconds = 3_600u
const val bolt11ExpirySec = 86_400u

/** Recommended transaction base fee in sats */
const val recommendedBaseFee = 256u
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import org.lightningdevkit.ldknode.ChannelDetails
import to.bitkit.async.ServiceQueue
import to.bitkit.data.CacheStore
import to.bitkit.di.BgDispatcher
import to.bitkit.env.Defaults
import to.bitkit.env.Env
import to.bitkit.ext.calculateRemoteBalance
import to.bitkit.ext.nowTimestamp
Expand All @@ -57,6 +56,7 @@ import javax.inject.Named
import javax.inject.Singleton
import kotlin.math.ceil
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds

@Singleton
Expand Down Expand Up @@ -463,7 +463,7 @@ class BlocktankRepo @Inject constructor(
val invoice = lightningRepo.createInvoice(
amountSats = null,
description = "blocktank-gift-code:$code",
expirySeconds = Defaults.bolt11InvoiceExpirySeconds,
expirySeconds = 1.hours.inWholeSeconds.toUInt(),
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could add another constant in Defaults in Env and use it.

).getOrThrow()

Logger.debug("Created invoice for gift code, requesting payment from LSP", context = TAG)
Expand Down
72 changes: 65 additions & 7 deletions app/src/main/java/to/bitkit/repositories/LightningRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import to.bitkit.data.SettingsStore
import to.bitkit.data.backup.VssBackupClientLdk
import to.bitkit.data.keychain.Keychain
import to.bitkit.di.BgDispatcher
import to.bitkit.env.Defaults
import to.bitkit.env.Env
import to.bitkit.ext.getSatsPerVByteFor
import to.bitkit.ext.nowTimestamp
Expand Down Expand Up @@ -94,6 +95,7 @@ import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

Expand Down Expand Up @@ -926,7 +928,7 @@ class LightningRepo @Inject constructor(
suspend fun createInvoice(
amountSats: ULong? = null,
description: String,
expirySeconds: UInt = 86_400u,
expirySeconds: UInt = Defaults.bolt11ExpirySec,
): Result<String> = executeWhenNodeRunning("createInvoice") {
updateGeoBlockState()
runCatching { lightningService.receive(amountSats, description, expirySeconds) }
Expand All @@ -935,7 +937,7 @@ class LightningRepo @Inject constructor(
suspend fun createInvoiceMsats(
amountMsats: ULong,
description: String,
expirySeconds: UInt = 86_400u,
expirySeconds: UInt = Defaults.bolt11ExpirySec,
): Result<String> = executeWhenNodeRunning("createInvoiceMsats") {
updateGeoBlockState()
runCatching { lightningService.receiveMsats(amountMsats, description, expirySeconds) }
Expand Down Expand Up @@ -1019,13 +1021,68 @@ class LightningRepo @Inject constructor(
}

suspend fun waitForUsableChannels() = withContext(bgDispatcher) {
if (_lightningState.value.channels.any { it.isUsable }) return@withContext
var state = _lightningState.value
if (!state.nodeLifecycleState.canRun()) {
delayNoUsableChannelsFeedback()
return@withContext
}
if (state.hasUsableChannels()) return@withContext

state = waitForChannelsToLoadIfNeeded(state) ?: return@withContext
if (!state.nodeLifecycleState.canRun()) {
delayNoUsableChannelsFeedback()
return@withContext
}

if (state.channels.isEmpty()) {
if (state.nodeLifecycleState.isRunning()) {
syncState()
state = _lightningState.value
}

if (state.channels.isEmpty()) {
delayNoUsableChannelsFeedback()
return@withContext
}
if (state.hasUsableChannels()) return@withContext
}

Logger.info("Waiting for usable channels before sending payment", context = TAG)

withTimeoutOrNull(CHANNELS_USABLE_TIMEOUT_MS) {
_lightningState.first { state -> state.channels.any { it.isUsable } }
} ?: Logger.warn("Timed out waiting for usable channels", context = TAG)
val finalState = withTimeoutOrNull(CHANNELS_USABLE_TIMEOUT) {
_lightningState.first { it.shouldStopWaitingForUsableChannels() }
} ?: run {
Logger.warn("Timed out waiting for usable channels", context = TAG)
return@withContext
}

if (!finalState.nodeLifecycleState.canRun() || finalState.channels.isEmpty()) {
delayNoUsableChannelsFeedback()
}
}

private suspend fun waitForChannelsToLoadIfNeeded(state: LightningState): LightningState? {
if (state.channels.isNotEmpty() || state.nodeLifecycleState.isRunning()) return state

Logger.info("Waiting for node to load channels before sending payment", context = TAG)
return withTimeoutOrNull(CHANNELS_USABLE_TIMEOUT) {
_lightningState.first { it.shouldStopWaitingForLoadedChannels() }
} ?: run {
Logger.warn("Timed out waiting for node to load channels", context = TAG)
null
}
}

private fun LightningState.hasUsableChannels() = channels.any { it.isUsable }

private fun LightningState.shouldStopWaitingForLoadedChannels() =
!nodeLifecycleState.canRun() || nodeLifecycleState.isRunning() || channels.isNotEmpty()

private fun LightningState.shouldStopWaitingForUsableChannels() =
!nodeLifecycleState.canRun() || channels.isEmpty() || hasUsableChannels()

private suspend fun delayNoUsableChannelsFeedback() {
delay(NO_USABLE_CHANNELS_FEEDBACK_DELAY)
}
Comment on lines +1084 to 1086
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can use expression body here as well.


@Suppress("LongParameterList")
Expand Down Expand Up @@ -1487,7 +1544,8 @@ class LightningRepo @Inject constructor(
private const val LENGTH_CHANNEL_ID_PREVIEW = 10
private const val MS_SYNC_LOOP_DEBOUNCE = 500L
private const val SYNC_RETRY_DELAY_MS = 15_000L
private const val CHANNELS_USABLE_TIMEOUT_MS = 15_000L
private val CHANNELS_USABLE_TIMEOUT = 15.seconds
private val NO_USABLE_CHANNELS_FEEDBACK_DELAY = 2_500.milliseconds
val SEND_LN_TIMEOUT = 10.seconds
private val PROBE_TIMEOUT = 60.seconds
}
Expand Down
9 changes: 7 additions & 2 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import org.lightningdevkit.ldknode.TransactionDetails
import to.bitkit.async.ServiceQueue
import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.env.Defaults
import to.bitkit.env.Env
import to.bitkit.ext.amountSats
import to.bitkit.ext.channelId
Expand Down Expand Up @@ -1523,11 +1524,15 @@ class BlocktankService(
)
}

suspend fun regtestCloseChannel(fundingTxId: String, vout: UInt, forceCloseAfterS: ULong = 86_400uL): String {
suspend fun regtestCloseChannel(
fundingTxId: String,
vout: UInt,
forceCloseAfterS: UInt = Defaults.bolt11ExpirySec,
): String {
return com.synonym.bitkitcore.regtestCloseChannel(
fundingTxId = fundingTxId,
vout = vout,
forceCloseAfterS = forceCloseAfterS,
forceCloseAfterS = forceCloseAfterS.toULong(),
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/to/bitkit/services/LightningService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -595,15 +595,15 @@ class LightningService @Inject constructor(
suspend fun receive(
sat: ULong? = null,
description: String,
expirySecs: UInt = Defaults.bolt11InvoiceExpirySeconds,
expirySecs: UInt = Defaults.bolt11ExpirySec,
): String {
return receiveMsats(amountMsat = sat?.let { it * 1000u }, description = description, expirySecs = expirySecs)
}

suspend fun receiveMsats(
amountMsat: ULong? = null,
description: String,
expirySecs: UInt = Defaults.bolt11InvoiceExpirySeconds,
expirySecs: UInt = Defaults.bolt11ExpirySec,
): String {
val node = this.node ?: throw ServiceError.NodeNotSetup()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ fun BlocktankRegtestScreen(
runCatching {
val voutNum = vout.toUIntOrNull() ?: error("Invalid Vout: $vout")
val closeAfter =
forceCloseAfter.toULongOrNull() ?: error("Invalid Force Close After: $forceCloseAfter")
forceCloseAfter.toUIntOrNull() ?: error("Invalid Force Close After: $forceCloseAfter")
val closingTxId = viewModel.regtestCloseChannel(
fundingTxId = fundingTxId,
vout = voutNum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package to.bitkit.ui.settings

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import to.bitkit.env.Defaults
import to.bitkit.services.CoreService
import javax.inject.Inject

Expand All @@ -28,7 +29,11 @@ class BlocktankRegtestViewModel @Inject constructor(
)
}

suspend fun regtestCloseChannel(fundingTxId: String, vout: UInt, forceCloseAfterS: ULong = 86_400uL): String {
suspend fun regtestCloseChannel(
fundingTxId: String,
vout: UInt,
forceCloseAfterS: UInt = Defaults.bolt11ExpirySec,
): String {
return coreService.blocktank.regtestCloseChannel(
fundingTxId = fundingTxId,
vout = vout,
Expand Down
Loading
Loading