Skip to content

Commit 2c5eb22

Browse files
authored
Merge pull request #125 from synonymdev/feat/ldk-050
feat: ldk-node 0.5.0
2 parents 44bfcbd + c8a016f commit 2c5eb22

File tree

12 files changed

+193
-62
lines changed

12 files changed

+193
-62
lines changed

app/src/main/java/to/bitkit/env/Env.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package to.bitkit.env
22

3+
import org.lightningdevkit.ldknode.LogLevel
34
import org.lightningdevkit.ldknode.Network
45
import to.bitkit.BuildConfig
56
import to.bitkit.ext.ensureDir
@@ -16,7 +17,6 @@ internal object Env {
1617
val defaultWalletWordCount = 12
1718
val walletSyncIntervalSecs = 10_uL // TODO review
1819
val ldkNodeSyncIntervalSecs = 60_uL // TODO review
19-
val esploraParallelRequests = 6
2020
val trustedLnPeers
2121
get() = when (network) {
2222
Network.REGTEST -> listOf(
@@ -83,6 +83,14 @@ internal object Env {
8383
appStoragePath = path
8484
}
8585

86+
fun ldkLogFilePath(walletIndex: Int): String {
87+
val logPath = Path(ldkStoragePath(walletIndex), "ldk_node_latest.log").toFile().absolutePath
88+
Logger.info("LDK-node log path: $logPath")
89+
return logPath
90+
}
91+
92+
val ldkLogLevel = LogLevel.TRACE
93+
8694
fun ldkStoragePath(walletIndex: Int) = storagePathOf(walletIndex, network.name.lowercase(), "ldk")
8795
fun bitkitCoreStoragePath(walletIndex: Int) = storagePathOf(walletIndex, network.name.lowercase(), "core")
8896

app/src/main/java/to/bitkit/ext/ChannelDetails.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,8 @@ fun mockChannelDetails(
5555
forceCloseAvoidanceMaxFeeSatoshis = 0uL,
5656
acceptUnderpayingHtlcs = false,
5757
),
58+
shortChannelId = 1234uL,
59+
outboundScidAlias = 2345uL,
60+
inboundScidAlias = 3456uL,
5861
)
5962
}

app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ class WakeNodeWorker @AssistedInject constructor(
170170

171171
is Event.PaymentSuccessful -> Unit
172172
is Event.PaymentClaimable -> Unit
173+
is Event.PaymentForwarded -> Unit
173174

174175
is Event.PaymentFailed -> {
175176
self.bestAttemptContent?.title = "Payment failed"

app/src/main/java/to/bitkit/services/CoreService.kt

Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package to.bitkit.services
33
import io.ktor.client.HttpClient
44
import io.ktor.client.request.get
55
import io.ktor.http.HttpStatusCode
6+
import org.lightningdevkit.ldknode.ConfirmationStatus
67
import org.lightningdevkit.ldknode.Network
78
import org.lightningdevkit.ldknode.PaymentDetails
89
import org.lightningdevkit.ldknode.PaymentDirection
10+
import org.lightningdevkit.ldknode.PaymentKind
911
import org.lightningdevkit.ldknode.PaymentStatus
1012
import to.bitkit.async.ServiceQueue
1113
import to.bitkit.env.Env
@@ -46,6 +48,7 @@ import uniffi.bitkitcore.openChannel
4648
import uniffi.bitkitcore.removeTags
4749
import uniffi.bitkitcore.updateActivity
4850
import uniffi.bitkitcore.updateBlocktankUrl
51+
import uniffi.bitkitcore.upsertActivity
4952
import javax.inject.Inject
5053
import javax.inject.Singleton
5154
import kotlin.random.Random
@@ -164,47 +167,110 @@ class ActivityService(
164167
ServiceQueue.CORE.background {
165168
var addedCount = 0
166169
var updatedCount = 0
167-
168-
for (payment in payments) { // Skip pending inbound payments, just means they created an invoice
169-
if (payment.status == PaymentStatus.PENDING && payment.direction == PaymentDirection.INBOUND) {
170-
continue
171-
}
172-
173-
val state = when (payment.status) {
174-
PaymentStatus.FAILED -> PaymentState.FAILED
175-
PaymentStatus.PENDING -> PaymentState.PENDING
176-
PaymentStatus.SUCCEEDED -> PaymentState.SUCCEEDED
170+
var latestCaughtError: Throwable? = null
171+
172+
for (payment in payments) {
173+
try {
174+
val state = when (payment.status) {
175+
PaymentStatus.FAILED -> PaymentState.FAILED
176+
PaymentStatus.PENDING -> PaymentState.PENDING
177+
PaymentStatus.SUCCEEDED -> PaymentState.SUCCEEDED
178+
}
179+
180+
when (val kind = payment.kind) {
181+
is PaymentKind.Onchain -> {
182+
var isConfirmed = false
183+
var confirmedTimestamp: ULong? = null
184+
185+
val status = kind.status
186+
if (status is ConfirmationStatus.Confirmed) {
187+
isConfirmed = true
188+
confirmedTimestamp = status.timestamp
189+
}
190+
191+
// Ensure confirmTimestamp is at least equal to timestamp when confirmed
192+
val timestamp = payment.latestUpdateTimestamp
193+
194+
if (isConfirmed && confirmedTimestamp != null && confirmedTimestamp < timestamp) {
195+
confirmedTimestamp = timestamp
196+
}
197+
198+
val onchain = OnchainActivity(
199+
id = payment.id,
200+
txType = payment.direction.toPaymentType(),
201+
txId = kind.txid,
202+
value = payment.amountSats ?: 0u,
203+
fee = (payment.feePaidMsat ?: 0u) / 1000u,
204+
feeRate = 1u, // TODO: get from somewhere
205+
address = "todo_find_address", // TODO: find address
206+
confirmed = isConfirmed,
207+
timestamp = timestamp,
208+
isBoosted = false, // TODO: handle
209+
isTransfer = false, // TODO: handle when paying for order
210+
doesExist = true,
211+
confirmTimestamp = confirmedTimestamp,
212+
channelId = null, // TODO: get from linked order
213+
transferTxId = null, // TODO: get from linked order
214+
createdAt = timestamp,
215+
updatedAt = timestamp,
216+
)
217+
218+
if (getActivityById(payment.id) != null) {
219+
updateActivity(payment.id, Activity.Onchain(onchain))
220+
updatedCount++
221+
} else {
222+
upsertActivity(Activity.Onchain(onchain))
223+
addedCount++
224+
}
225+
}
226+
227+
is PaymentKind.Bolt11 -> {
228+
// Skip pending inbound payments, just means they created an invoice
229+
if (payment.status == PaymentStatus.PENDING && payment.direction == PaymentDirection.INBOUND) {
230+
continue
231+
}
232+
233+
val ln = LightningActivity(
234+
id = payment.id,
235+
txType = payment.direction.toPaymentType(),
236+
status = state,
237+
value = payment.amountSats ?: 0u,
238+
fee = null, // TODO
239+
invoice = "lnbc123", // TODO
240+
message = "",
241+
timestamp = payment.latestUpdateTimestamp,
242+
preimage = null,
243+
createdAt = payment.latestUpdateTimestamp,
244+
updatedAt = payment.latestUpdateTimestamp,
245+
)
246+
247+
if (getActivityById(payment.id) != null) {
248+
updateActivity(payment.id, Activity.Lightning(ln))
249+
updatedCount++
250+
} else {
251+
upsertActivity(Activity.Lightning(ln))
252+
addedCount++
253+
}
254+
}
255+
256+
else -> Unit // Handle spontaneous payments if needed
257+
}
258+
} catch (e: Throwable) {
259+
Logger.error("Error syncing LDK payment:", e, context = "CoreService")
260+
latestCaughtError = e
177261
}
178-
179-
val ln = LightningActivity(
180-
id = payment.id,
181-
txType = if (payment.direction == PaymentDirection.OUTBOUND) PaymentType.SENT else PaymentType.RECEIVED,
182-
status = state,
183-
value = payment.amountSats ?: 0u,
184-
fee = null, // TODO
185-
invoice = "lnbc123", // TODO
186-
message = "",
187-
timestamp = payment.latestUpdateTimestamp,
188-
preimage = null,
189-
createdAt = payment.latestUpdateTimestamp,
190-
updatedAt = payment.latestUpdateTimestamp,
191-
)
192-
193-
if (getActivityById(payment.id) != null) {
194-
updateActivity(payment.id, Activity.Lightning(ln))
195-
updatedCount++
196-
} else {
197-
insertActivity(Activity.Lightning(ln))
198-
addedCount++
199-
}
200-
201-
// TODO: handle onchain activity when it comes in ldk-node
202262
}
203263

204-
Logger.info("Synced LDK payments - Added: $addedCount, Updated: $updatedCount")
264+
// If any of the inserts failed, we want to throw the error up
265+
latestCaughtError?.let { throw it }
266+
267+
Logger.info("Synced LDK payments - Added: $addedCount - Updated: $updatedCount", context = "CoreService")
205268
}
206269
}
207270

271+
private fun PaymentDirection.toPaymentType(): PaymentType =
272+
if (this == PaymentDirection.OUTBOUND) PaymentType.SENT else PaymentType.RECEIVED
273+
208274
suspend fun getActivity(id: String): Activity? {
209275
return ServiceQueue.CORE.background {
210276
getActivityById(id)
@@ -240,7 +306,7 @@ class ActivityService(
240306

241307
// MARK: - Tag Methods
242308

243-
suspend fun appendTags(toActivityId: String, tags: List<String>) : Result<Unit>{
309+
suspend fun appendTags(toActivityId: String, tags: List<String>): Result<Unit> {
244310
return try {
245311
ServiceQueue.CORE.background {
246312
addTags(toActivityId, tags)

app/src/main/java/to/bitkit/services/LightningService.kt

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ import kotlinx.coroutines.launch
1111
import kotlinx.coroutines.withTimeout
1212
import org.lightningdevkit.ldknode.Address
1313
import org.lightningdevkit.ldknode.AnchorChannelsConfig
14+
import org.lightningdevkit.ldknode.BackgroundSyncConfig
1415
import org.lightningdevkit.ldknode.BalanceDetails
1516
import org.lightningdevkit.ldknode.Bolt11Invoice
17+
import org.lightningdevkit.ldknode.Bolt11InvoiceDescription
1618
import org.lightningdevkit.ldknode.BuildException
1719
import org.lightningdevkit.ldknode.Builder
1820
import org.lightningdevkit.ldknode.ChannelDetails
1921
import org.lightningdevkit.ldknode.EsploraSyncConfig
2022
import org.lightningdevkit.ldknode.Event
21-
import org.lightningdevkit.ldknode.LogLevel
23+
import org.lightningdevkit.ldknode.FeeRate
2224
import org.lightningdevkit.ldknode.Network
2325
import org.lightningdevkit.ldknode.Node
2426
import org.lightningdevkit.ldknode.NodeException
@@ -68,9 +70,7 @@ class LightningService @Inject constructor(
6870
.fromConfig(
6971
defaultConfig().apply {
7072
storageDirPath = dirPath
71-
logDirPath = dirPath
7273
network = Env.network
73-
logLevel = LogLevel.TRACE
7474

7575
trustedPeers0conf = Env.trustedLnPeers.map { it.nodeId }
7676
anchorChannelsConfig = AnchorChannelsConfig(
@@ -79,12 +79,15 @@ class LightningService @Inject constructor(
7979
)
8080
})
8181
.apply {
82+
setFilesystemLogger(Env.ldkLogFilePath(walletIndex), Env.ldkLogLevel)
8283
setChainSourceEsplora(
8384
serverUrl = Env.esploraServerUrl,
8485
config = EsploraSyncConfig(
85-
onchainWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
86-
lightningWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
87-
feeRateCacheUpdateIntervalSecs = Env.walletSyncIntervalSecs,
86+
BackgroundSyncConfig(
87+
onchainWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
88+
lightningWalletSyncIntervalSecs = Env.walletSyncIntervalSecs,
89+
feeRateCacheUpdateIntervalSecs = Env.walletSyncIntervalSecs,
90+
),
8891
),
8992
)
9093
if (Env.ldkRgsServerUrl != null) {
@@ -266,7 +269,7 @@ class LightningService @Inject constructor(
266269
peer: LnPeer,
267270
channelAmountSats: ULong,
268271
pushToCounterpartySats: ULong? = null,
269-
) : Result<UserChannelId> {
272+
): Result<UserChannelId> {
270273
val node = this.node ?: throw ServiceError.NodeNotSetup
271274

272275
return ServiceQueue.LDK.background {
@@ -311,10 +314,23 @@ class LightningService @Inject constructor(
311314
return ServiceQueue.LDK.background {
312315
if (sat != null) {
313316
Logger.debug("Creating bolt11 for $sat sats")
314-
node.bolt11Payment().receive(sat.millis, description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }, expirySecs)
317+
node.bolt11Payment()
318+
.receive(
319+
amountMsat = sat.millis,
320+
description = Bolt11InvoiceDescription.Direct(
321+
description = description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }
322+
),
323+
expirySecs = expirySecs,
324+
)
315325
} else {
316326
Logger.debug("Creating bolt11 for variable amount")
317-
node.bolt11Payment().receiveVariableAmount(description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }, expirySecs)
327+
node.bolt11Payment()
328+
.receiveVariableAmount(
329+
description = Bolt11InvoiceDescription.Direct(
330+
description = description.ifBlank { Env.DEFAULT_INVOICE_MESSAGE }
331+
),
332+
expirySecs = expirySecs,
333+
)
318334
}
319335
}
320336
}
@@ -338,13 +354,18 @@ class LightningService @Inject constructor(
338354
return true
339355
}
340356

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

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

346363
return ServiceQueue.LDK.background {
347-
node.onchainPayment().sendToAddress(address, sats)
364+
node.onchainPayment().sendToAddress(
365+
address = address,
366+
amountSats = sats,
367+
feeRate = FeeRate.fromSatPerKwu(satKwu)
368+
)
348369
}
349370
}
350371

@@ -373,8 +394,12 @@ class LightningService @Inject constructor(
373394
}
374395
val event = node.nextEventAsync()
375396

376-
Logger.debug("LDK eventHandled: $event")
377-
node.eventHandled()
397+
try {
398+
node.eventHandled()
399+
Logger.debug("LDK eventHandled: $event")
400+
} catch (e: NodeException) {
401+
Logger.error("LDK eventHandled error", LdkError(e))
402+
}
378403

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

439+
is Event.PaymentForwarded -> Unit
440+
414441
is Event.ChannelPending -> {
415442
val channelId = event.channelId
416443
val userChannelId = event.userChannelId

app/src/main/java/to/bitkit/services/MigrationService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,6 @@ class MigrationService @Inject constructor(
113113
private const val KEY = "key"
114114
private const val VALUE = "value"
115115
private const val LDK_DB_NAME = "$LDK_NODE_DATA.sqlite"
116-
private const val LDK_DB_VERSION = 2
116+
private const val LDK_DB_VERSION = 2 // Should match SCHEMA_USER_VERSION from ldk-node
117117
}
118118
}

0 commit comments

Comments
 (0)