fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: sync exception causing infinite loop on start - #657

Merged
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding
Jan 20, 2026
Merged

fix: sync exception causing infinite loop on start#657
jvsena42 merged 7 commits into
masterfrom
fix/bitkit-not-responding

Conversation

@jvsena42

@jvsena42jvsena42 commented Jan 16, 2026

Copy link
Copy Markdown
Member

Fixes#652
Fixes#676
Fixes#662

This PR fixes the "Bitkit keeps stopping" crash that occurs after wallet restoration when LDK's transaction sync encounters an unconfirmed channel funding transaction on Electrum.

Description

The root cause was that after wallet restoration, LDK couldn't find a confirmed channel's funding transaction on Electrum, because it is a zero-conf channel restored before the transaction is confirmed, causing an infinite retry loop until timeout.
Also the retry flow was setting the node state as initialLifecycleState on repository, even it being running on service

Preview

master-with-crash.webm
fixed.webm

Uploading migration.webm…

QA Notes

1. Verify sync error handling

  1. Receive 10K sats on CJIT
  2. Reset and restored the wallet before the channel is confirmed
  3. Verify the app remains responsive and doesn't crash
  4. Perform LN transactions

2. Migration

  1. Instal the RN app
  2. Receive 10K sats on CJIT
  3. Install this branch and trigger migration
  4. Verify the app remains responsive and doesn't crash
  5. Perform LN transactions

@jvsena42jvsena42 changed the title fix: sync exception not caught causing app crashfix: sync exception caught causing app crashJan 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 16:53
@jvsena42
jvsena42 requested a review from CopilotJanuary 19, 2026 16:55
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt

This comment was marked as outdated.

@claude

This comment has been minimized.

@jvsena42
jvsena42 marked this pull request as draft January 19, 2026 17:25
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as ready for review January 19, 2026 17:32
@claude

This comment has been minimized.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 requested a review from ovitrifJanuary 20, 2026 10:00
@jvsena42jvsena42 self-assigned this Jan 20, 2026
@jvsena42jvsena42 changed the title fix: sync exception caught causing app crashfix: sync exception causing infinite loop on startJan 20, 2026
@jvsena42jvsena42 mentioned this pull request Jan 20, 2026
@jvsena42
jvsena42 enabled auto-merge January 20, 2026 12:35
@piotr-iohk

Copy link
Copy Markdown
Collaborator

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though.
What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI:
Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

@jvsena42

Copy link
Copy Markdown
MemberAuthor

I managed to crash Bitkit on this branch, could be not related with the changes, nor the original issue though. What I did was:

  • Receive 10K sats on CJIT
  • reset and restore wallet (although CJIT channel was already opened)
adb logcat -d | grep -A 50 "01-20 11:48:18.381.*FATAL EXCEPTION: DefaultDispatcher-worker-6"
01-20 11:48:18.381 12278 12322 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-6
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Process: to.bitkit.dev, PID: 12278
01-20 11:48:18.381 12278 12322 E AndroidRuntime: to.bitkit.utils.ServiceError$MnemonicNotFound: Mnemonic not found
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssStoreIdProvider.getVssStoreId(VssStoreIdProvider.kt:23)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invokeSuspend(VssBackupClient.kt:38)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2$1$1.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invokeSuspend(VssBackupClient.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:8)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient$setup$2.invoke(Unknown Source:4)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.data.backup.VssBackupClient.setup-gIAlu-s$default(VssBackupClient.kt:32)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at to.bitkit.repositories.BackupRepo$startObservingBackups$1.invokeSuspend(BackupRepo.kt:123)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
01-20 11:48:18.381 12278 12322 E AndroidRuntime: Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c714896, Dispatchers.IO]
01-20 11:48:18.385 506 12606 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-20 11:48:18.394 506 521 W ActivityTaskManager: Force finishing activity to.bitkit.dev/to.bitkit.ui.MainActivity
01-20 11:48:18.414 12278 12322 I Process : Sending signal. PID: 12278 SIG: 9
01-20 11:48:18.414 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
01-20 11:48:18.415 506 536 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver
01-20 11:48:18.417 1064 1064 D MainContentCaptureSession: Flushing 1 event(s) for act:com.google.android.apps.nexuslauncher/.NexusLauncherActivity [state=2 (ACTIVE), disabled=false], reason=FULL
01-20 11:48:18.421 506 1731 W AppSearchIcing: icing-search-engine.cc:217: Error: 5, Message: Document (com.google.android.googlequicksearchbox$OneSearchZeroStateGoogleSuggestions/default, zp) not found.
01-20 11:48:18.426 14758 15605 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.427 1064 2152 D OneSearchSuggestProvider: Created the binder channel successfully for end point service =com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService , mChannel=Q0{delegate=L0{logId=557, target=directaddress:///AndroidComponentAddress%5BIntent%20%7B%20act=grpc.io.action.BIND%20cmp=com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.search.suggest.plugins.onesearch.server.OneSearchSuggestService%20%7D%5D}} , mOneSearchConnection=y1.J@66c1ae1
01-20 11:48:18.428 1064 2002 E OpenGLRenderer: Unable to match the desired swap behavior.
01-20 11:48:18.430 1665 1942 I AiAiEcho: Predicting[0]: 01-20 11:48:18.430 1665 1942 I AiAiEcho: Ranked targets strategy: SORT, count: 0, ranking metadata: 01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface lockscreen with targets# 0
01-20 11:48:18.431 1665 1942 I AiAiEcho: #postPredictionTargets: Sending updates to UISurface home with targets# 0
01-20 11:48:18.468 506 2087 W InputMethodManagerService: Got RemoteException sending setActive(false) notification to pid 12278 uid 10232
01-20 11:48:18.469 2087 2087 I binder_alloc: 12278: binder_alloc_buf, no vma
01-20 11:48:18.469 2087 2087 I binder : 506:2087 transaction failed 29189/-3, size 120-0 line 3371
01-20 11:48:18.469 1543 1543 I GoogleInputMethodService: GoogleInputMethodService.onFinishInput():3227 01-20 11:48:18.470 506 569 I ActivityManager: Process to.bitkit.dev (pid 12278) has died: fg +50 FGS 01-20 11:48:18.471 506 538 I libprocessgroup: Successfully killed process cgroup uid 10232 pid 12278 in 0ms

Accoriging to AI: Root Cause: Race condition - VssBackupClient.setup() is being called before the mnemonic is available in the keychain. The VSS store ID provider needs the mnemonic to generate the unique store ID, but BackupRepo.startObservingBackups() is triggered too early in the startup sequence.

AI proposed fix:

Summary of Changes

1. VssBackupClient.kt

  • Changed setup() return type from Unit to Boolean:
  • Returns true if setup succeeded
  • Returns false if mnemonic is not available yet (no crash)
  • Throws on other errors (network issues, etc.)
  • Added idempotency check: If already set up successfully, returns immediately without re-initializing
  • Moved mnemonic check outside runCatching: Now checks for mnemonic availability before attempting setup, preventing the MnemonicNotFound exception

2. BackupRepo.kt

  • Added setupVssClientWithRetry() function that:
  • Retries setup up to 10 times with linear backoff (1s, 2s, 3s, ...)
  • Stops retrying if stopObservingBackups() is called
  • Logs progress and final status
  • Changed startObservingBackups() to call the retry function instead of directly calling setup()

Behavior

  • When the Lightning node starts, backup observation begins
  • VSS client setup is attempted
  • If mnemonic is not available yet (race condition), setup returns false and retries after 1 second
  • Retries continue with increasing delay until mnemonic becomes available (up to 10 attempts)
  • Once mnemonic is available, setup succeeds and backups work normally
  • This prevents the crash while ensuring backups eventually start working once the mnemonic is saved to keychain.

Thanks! It is a different issue #553 . It was closed because we couldn't reproduce it again

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt Outdated
Comment threadapp/src/main/java/to/bitkit/repositories/LightningRepo.kt
@jvsena42
jvsena42 merged commit 9e6fb8c into masterJan 20, 2026
18 checks passed
@jvsena42
jvsena42 deleted the fix/bitkit-not-responding branch January 20, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ldk-node Error Rocket screen showed again after home screen [Bug] Bitkit keeps stopping Error

4 participants

@jvsena42@piotr-iohk@ovitrif