Skip to content

Commit a6f0f40

Browse files
committed
fix: check if RGS url is reachable
1 parent 3b75e1e commit a6f0f40

4 files changed

Lines changed: 67 additions & 0 deletions

File tree

app/src/main/java/to/bitkit/di/HttpModule.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ import io.ktor.client.plugins.defaultRequest
1212
import io.ktor.client.plugins.logging.LogLevel
1313
import io.ktor.client.plugins.logging.Logging
1414
import io.ktor.client.plugins.logging.LoggingConfig
15+
import io.ktor.client.request.head
1516
import io.ktor.http.ContentType
1617
import io.ktor.http.contentType
18+
import io.ktor.http.isSuccess
1719
import io.ktor.serialization.kotlinx.json.json
1820
import kotlinx.serialization.json.Json
21+
import to.bitkit.utils.UrlValidator
22+
import to.bitkit.utils.AppError
1923
import to.bitkit.utils.Logger
2024
import javax.inject.Singleton
2125
import io.ktor.client.plugins.logging.Logger as KtorLogger
@@ -43,6 +47,17 @@ object HttpModule {
4347
}
4448
}
4549

50+
@Provides
51+
@Singleton
52+
fun provideUrlValidator(httpClient: HttpClient) = UrlValidator { url ->
53+
runCatching {
54+
val response = httpClient.head(url)
55+
if (!response.status.isSuccess()) {
56+
throw AppError("Server returned '${response.status}'")
57+
}
58+
}
59+
}
60+
4661
@Suppress("MagicNumber")
4762
private fun HttpTimeoutConfig.defaultTimeoutConfig() {
4863
requestTimeoutMillis = 60_000

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import to.bitkit.services.NodeEventHandler
7979
import to.bitkit.utils.AppError
8080
import to.bitkit.utils.Logger
8181
import to.bitkit.utils.ServiceError
82+
import to.bitkit.utils.UrlValidator
8283
import java.io.File
8384
import java.util.concurrent.ConcurrentHashMap
8485
import java.util.concurrent.atomic.AtomicBoolean
@@ -105,6 +106,7 @@ class LightningRepo @Inject constructor(
105106
private val preActivityMetadataRepo: PreActivityMetadataRepo,
106107
private val connectivityRepo: ConnectivityRepo,
107108
private val vssBackupClientLdk: VssBackupClientLdk,
109+
private val urlValidator: UrlValidator,
108110
) {
109111
private val _lightningState = MutableStateFlow(LightningState())
110112
val lightningState = _lightningState.asStateFlow()
@@ -620,6 +622,8 @@ class LightningRepo @Inject constructor(
620622
runCatching {
621623
Logger.info("Changing ldk-node RGS server to: '$newRgsUrl'", context = TAG)
622624

625+
validateRgsUrl(newRgsUrl).onFailure { return@runCatching Result.failure(it) }
626+
623627
waitForNodeToStop().onFailure { return@runCatching Result.failure(it) }
624628
stop().onFailure {
625629
Logger.error("Failed to stop node during RGS server change", it, context = TAG)
@@ -645,6 +649,14 @@ class LightningRepo @Inject constructor(
645649
}
646650
}
647651

652+
private suspend fun validateRgsUrl(url: String): Result<Unit> = withContext(bgDispatcher) {
653+
val initialTimestamp = 0
654+
val testUrl = "${url.trimEnd('/')}/$initialTimestamp"
655+
urlValidator.validate(testUrl).onFailure {
656+
Logger.warn("RGS server unreachable at '$testUrl'", it, context = TAG)
657+
}
658+
}
659+
648660
suspend fun getBalanceForAddressType(addressType: AddressType): Result<ULong> = withContext(bgDispatcher) {
649661
executeWhenNodeRunning("getBalanceForAddressType") {
650662
runCatching {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package to.bitkit.utils
2+
3+
fun interface UrlValidator {
4+
suspend fun validate(url: String): Result<Unit>
5+
}

app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import to.bitkit.services.LightningService
5151
import to.bitkit.services.LnurlService
5252
import to.bitkit.services.LspNotificationsService
5353
import to.bitkit.test.BaseUnitTest
54+
import to.bitkit.utils.UrlValidator
5455
import kotlin.test.assertEquals
5556
import kotlin.test.assertFalse
5657
import kotlin.test.assertNotNull
@@ -72,6 +73,7 @@ class LightningRepoTest : BaseUnitTest() {
7273
private val lnurlService = mock<LnurlService>()
7374
private val connectivityRepo = mock<ConnectivityRepo>()
7475
private val vssBackupClientLdk = mock<VssBackupClientLdk>()
76+
private val urlValidator = UrlValidator { Result.success(Unit) }
7577

7678
@Before
7779
fun setUp() = runBlocking {
@@ -94,6 +96,7 @@ class LightningRepoTest : BaseUnitTest() {
9496
preActivityMetadataRepo = preActivityMetadataRepo,
9597
connectivityRepo = connectivityRepo,
9698
vssBackupClientLdk = vssBackupClientLdk,
99+
urlValidator = urlValidator,
97100
)
98101
}
99102

@@ -538,6 +541,38 @@ class LightningRepoTest : BaseUnitTest() {
538541
assertTrue(result.isFailure)
539542
}
540543

544+
@Test
545+
fun `restartWithRgsServer should fail when url is unreachable`() = test {
546+
val failingValidator = UrlValidator { Result.failure(Exception("DNS resolution failed")) }
547+
val sutWithFailingValidator = LightningRepo(
548+
bgDispatcher = testDispatcher,
549+
lightningService = lightningService,
550+
settingsStore = settingsStore,
551+
coreService = coreService,
552+
lspNotificationsService = lspNotificationsService,
553+
firebaseMessaging = firebaseMessaging,
554+
keychain = keychain,
555+
lnurlService = lnurlService,
556+
cacheStore = cacheStore,
557+
preActivityMetadataRepo = preActivityMetadataRepo,
558+
connectivityRepo = connectivityRepo,
559+
vssBackupClientLdk = vssBackupClientLdk,
560+
urlValidator = failingValidator,
561+
)
562+
sutWithFailingValidator.setInitNodeLifecycleState()
563+
whenever(lightningService.node).thenReturn(mock())
564+
whenever(lightningService.sync()).thenReturn(Unit)
565+
val blocktank = mock<BlocktankService>()
566+
whenever(coreService.blocktank).thenReturn(blocktank)
567+
whenever(blocktank.info(any())).thenReturn(null)
568+
sutWithFailingValidator.start()
569+
570+
val result = sutWithFailingValidator.restartWithRgsServer("https://rapidsync.lightningdevkit/snapshot")
571+
572+
assertTrue(result.isFailure)
573+
assertEquals("DNS resolution failed", result.exceptionOrNull()?.message)
574+
}
575+
541576
@Test
542577
fun `getFeeRateForSpeed should use provided feeRates`() = test {
543578
val mockFeeRates = mock<FeeRates>()

0 commit comments

Comments
 (0)