From 2e6d5e3d743fb900a0836e71d22c65784e3e36b2 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 20 Jan 2026 11:31:27 -0300 Subject: [PATCH 1/3] fix: add defensive check to retry flow --- .../to/bitkit/repositories/LightningRepo.kt | 6 +++++ .../bitkit/repositories/LightningRepoTest.kt | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index d0c4c2ee5..5fa27d84d 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -256,6 +256,12 @@ class LightningRepo @Inject constructor( scope.launch { registerForNotifications() } Unit }.onFailure { e -> + val currentLifecycleState = _lightningState.value.nodeLifecycleState + if (currentLifecycleState.isRunningOrStarting()) { + Logger.warn("Start error occurred but node is $currentLifecycleState, skipping retry", e, context = TAG) + return@withContext Result.success(Unit) + } + if (shouldRetry) { val retryDelay = 2.seconds Logger.warn("Start error, retrying after $retryDelay...", e, context = TAG) diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index 2cd88e6c5..46947f064 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -23,6 +23,7 @@ import org.mockito.kotlin.inOrder import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.spy +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyBlocking import org.mockito.kotlin.whenever @@ -646,4 +647,26 @@ class LightningRepoTest : BaseUnitTest() { assertTrue(result.isSuccess) verify(lightningService).setup(any(), anyOrNull(), anyOrNull(), isNull(), anyOrNull()) } + + @Test + fun `start should not retry when node lifecycle state is Starting`() = test { + sut.setInitNodeLifecycleState() + whenever(lightningService.node).thenReturn(null) + whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit) + whenever(settingsStore.data).thenReturn(flowOf(SettingsData())) + val blocktank = mock() + whenever(coreService.blocktank).thenReturn(blocktank) + whenever(blocktank.info(any())).thenReturn(null) + + // Simulate: start throws (state will be Starting when onFailure is called) + whenever(lightningService.start(anyOrNull(), any())).thenThrow(RuntimeException("error")) + + val result = sut.start() + + // Defensive check: state is Starting, so don't retry, return success + assertTrue(result.isSuccess) + assertEquals(NodeLifecycleState.Starting, sut.lightningState.value.nodeLifecycleState) + // Verify start was only called once (no retry) + verify(lightningService, times(1)).start(anyOrNull(), any()) + } } From badbdc38206dfec7fe94836cb45234c8a28a8fc6 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 20 Jan 2026 12:03:56 -0300 Subject: [PATCH 2/3] fix: check only if node is runnning --- app/src/main/java/to/bitkit/repositories/LightningRepo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 5fa27d84d..615f0efac 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -257,7 +257,7 @@ class LightningRepo @Inject constructor( Unit }.onFailure { e -> val currentLifecycleState = _lightningState.value.nodeLifecycleState - if (currentLifecycleState.isRunningOrStarting()) { + if (currentLifecycleState.isRunning()) { Logger.warn("Start error occurred but node is $currentLifecycleState, skipping retry", e, context = TAG) return@withContext Result.success(Unit) } From a9e76cd6c888e752849f6627aa2d52d55ead0ccc Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 20 Jan 2026 12:10:08 -0300 Subject: [PATCH 3/3] test: update defensive check test for isRunning Co-Authored-By: Claude Opus 4.5 --- .../java/to/bitkit/repositories/LightningRepoTest.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index 46947f064..6a3695570 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -649,7 +649,7 @@ class LightningRepoTest : BaseUnitTest() { } @Test - fun `start should not retry when node lifecycle state is Starting`() = test { + fun `start should not retry when node lifecycle state is Running`() = test { sut.setInitNodeLifecycleState() whenever(lightningService.node).thenReturn(null) whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit) @@ -658,14 +658,16 @@ class LightningRepoTest : BaseUnitTest() { whenever(coreService.blocktank).thenReturn(blocktank) whenever(blocktank.info(any())).thenReturn(null) - // Simulate: start throws (state will be Starting when onFailure is called) - whenever(lightningService.start(anyOrNull(), any())).thenThrow(RuntimeException("error")) + // lightningService.start() succeeds (state becomes Running at line 241) + whenever(lightningService.start(anyOrNull(), any())).thenReturn(Unit) + // lightningService.nodeId throws during syncState() (called at line 244, AFTER state = Running) + whenever(lightningService.nodeId).thenThrow(RuntimeException("error during syncState")) val result = sut.start() - // Defensive check: state is Starting, so don't retry, return success + // Defensive check: state is Running, so don't retry, return success assertTrue(result.isSuccess) - assertEquals(NodeLifecycleState.Starting, sut.lightningState.value.nodeLifecycleState) + assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) // Verify start was only called once (no retry) verify(lightningService, times(1)).start(anyOrNull(), any()) }