Skip to content

Commit 971f29c

Browse files
committed
Fixes
1 parent 29903db commit 971f29c

File tree

7 files changed

+80
-33
lines changed

7 files changed

+80
-33
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ android {
4646
applicationId = "to.bitkit"
4747
minSdk = 28
4848
targetSdk = 36
49-
versionCode = 160
49+
versionCode = 162
5050
versionName = "0.0.17"
5151
testInstrumentationRunner = "to.bitkit.test.HiltTestRunner"
5252
vectorDrawables {

app/src/main/java/to/bitkit/services/MigrationService.kt

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,16 @@ import to.bitkit.models.TransactionSpeed
4141
import to.bitkit.models.WidgetType
4242
import to.bitkit.models.WidgetWithPosition
4343
import to.bitkit.repositories.ActivityRepo
44+
import to.bitkit.services.core.Bip39Service
4445
import to.bitkit.utils.Logger
4546
import java.io.File
4647
import java.security.KeyStore
4748
import javax.inject.Inject
4849
import javax.inject.Singleton
4950

50-
private val Context.rnMigrationDataStore:
51-
androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> by preferencesDataStore(
52-
name = "rn_migration"
53-
)
51+
private val Context.rnMigrationDataStore: DataStore<Preferences> by preferencesDataStore(
52+
name = "rn_migration"
53+
)
5454

5555
private val Context.rnKeychainDataStore: DataStore<Preferences> by preferencesDataStore(
5656
name = "RN_KEYCHAIN"
@@ -117,6 +117,7 @@ class MigrationService @Inject constructor(
117117
private val activityRepo: ActivityRepo,
118118
private val coreService: CoreService,
119119
private val rnBackupClient: RNBackupClient,
120+
private val bip39Service: Bip39Service,
120121
) {
121122
companion object {
122123
private const val TAG = "Migration"
@@ -401,10 +402,9 @@ class MigrationService @Inject constructor(
401402
)
402403
}
403404

404-
val words = mnemonic.split(" ").filter { it.isNotBlank() }
405-
if (words.size != MNEMONIC_WORD_COUNT_12 && words.size != MNEMONIC_WORD_COUNT_24) {
405+
bip39Service.validateMnemonic(mnemonic).onFailure {
406406
throw to.bitkit.utils.AppError(
407-
"Recovery phrase format is invalid. Please use your 12 or 24 word recovery phrase to restore manually."
407+
"Recovery phrase is invalid. Please use your 12 or 24 word recovery phrase to restore manually."
408408
)
409409
}
410410

@@ -424,6 +424,20 @@ class MigrationService @Inject constructor(
424424
if (pin.isNullOrEmpty()) {
425425
return
426426
}
427+
428+
if (pin.length != Env.PIN_LENGTH) {
429+
Logger.warn(
430+
"Invalid PIN length during migration: ${pin.length}, expected: ${Env.PIN_LENGTH}",
431+
context = TAG,
432+
)
433+
return
434+
}
435+
436+
if (!pin.all { it.isDigit() }) {
437+
Logger.warn("Invalid PIN format during migration: contains non-numeric characters", context = TAG)
438+
return
439+
}
440+
427441
keychain.saveString(Keychain.Key.PIN.name, pin)
428442
}
429443

@@ -611,6 +625,9 @@ class MigrationService @Inject constructor(
611625
"branchAndBound" -> CoinSelectionPreference.BranchAndBound
612626
else -> CoinSelectionPreference.SmallestFirst
613627
},
628+
isPinEnabled = settings.pin ?: current.isPinEnabled,
629+
isPinOnLaunchEnabled = settings.pinOnLaunch ?: current.isPinOnLaunchEnabled,
630+
isPinOnIdleEnabled = settings.pinOnIdle ?: current.isPinOnIdleEnabled,
614631
isPinForPaymentsEnabled = settings.pinForPayments ?: current.isPinForPaymentsEnabled,
615632
isBiometricEnabled = settings.biometrics ?: current.isBiometricEnabled,
616633
quickPayIntroSeen = settings.quickpayIntroSeen ?: current.quickPayIntroSeen,

app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ class WalletViewModel @Inject constructor(
252252
isStarting = true
253253
try {
254254
waitForRestoreIfNeeded()
255+
256+
val hasPendingMigration = migrationService.peekPendingChannelMigration() != null
257+
val nodeState = lightningState.value.nodeLifecycleState
258+
val nodeAlreadyRunning = nodeState.isRunningOrStarting()
259+
260+
if (hasPendingMigration && nodeAlreadyRunning) {
261+
lightningRepo.stop()
262+
delay(NODE_RESTART_DELAY_MS)
263+
}
264+
255265
val channelMigration = buildChannelMigrationIfAvailable()
256266
startNode(walletIndex, channelMigration)
257267
} finally {
@@ -268,7 +278,7 @@ class WalletViewModel @Inject constructor(
268278
}
269279

270280
private fun buildChannelMigrationIfAvailable(): ChannelDataMigration? {
271-
val migration = migrationService.peekPendingChannelMigration() ?: return null
281+
val migration = migrationService.consumePendingChannelMigration() ?: return null
272282
return ChannelDataMigration(
273283
channelManager = migration.channelManager.map { it.toUByte() },
274284
channelMonitors = migration.channelMonitors.map { monitor -> monitor.map { it.toUByte() } },

app/src/test/java/to/bitkit/androidServices/LightningNodeServiceTest.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,17 @@ class LightningNodeServiceTest : BaseUnitTest() {
8181
@Before
8282
fun setUp() = runBlocking {
8383
hiltRule.inject()
84-
whenever(lightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), captor.capture()))
85-
.thenReturn(Result.success(Unit))
84+
whenever(
85+
lightningRepo.start(
86+
any(),
87+
anyOrNull(),
88+
any(),
89+
anyOrNull(),
90+
anyOrNull(),
91+
captor.capture(),
92+
anyOrNull(),
93+
)
94+
).thenReturn(Result.success(Unit))
8695
whenever(lightningRepo.stop()).thenReturn(Result.success(Unit))
8796

8897
// Set up CacheStore mock

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class LightningRepoTest : BaseUnitTest() {
8383
private suspend fun startNodeForTesting() {
8484
sut.setInitNodeLifecycleState()
8585
whenever(lightningService.node).thenReturn(mock())
86-
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
86+
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
8787
whenever(lightningService.start(anyOrNull(), any())).thenReturn(Unit)
8888
whenever(settingsStore.data).thenReturn(flowOf(SettingsData()))
8989
val blocktank = mock<BlocktankService>()
@@ -97,7 +97,7 @@ class LightningRepoTest : BaseUnitTest() {
9797
fun `start should transition through correct states`() = test {
9898
sut.setInitNodeLifecycleState()
9999
whenever(lightningService.node).thenReturn(mock())
100-
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
100+
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
101101
whenever(lightningService.start(anyOrNull(), any())).thenReturn(Unit)
102102
val blocktank = mock<BlocktankService>()
103103
whenever(coreService.blocktank).thenReturn(blocktank)
@@ -435,7 +435,7 @@ class LightningRepoTest : BaseUnitTest() {
435435
assertTrue(result.isSuccess)
436436
val inOrder = inOrder(lightningService)
437437
inOrder.verify(lightningService).stop()
438-
inOrder.verify(lightningService).setup(any(), eq(customServerUrl), anyOrNull(), anyOrNull())
438+
inOrder.verify(lightningService).setup(any(), eq(customServerUrl), anyOrNull(), anyOrNull(), anyOrNull())
439439
inOrder.verify(lightningService).start(anyOrNull(), any())
440440
assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState)
441441
}
@@ -588,7 +588,7 @@ class LightningRepoTest : BaseUnitTest() {
588588
fun `start should load trusted peers from blocktank info`() = test {
589589
sut.setInitNodeLifecycleState()
590590
whenever(lightningService.node).thenReturn(null)
591-
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
591+
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
592592
whenever(lightningService.start(anyOrNull(), any())).thenReturn(Unit)
593593
whenever(settingsStore.data).thenReturn(flowOf(SettingsData()))
594594

@@ -623,15 +623,16 @@ class LightningRepoTest : BaseUnitTest() {
623623
peers?.size == 2 &&
624624
peers.any { it.nodeId == "node1pubkey" && it.address == "node1.example.com:9735" } &&
625625
peers.any { it.nodeId == "node2pubkey" && it.address == "node2.example.com:9735" }
626-
}
626+
},
627+
anyOrNull(),
627628
)
628629
}
629630

630631
@Test
631632
fun `start should pass null trusted peers when blocktank returns null`() = test {
632633
sut.setInitNodeLifecycleState()
633634
whenever(lightningService.node).thenReturn(null)
634-
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
635+
whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Unit)
635636
whenever(lightningService.start(anyOrNull(), any())).thenReturn(Unit)
636637
whenever(settingsStore.data).thenReturn(flowOf(SettingsData()))
637638

@@ -643,6 +644,6 @@ class LightningRepoTest : BaseUnitTest() {
643644
val result = sut.start()
644645

645646
assertTrue(result.isSuccess)
646-
verify(lightningService).setup(any(), anyOrNull(), anyOrNull(), isNull())
647+
verify(lightningService).setup(any(), anyOrNull(), anyOrNull(), isNull(), anyOrNull())
647648
}
648649
}

app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package to.bitkit.ui
22

33
import kotlinx.coroutines.ExperimentalCoroutinesApi
44
import kotlinx.coroutines.flow.MutableStateFlow
5+
import kotlinx.coroutines.runBlocking
56
import kotlinx.coroutines.test.advanceUntilIdle
67
import org.junit.Assert.assertEquals
78
import org.junit.Before
@@ -23,6 +24,7 @@ import to.bitkit.repositories.LightningState
2324
import to.bitkit.repositories.SyncSource
2425
import to.bitkit.repositories.WalletRepo
2526
import to.bitkit.repositories.WalletState
27+
import to.bitkit.services.MigrationService
2628
import to.bitkit.test.BaseUnitTest
2729
import to.bitkit.viewmodels.RestoreState
2830
import to.bitkit.viewmodels.WalletViewModel
@@ -36,16 +38,18 @@ class WalletViewModelTest : BaseUnitTest() {
3638
private val settingsStore = mock<SettingsStore>()
3739
private val backupRepo = mock<BackupRepo>()
3840
private val blocktankRepo = mock<BlocktankRepo>()
41+
private val migrationService = mock<MigrationService>()
3942

4043
private val lightningState = MutableStateFlow(LightningState())
4144
private val walletState = MutableStateFlow(WalletState())
4245
private val balanceState = MutableStateFlow(BalanceState())
4346
private val isRecoveryMode = MutableStateFlow(false)
4447

4548
@Before
46-
fun setUp() {
49+
fun setUp() = runBlocking {
4750
whenever(walletRepo.walletState).thenReturn(walletState)
4851
whenever(lightningRepo.lightningState).thenReturn(lightningState)
52+
whenever(migrationService.isMigrationChecked()).thenReturn(true)
4953

5054
sut = WalletViewModel(
5155
bgDispatcher = testDispatcher,
@@ -54,6 +58,7 @@ class WalletViewModelTest : BaseUnitTest() {
5458
settingsStore = settingsStore,
5559
backupRepo = backupRepo,
5660
blocktankRepo = blocktankRepo,
61+
migrationService = migrationService,
5762
)
5863
}
5964

@@ -164,7 +169,7 @@ class WalletViewModelTest : BaseUnitTest() {
164169

165170
@Test
166171
fun `backup restore should not be triggered when wallet exists while not restoring`() = test {
167-
assertEquals(RestoreState.Initial, sut.restoreState)
172+
assertEquals(RestoreState.Initial, sut.restoreState.value)
168173

169174
walletState.value = walletState.value.copy(walletExists = true)
170175

@@ -176,11 +181,11 @@ class WalletViewModelTest : BaseUnitTest() {
176181
whenever(backupRepo.performFullRestoreFromLatestBackup()).thenReturn(Result.success(Unit))
177182
walletState.value = walletState.value.copy(walletExists = true)
178183
sut.restoreWallet("mnemonic", "passphrase")
179-
assertEquals(RestoreState.InProgress.Wallet, sut.restoreState)
184+
assertEquals(RestoreState.InProgress.Wallet, sut.restoreState.value)
180185

181186
sut.onRestoreContinue()
182187

183-
assertEquals(RestoreState.Settled, sut.restoreState)
188+
assertEquals(RestoreState.Settled, sut.restoreState.value)
184189
}
185190

186191
@Test
@@ -189,26 +194,26 @@ class WalletViewModelTest : BaseUnitTest() {
189194
whenever(backupRepo.performFullRestoreFromLatestBackup()).thenReturn(Result.failure(testError))
190195
sut.restoreWallet("mnemonic", "passphrase")
191196
walletState.value = walletState.value.copy(walletExists = true)
192-
assertEquals(RestoreState.Completed, sut.restoreState)
197+
assertEquals(RestoreState.Completed, sut.restoreState.value)
193198

194199
sut.proceedWithoutRestore(onDone = {})
195200
advanceUntilIdle()
196-
assertEquals(RestoreState.Settled, sut.restoreState)
201+
assertEquals(RestoreState.Settled, sut.restoreState.value)
197202
}
198203

199204
@Test
200205
fun `restore state should transition as expected`() = test {
201206
whenever(backupRepo.performFullRestoreFromLatestBackup()).thenReturn(Result.success(Unit))
202-
assertEquals(RestoreState.Initial, sut.restoreState)
207+
assertEquals(RestoreState.Initial, sut.restoreState.value)
203208

204209
sut.restoreWallet("mnemonic", "passphrase")
205-
assertEquals(RestoreState.InProgress.Wallet, sut.restoreState)
210+
assertEquals(RestoreState.InProgress.Wallet, sut.restoreState.value)
206211

207212
walletState.value = walletState.value.copy(walletExists = true)
208-
assertEquals(RestoreState.Completed, sut.restoreState)
213+
assertEquals(RestoreState.Completed, sut.restoreState.value)
209214

210215
sut.onRestoreContinue()
211-
assertEquals(RestoreState.Settled, sut.restoreState)
216+
assertEquals(RestoreState.Settled, sut.restoreState.value)
212217
}
213218

214219
@Test
@@ -226,7 +231,7 @@ class WalletViewModelTest : BaseUnitTest() {
226231
whenever(testWalletRepo.walletExists()).thenReturn(true)
227232
whenever(testLightningRepo.lightningState).thenReturn(lightningState)
228233
whenever(testLightningRepo.isRecoveryMode).thenReturn(isRecoveryMode)
229-
whenever(testLightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
234+
whenever(testLightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()))
230235
.thenReturn(Result.success(Unit))
231236

232237
val testSut = WalletViewModel(
@@ -236,15 +241,16 @@ class WalletViewModelTest : BaseUnitTest() {
236241
settingsStore = settingsStore,
237242
backupRepo = backupRepo,
238243
blocktankRepo = blocktankRepo,
244+
migrationService = migrationService,
239245
)
240246

241-
assertEquals(RestoreState.Initial, testSut.restoreState)
247+
assertEquals(RestoreState.Initial, testSut.restoreState.value)
242248
assertEquals(true, testSut.walletExists)
243249

244250
testSut.start()
245251
advanceUntilIdle()
246252

247-
verify(testLightningRepo).start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull())
253+
verify(testLightningRepo).start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
248254
verify(testWalletRepo).refreshBip21()
249255
}
250256

@@ -264,7 +270,7 @@ class WalletViewModelTest : BaseUnitTest() {
264270
whenever(testWalletRepo.restoreWallet(any(), anyOrNull())).thenReturn(Result.success(Unit))
265271
whenever(testLightningRepo.lightningState).thenReturn(lightningState)
266272
whenever(testLightningRepo.isRecoveryMode).thenReturn(isRecoveryMode)
267-
whenever(testLightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
273+
whenever(testLightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()))
268274
.thenReturn(Result.success(Unit))
269275

270276
val testSut = WalletViewModel(
@@ -274,11 +280,12 @@ class WalletViewModelTest : BaseUnitTest() {
274280
settingsStore = settingsStore,
275281
backupRepo = backupRepo,
276282
blocktankRepo = blocktankRepo,
283+
migrationService = migrationService,
277284
)
278285

279286
// Trigger restore to put state in non-idle
280287
testSut.restoreWallet("mnemonic", null)
281-
assertEquals(RestoreState.InProgress.Wallet, testSut.restoreState)
288+
assertEquals(RestoreState.InProgress.Wallet, testSut.restoreState.value)
282289

283290
testSut.start()
284291
advanceUntilIdle()

app/src/test/java/to/bitkit/usecases/WipeWalletUseCaseTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import to.bitkit.repositories.BackupRepo
1919
import to.bitkit.repositories.BlocktankRepo
2020
import to.bitkit.repositories.LightningRepo
2121
import to.bitkit.services.CoreService
22+
import to.bitkit.services.MigrationService
2223
import to.bitkit.test.BaseUnitTest
2324
import kotlin.test.assertTrue
2425

@@ -35,6 +36,7 @@ class WipeWalletUseCaseTest : BaseUnitTest() {
3536
private val activityRepo = mock<ActivityRepo>()
3637
private val lightningRepo = mock<LightningRepo>()
3738
private val firebaseMessaging = mock<FirebaseMessaging>()
39+
private val migrationService = mock<MigrationService>()
3840

3941
private lateinit var sut: WipeWalletUseCase
4042

@@ -59,6 +61,7 @@ class WipeWalletUseCaseTest : BaseUnitTest() {
5961
activityRepo = activityRepo,
6062
lightningRepo = lightningRepo,
6163
firebaseMessaging = firebaseMessaging,
64+
migrationService = migrationService,
6265
)
6366
}
6467

0 commit comments

Comments
 (0)