@@ -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