Skip to content
Merged
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
10 changes: 9 additions & 1 deletion app/src/main/java/to/bitkit/env/Env.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package to.bitkit.env

import org.lightningdevkit.ldknode.LogLevel
import org.lightningdevkit.ldknode.Network
import to.bitkit.BuildConfig
import to.bitkit.ext.ensureDir
Expand All @@ -16,7 +17,6 @@ internal object Env {
val defaultWalletWordCount = 12
val walletSyncIntervalSecs = 10_uL // TODO review
val ldkNodeSyncIntervalSecs = 60_uL // TODO review
val esploraParallelRequests = 6
val trustedLnPeers
get() = when (network) {
Network.REGTEST -> listOf(
Expand Down Expand Up @@ -83,6 +83,14 @@ internal object Env {
appStoragePath = path
}

fun ldkLogFilePath(walletIndex: Int): String {
val logPath = Path(ldkStoragePath(walletIndex), "ldk_node_latest.log").toFile().absolutePath
Logger.info("LDK-node log path: $logPath")
return logPath
}

val ldkLogLevel = LogLevel.TRACE

fun ldkStoragePath(walletIndex: Int) = storagePathOf(walletIndex, network.name.lowercase(), "ldk")
fun bitkitCoreStoragePath(walletIndex: Int) = storagePathOf(walletIndex, network.name.lowercase(), "core")

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/to/bitkit/ext/ChannelDetails.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,8 @@ fun mockChannelDetails(
forceCloseAvoidanceMaxFeeSatoshis = 0uL,
acceptUnderpayingHtlcs = false,
),
shortChannelId = 1234uL,
outboundScidAlias = 2345uL,
inboundScidAlias = 3456uL,
)
}
1 change: 1 addition & 0 deletions app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class WakeNodeWorker @AssistedInject constructor(

is Event.PaymentSuccessful -> Unit
is Event.PaymentClaimable -> Unit
is Event.PaymentForwarded -> Unit

is Event.PaymentFailed -> {
self.bestAttemptContent?.title = "Payment failed"
Expand Down
138 changes: 102 additions & 36 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package to.bitkit.services
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode
import org.lightningdevkit.ldknode.ConfirmationStatus
import org.lightningdevkit.ldknode.Network
import org.lightningdevkit.ldknode.PaymentDetails
import org.lightningdevkit.ldknode.PaymentDirection
import org.lightningdevkit.ldknode.PaymentKind
import org.lightningdevkit.ldknode.PaymentStatus
import to.bitkit.async.ServiceQueue
import to.bitkit.env.Env
Expand Down Expand Up @@ -46,6 +48,7 @@ import uniffi.bitkitcore.openChannel
import uniffi.bitkitcore.removeTags
import uniffi.bitkitcore.updateActivity
import uniffi.bitkitcore.updateBlocktankUrl
import uniffi.bitkitcore.upsertActivity
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.random.Random
Expand Down Expand Up @@ -164,47 +167,110 @@ class ActivityService(
ServiceQueue.CORE.background {
var addedCount = 0
var updatedCount = 0

for (payment in payments) { // Skip pending inbound payments, just means they created an invoice
if (payment.status == PaymentStatus.PENDING && payment.direction == PaymentDirection.INBOUND) {
continue
}

val state = when (payment.status) {
PaymentStatus.FAILED -> PaymentState.FAILED
PaymentStatus.PENDING -> PaymentState.PENDING
PaymentStatus.SUCCEEDED -> PaymentState.SUCCEEDED
var latestCaughtError: Throwable? = null

for (payment in payments) {
try {
val state = when (payment.status) {
PaymentStatus.FAILED -> PaymentState.FAILED
PaymentStatus.PENDING -> PaymentState.PENDING
PaymentStatus.SUCCEEDED -> PaymentState.SUCCEEDED
}

when (val kind = payment.kind) {
is PaymentKind.Onchain -> {
var isConfirmed = false
var confirmedTimestamp: ULong? = null

val status = kind.status
if (status is ConfirmationStatus.Confirmed) {
isConfirmed = true
confirmedTimestamp = status.timestamp
}

// Ensure confirmTimestamp is at least equal to timestamp when confirmed
val timestamp = payment.latestUpdateTimestamp

if (isConfirmed && confirmedTimestamp != null && confirmedTimestamp < timestamp) {
confirmedTimestamp = timestamp
}

val onchain = OnchainActivity(
id = payment.id,
txType = payment.direction.toPaymentType(),
txId = kind.txid,
value = payment.amountSats ?: 0u,
fee = (payment.feePaidMsat ?: 0u) / 1000u,
feeRate = 1u, // TODO: get from somewhere
address = "todo_find_address", // TODO: find address
confirmed = isConfirmed,
timestamp = timestamp,
isBoosted = false, // TODO: handle
isTransfer = false, // TODO: handle when paying for order
doesExist = true,
confirmTimestamp = confirmedTimestamp,
channelId = null, // TODO: get from linked order
transferTxId = null, // TODO: get from linked order
createdAt = timestamp,
updatedAt = timestamp,
)

if (getActivityById(payment.id) != null) {
updateActivity(payment.id, Activity.Onchain(onchain))
updatedCount++
} else {
upsertActivity(Activity.Onchain(onchain))
addedCount++
}
}

is PaymentKind.Bolt11 -> {
// Skip pending inbound payments, just means they created an invoice
if (payment.status == PaymentStatus.PENDING && payment.direction == PaymentDirection.INBOUND) {
continue
}

val ln = LightningActivity(
id = payment.id,
txType = payment.direction.toPaymentType(),
status = state,
value = payment.amountSats ?: 0u,
fee = null, // TODO
invoice = "lnbc123", // TODO
message = "",
timestamp = payment.latestUpdateTimestamp,
preimage = null,
createdAt = payment.latestUpdateTimestamp,
updatedAt = payment.latestUpdateTimestamp,
)

if (getActivityById(payment.id) != null) {
updateActivity(payment.id, Activity.Lightning(ln))
updatedCount++
} else {
upsertActivity(Activity.Lightning(ln))
addedCount++
}
}

else -> Unit // Handle spontaneous payments if needed
}
} catch (e: Throwable) {
Logger.error("Error syncing LDK payment:", e, context = "CoreService")
latestCaughtError = e
}

val ln = LightningActivity(
id = payment.id,
txType = if (payment.direction == PaymentDirection.OUTBOUND) PaymentType.SENT else PaymentType.RECEIVED,
status = state,
value = payment.amountSats ?: 0u,
fee = null, // TODO
invoice = "lnbc123", // TODO
message = "",
timestamp = payment.latestUpdateTimestamp,
preimage = null,
createdAt = payment.latestUpdateTimestamp,
updatedAt = payment.latestUpdateTimestamp,
)

if (getActivityById(payment.id) != null) {
updateActivity(payment.id, Activity.Lightning(ln))
updatedCount++
} else {
insertActivity(Activity.Lightning(ln))
addedCount++
}

// TODO: handle onchain activity when it comes in ldk-node
}

Logger.info("Synced LDK payments - Added: $addedCount, Updated: $updatedCount")
// If any of the inserts failed, we want to throw the error up
latestCaughtError?.let { throw it }

Logger.info("Synced LDK payments - Added: $addedCount - Updated: $updatedCount", context = "CoreService")
}
}

private fun PaymentDirection.toPaymentType(): PaymentType =
if (this == PaymentDirection.OUTBOUND) PaymentType.SENT else PaymentType.RECEIVED

suspend fun getActivity(id: String): Activity? {
return ServiceQueue.CORE.background {
getActivityById(id)
Expand Down Expand Up @@ -240,7 +306,7 @@ class ActivityService(

// MARK: - Tag Methods

suspend fun appendTags(toActivityId: String, tags: List<String>) : Result<Unit>{
suspend fun appendTags(toActivityId: String, tags: List<String>): Result<Unit> {
return try {
ServiceQueue.CORE.background {
addTags(toActivityId, tags)
Expand Down
53 changes: 40 additions & 13 deletions app/src/main/java/to/bitkit/services/LightningService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import org.lightningdevkit.ldknode.Address
import org.lightningdevkit.ldknode.AnchorChannelsConfig
import org.lightningdevkit.ldknode.BackgroundSyncConfig
import org.lightningdevkit.ldknode.BalanceDetails
import org.lightningdevkit.ldknode.Bolt11Invoice
import org.lightningdevkit.ldknode.Bolt11InvoiceDescription
import org.lightningdevkit.ldknode.BuildException
import org.lightningdevkit.ldknode.Builder
import org.lightningdevkit.ldknode.ChannelDetails
import org.lightningdevkit.ldknode.EsploraSyncConfig
import org.lightningdevkit.ldknode.Event
import org.lightningdevkit.ldknode.LogLevel
import org.lightningdevkit.ldknode.FeeRate
import org.lightningdevkit.ldknode.Network
import org.lightningdevkit.ldknode.Node
import org.lightningdevkit.ldknode.NodeException
Expand Down Expand Up @@ -68,9 +70,7 @@ class LightningService @Inject constructor(
.fromConfig(
defaultConfig().apply {
storageDirPath = dirPath
logDirPath = dirPath
network = Env.network
logLevel = LogLevel.TRACE

trustedPeers0conf = Env.trustedLnPeers.map { it.nodeId }
anchorChannelsConfig = AnchorChannelsConfig(
Expand All @@ -79,12 +79,15 @@ class LightningService @Inject constructor(
)
})
.apply {
setFilesystemLogger(Env.ldkLogFilePath(walletIndex), Env.ldkLogLevel)
setChainSourceEsplora(
serverUrl = Env.esploraServerUrl,
config = EsploraSyncConfig(
onchainWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
lightningWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
feeRateCacheUpdateIntervalSecs = Env.walletSyncIntervalSecs,
BackgroundSyncConfig(
onchainWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
lightningWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
feeRateCacheUpdateIntervalSecs = Env.walletSyncIntervalSecs,
),
),
)
if (Env.ldkRgsServerUrl != null) {
Expand Down Expand Up @@ -266,7 +269,7 @@ class LightningService @Inject constructor(
peer: LnPeer,
channelAmountSats: ULong,
pushToCounterpartySats: ULong? = null,
) : Result<UserChannelId> {
): Result<UserChannelId> {
val node = this.node ?: throw ServiceError.NodeNotSetup

return ServiceQueue.LDK.background {
Expand Down Expand Up @@ -311,10 +314,23 @@ class LightningService @Inject constructor(
return ServiceQueue.LDK.background {
if (sat != null) {
Logger.debug("Creating bolt11 for $sat sats")
node.bolt11Payment().receive(sat.millis, description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }, expirySecs)
node.bolt11Payment()
.receive(
amountMsat = sat.millis,
description = Bolt11InvoiceDescription.Direct(
description = description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }
),
expirySecs = expirySecs,
)
} else {
Logger.debug("Creating bolt11 for variable amount")
node.bolt11Payment().receiveVariableAmount(description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }, expirySecs)
node.bolt11Payment()
.receiveVariableAmount(
description = Bolt11InvoiceDescription.Direct(
description = description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }
),
expirySecs = expirySecs,
)
}
}
}
Expand All @@ -338,13 +354,18 @@ class LightningService @Inject constructor(
return true
}

suspend fun send(address: Address, sats: ULong): Txid {
//TODO: get feeRate from real source
suspend fun send(address: Address, sats: ULong, satKwu: ULong = 250uL * 5uL): Txid {
val node = this.node ?: throw ServiceError.NodeNotSetup

Logger.info("Sending $sats sats to $address")

return ServiceQueue.LDK.background {
node.onchainPayment().sendToAddress(address, sats)
node.onchainPayment().sendToAddress(
address = address,
amountSats = sats,
feeRate = FeeRate.fromSatPerKwu(satKwu)
)
}
}

Expand Down Expand Up @@ -373,8 +394,12 @@ class LightningService @Inject constructor(
}
val event = node.nextEventAsync()

Logger.debug("LDK eventHandled: $event")
node.eventHandled()
try {
node.eventHandled()
Logger.debug("LDK eventHandled: $event")
} catch (e: NodeException) {
Logger.error("LDK eventHandled error", LdkError(e))
}

logEvent(event)
onEvent?.invoke(event)
Expand Down Expand Up @@ -411,6 +436,8 @@ class LightningService @Inject constructor(
Logger.info("🫰 Payment claimable: paymentId: $paymentId paymentHash: $paymentHash claimableAmountMsat: $claimableAmountMsat")
}

is Event.PaymentForwarded -> Unit

is Event.ChannelPending -> {
val channelId = event.channelId
val userChannelId = event.userChannelId
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/services/MigrationService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,6 @@ class MigrationService @Inject constructor(
private const val KEY = "key"
private const val VALUE = "value"
private const val LDK_DB_NAME = "$LDK_NODE_DATA.sqlite"
private const val LDK_DB_VERSION = 2
private const val LDK_DB_VERSION = 2 // Should match SCHEMA_USER_VERSION from ldk-node
}
}
Loading