diff --git a/app/src/main/java/to/bitkit/services/MigrationService.kt b/app/src/main/java/to/bitkit/services/MigrationService.kt index 7456c8686..42fe920c0 100644 --- a/app/src/main/java/to/bitkit/services/MigrationService.kt +++ b/app/src/main/java/to/bitkit/services/MigrationService.kt @@ -90,6 +90,7 @@ class MigrationService @Inject constructor( private const val RN_PENDING_METADATA_KEY = "rnPendingMetadata" private const val RN_PENDING_TRANSFERS_KEY = "rnPendingTransfers" private const val RN_PENDING_BOOSTS_KEY = "rnPendingBoosts" + private const val RN_DID_ATTEMPT_PEER_RECOVERY_KEY = "rnDidAttemptMigrationPeerRecovery" private const val OPENING_CURLY_BRACE = "{" private const val MMKV_ROOT = "persist:root" private const val RN_WALLET_NAME = "wallet0" @@ -1251,6 +1252,44 @@ class MigrationService @Inject constructor( Logger.info("RN migration completed, marked for post-migration sync", context = TAG) } + private suspend fun isRnMigrationCompleted(): Boolean { + val key = stringPreferencesKey(RN_MIGRATION_COMPLETED_KEY) + return rnMigrationStore.data.first()[key] == "true" + } + + private suspend fun didAttemptPeerRecovery(): Boolean { + val key = stringPreferencesKey(RN_DID_ATTEMPT_PEER_RECOVERY_KEY) + return rnMigrationStore.data.first()[key] == "true" + } + + private suspend fun setDidAttemptPeerRecovery() { + val key = stringPreferencesKey(RN_DID_ATTEMPT_PEER_RECOVERY_KEY) + rnMigrationStore.edit { it[key] = "true" } + } + + suspend fun tryFetchMigrationPeersFromBackup(): List { + if (!isRnMigrationCompleted()) return emptyList() + if (didAttemptPeerRecovery()) return emptyList() + + setDidAttemptPeerRecovery() + + return runCatching { + val data = rnBackupClient.retrieve("peers", fileGroup = "ldk") ?: return emptyList() + val peers = json.decodeFromString>(String(data)) + if (peers.isEmpty()) return emptyList() + + val trustedIds = Env.trustedLnPeers.map { it.nodeId }.toSet() + val uris = peers + .filter { it.pubKey !in trustedIds } + .map { "${it.pubKey}@${it.address}:${it.port}" } + + Logger.info("Migration peer recovery: fetched ${uris.size} peer(s) from remote backup", context = TAG) + uris + }.onFailure { + Logger.warn("Migration peer recovery failed (will not retry)", it, context = TAG) + }.getOrDefault(emptyList()) + } + suspend fun cleanupAfterMigration() { clearPersistedMigrationData() setNeedsPostMigrationSync(false) @@ -2076,6 +2115,13 @@ data class RNWidgetsWithOptions( val widgetOptions: Map, ) +@Serializable +data class BackupPeerEntry( + val pubKey: String, + val address: String, + val port: UShort, +) + private val Context.rnMigrationDataStore: DataStore by preferencesDataStore("rn_migration") private val Context.rnKeychainDataStore: DataStore by preferencesDataStore("RN_KEYCHAIN") diff --git a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt index cd2b5ee43..4e38e5ddd 100644 --- a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt @@ -26,6 +26,7 @@ import org.lightningdevkit.ldknode.PeerDetails import to.bitkit.R import to.bitkit.data.SettingsStore import to.bitkit.di.BgDispatcher +import to.bitkit.ext.of import to.bitkit.models.Toast import to.bitkit.repositories.BackupRepo import to.bitkit.repositories.BlocktankRepo @@ -291,6 +292,7 @@ class WalletViewModel @Inject constructor( migrationService.consumePendingChannelMigration() } walletRepo.setWalletExistsState() + connectMigrationPeers() walletRepo.syncBalances() if (_restoreState.value.isIdle()) { walletRepo.refreshBip21() @@ -304,6 +306,18 @@ class WalletViewModel @Inject constructor( } } + private suspend fun connectMigrationPeers() { + val peerUris = migrationService.tryFetchMigrationPeersFromBackup() + for (uri in peerUris) { + runCatching { + val peer = PeerDetails.of(uri) + lightningRepo.connectPeer(peer) + }.onFailure { + Logger.error("Failed to connect migration peer: $uri", it, context = TAG) + } + } + } + fun stop() { if (!walletExists) return diff --git a/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt b/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt index fdfbacb49..817350c9c 100644 --- a/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt +++ b/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt @@ -57,6 +57,7 @@ class WalletViewModelTest : BaseUnitTest() { whenever(walletRepo.walletState).thenReturn(walletState) whenever(lightningRepo.lightningState).thenReturn(lightningState) whenever(migrationService.isMigrationChecked()).thenReturn(true) + whenever(migrationService.tryFetchMigrationPeersFromBackup()).thenReturn(emptyList()) whenever(connectivityRepo.isOnline).thenReturn(isOnline) sut = WalletViewModel(