Skip to content

Commit 3608f7e

Browse files
committed
fix(android): if fails to find route, reset graph, scorer, sync both again and retry payment
1 parent a3e02ee commit 3608f7e

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

lib/android/src/main/java/com/reactnativeldk/LdkModule.kt

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
184184
private var currentNetwork: String? = null
185185
private var currentBlockchainTipHash: String? = null
186186
private var currentBlockchainHeight: Double? = null
187+
private var currentScorerDownloadUrl: String? = null
188+
private var currentRapidGossipSyncUrl: String? = null
187189

188190
//List of peers that "should" remain connected. Stores address: String, port: Double, pubKey: String
189191
private var addedPeers = ConcurrentLinkedQueue<Map<String, Any>>()
@@ -291,6 +293,9 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
291293
@ReactMethod
292294
fun downloadScorer(scorerSyncUrl: String, skipHoursThreshold: Double, promise: Promise) {
293295
val scorerFile = File(accountStoragePath + "/" + LdkFileNames.Scorer.fileName)
296+
297+
currentScorerDownloadUrl = scorerSyncUrl
298+
294299
//If old one is still recent, skip download. Else delete it.
295300
if (scorerFile.exists()) {
296301
val lastModifiedHours = (System.currentTimeMillis().toDouble() - scorerFile.lastModified().toDouble()) / 1000 / 60 / 60
@@ -328,6 +333,8 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
328333
return handleReject(promise, LdkErrors.already_init)
329334
}
330335

336+
currentRapidGossipSyncUrl = rapidGossipSyncUrl
337+
331338
val networkGraphFile = File(accountStoragePath + "/" + LdkFileNames.NetworkGraph.fileName)
332339
if (networkGraphFile.exists()) {
333340
(NetworkGraph.read(networkGraphFile.readBytes(), logger.logger) as? Result_NetworkGraphDecodeErrorZ.Result_NetworkGraphDecodeErrorZ_OK)?.let { res ->
@@ -924,10 +931,107 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
924931
promise.resolve(parsedInvoice.res.asJson)
925932
}
926933

934+
private fun resetGraphAndScorerAndRetryPayment(
935+
originalError: LdkErrors,
936+
paymentRequest: String,
937+
amountSats: Double,
938+
timeoutSeconds: Double,
939+
promise: Promise
940+
) {
941+
if (accountStoragePath == "") {
942+
LdkEventEmitter.send(EventTypes.native_log, "Failed to reset graph: account storage path not set")
943+
return handleReject(promise, originalError)
944+
}
945+
946+
// Check required data and URLs
947+
val currentNetwork = currentNetwork ?: return handleReject(promise, originalError)
948+
949+
if (currentRapidGossipSyncUrl.isNullOrEmpty() || currentScorerDownloadUrl.isNullOrEmpty()) {
950+
val missingUrl = if (currentRapidGossipSyncUrl.isNullOrEmpty()) "rapid gossip sync" else "scorer download"
951+
LdkEventEmitter.send(EventTypes.native_log, "Failed to reset graph: $missingUrl URL not set")
952+
return handleReject(promise, originalError)
953+
}
954+
955+
val scorerFile = File("$accountStoragePath/${LdkFileNames.Scorer.fileName}")
956+
val networkGraphFile = File("$accountStoragePath/${LdkFileNames.NetworkGraph.fileName}")
957+
958+
// Delete scorer if exists
959+
if (scorerFile.exists()) {
960+
try {
961+
scorerFile.delete()
962+
LdkEventEmitter.send(EventTypes.native_log, "Deleted scorer file")
963+
} catch (e: Exception) {
964+
LdkEventEmitter.send(EventTypes.native_log, "Failed to delete scorer file: ${e.localizedMessage}")
965+
}
966+
}
967+
968+
// Delete network graph if exists
969+
if (networkGraphFile.exists()) {
970+
try {
971+
networkGraphFile.delete()
972+
LdkEventEmitter.send(EventTypes.native_log, "Deleted network graph file")
973+
networkGraph = null
974+
} catch (e: Exception) {
975+
LdkEventEmitter.send(EventTypes.native_log, "Failed to delete network graph file: ${e.localizedMessage}")
976+
}
977+
}
978+
979+
LdkEventEmitter.send(EventTypes.native_log, "Deleted scorer and network graph, resyncing from scratch so we can retry payment")
980+
981+
// Download everything again and retry
982+
downloadScorer(currentScorerDownloadUrl!!, 1.0, object : PromiseImpl(
983+
{ _ ->
984+
LdkEventEmitter.send(EventTypes.native_log, "Scorer downloaded, initializing network graph...")
985+
initNetworkGraph(currentNetwork, currentRapidGossipSyncUrl!!, 1.0, object : PromiseImpl(
986+
{ _ ->
987+
LdkEventEmitter.send(EventTypes.native_log, "Network graph initialized, restarting channel manager...")
988+
restart(object : PromiseImpl(
989+
{ _ ->
990+
// Run handleDroppedPeers on a background thread (can't work in the UI thread)
991+
Thread {
992+
handleDroppedPeers()
993+
}.start()
994+
995+
Thread.sleep(2500) //Wait a little as android peer connections happen async so we're just making sure they're all connected
996+
val channelsInGraph = networkGraph?.read_only()?.list_channels()?.size
997+
LdkEventEmitter.send(EventTypes.native_log, "Channels found in graph: $channelsInGraph")
998+
LdkEventEmitter.send(EventTypes.native_log, "Peers connected: ${peerManager?.list_peers()?.size}")
999+
LdkEventEmitter.send(EventTypes.native_log, "Restart complete. Attempting to retry payment after graph reset...")
1000+
val (paymentId2, error2) = handlePayment(paymentRequest, amountSats, timeoutSeconds)
1001+
1002+
if (error2 != null) {
1003+
LdkEventEmitter.send(EventTypes.native_log, "Failed to retry payment after graph reset: $error2")
1004+
handleReject(promise, error2)
1005+
} else {
1006+
LdkEventEmitter.send(EventTypes.native_log, "Successfully retried payment after graph reset")
1007+
// 2nd attempt found a path with fresh graph
1008+
promise.resolve(paymentId2)
1009+
}
1010+
},
1011+
{ _ -> handleReject(promise, originalError) }
1012+
) {})
1013+
},
1014+
{ _ -> handleReject(promise, originalError) }
1015+
) {})
1016+
},
1017+
{ _ -> handleReject(promise, originalError) }
1018+
) {})
1019+
}
1020+
9271021
@ReactMethod
9281022
fun pay(paymentRequest: String, amountSats: Double, timeoutSeconds: Double, promise: Promise) {
9291023
val (paymentId, error) = handlePayment(paymentRequest, amountSats, timeoutSeconds)
9301024
if (error != null) {
1025+
// If error is route not found, maybe a problem with the graph, so reset it, download all again and try payment one more time
1026+
if (error == LdkErrors.invoice_payment_fail_route_not_found) {
1027+
return resetGraphAndScorerAndRetryPayment(
1028+
error,
1029+
paymentRequest,
1030+
amountSats,
1031+
timeoutSeconds,
1032+
promise
1033+
)
1034+
}
9311035
return handleReject(promise, error)
9321036
}
9331037
return promise.resolve(paymentId)

0 commit comments

Comments
 (0)