Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1037,8 +1037,16 @@ class ActivityService(
return currentWalletAddress
}

val accounts = runSuspendCatching { lightningService.listOnchainWalletAccounts() }
.onFailure { Logger.warn("Failed to list onchain wallet accounts", it, context = TAG) }
.getOrNull()
val loadedPrimaryTypes = accounts?.filter { it.accountIndex == 0u }
?.map { it.addressType.toBitkitAddressType() }?.toSet()
val primaryTypes = prioritizedAddressTypes(selectedAddressType)
.filter { loadedPrimaryTypes == null || it in loadedPrimaryTypes }

for (isChange in listOf(false, true)) {
for (addressType in prioritizedAddressTypes(selectedAddressType)) {
for (addressType in primaryTypes) {
searchReceivingAddressForType(
details = details,
value = value,
Expand All @@ -1049,12 +1057,9 @@ class ActivityService(
}
}

val accounts = runSuspendCatching { lightningService.listOnchainWalletAccounts() }
.onFailure { Logger.warn("Failed to list onchain wallet accounts", it, context = TAG) }
.getOrDefault(emptyList())
.filter { it.accountIndex != 0u }
val companionAccounts = accounts.orEmpty().filter { it.accountIndex != 0u }
for (isChange in listOf(false, true)) {
for (account in accounts) {
for (account in companionAccounts) {
searchReceivingAddressForType(
details = details,
value = value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.synonym.bitkitcore.getActivityByTxId
import com.synonym.bitkitcore.getTransactionDetails
import com.synonym.bitkitcore.updateActivity
import com.synonym.bitkitcore.upsertActivity
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
Expand All @@ -27,8 +28,11 @@ import org.lightningdevkit.ldknode.PaymentStatus
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import to.bitkit.async.ServiceQueue
Expand Down Expand Up @@ -399,7 +403,10 @@ class ActivityServicePaykitContactsTest : BaseUnitTest() {
addressSearchLastUsedReceiveIndexes = mapOf("nativeSegwit" to 600),
)
whenever(lightningService.listOnchainWalletAccounts()).thenReturn(
listOf(OnchainWalletAccount(LdkAddressType.NATIVE_SEGWIT, 5u)),
listOf(
OnchainWalletAccount(LdkAddressType.NATIVE_SEGWIT, 5u),
OnchainWalletAccount(LdkAddressType.NATIVE_SEGWIT, 0u),
),
)
whenever(lightningService.addressInfosForType(AddressType.P2WPKH, false, 200, 200, 5u))
.thenReturn(listOf(AddressDerivationInfo("server-address", 203)))
Expand All @@ -413,6 +420,100 @@ class ActivityServicePaykitContactsTest : BaseUnitTest() {
mapOf("nativeSegwit" to 600, "nativeSegwit:account:5" to 203),
cacheData.value.addressSearchLastUsedReceiveIndexes,
)
for (type in listOf(AddressType.P2PKH, AddressType.P2SH, AddressType.P2TR)) {
verify(lightningService, never()).addressInfosForType(eq(type), any(), any(), any(), any())
}
inOrder(lightningService) {
verify(lightningService).listOnchainWalletAccounts()
verify(lightningService).addressInfosForType(AddressType.P2WPKH, false, 1600, 1, 0u)
verify(lightningService).addressInfosForType(AddressType.P2WPKH, true, 800, 200, 0u)
verify(lightningService).addressInfosForType(AddressType.P2WPKH, false, 200, 200, 5u)
}
}

@Test
fun `loaded primaries retain selected type and change priority over companion receive`() = coreTest {
whenever(lightningService.listOnchainWalletAccounts()).thenReturn(
listOf(
OnchainWalletAccount(LdkAddressType.LEGACY, 0u),
OnchainWalletAccount(LdkAddressType.NATIVE_SEGWIT, 5u),
OnchainWalletAccount(LdkAddressType.NATIVE_SEGWIT, 0u),
),
)
whenever(lightningService.addressInfosForType(AddressType.P2WPKH, true, 0, 200, 0u))
.thenReturn(listOf(AddressDerivationInfo("primary-change", 3)))
whenever(lightningService.addressInfosForType(AddressType.P2PKH, true, 0, 200, 0u))
.thenReturn(listOf(AddressDerivationInfo("legacy-change", 4)))
whenever(lightningService.addressInfosForType(AddressType.P2WPKH, false, 0, 200, 5u))
.thenReturn(listOf(AddressDerivationInfo("companion-receive", 5)))

receive("companion-receive", "legacy-change", "primary-change")

assertEquals("primary-change", (updates.single() as Activity.Onchain).v1.address)
assertEquals(mapOf("nativeSegwit" to 3), cacheData.value.addressSearchLastUsedChangeIndexes)
inOrder(lightningService) {
verify(lightningService).addressInfosForType(AddressType.P2WPKH, false, 0, 200, 0u)
verify(lightningService).addressInfosForType(AddressType.P2PKH, false, 0, 200, 0u)
verify(lightningService).addressInfosForType(AddressType.P2WPKH, true, 0, 200, 0u)
}
verify(lightningService, times(11)).addressInfosForType(any(), any(), any(), any(), any())
}

@Test
fun `account listing failure preserves primary address discovery`() = coreTest {
whenever(lightningService.listOnchainWalletAccounts()).thenAnswer { throw AppError("Unavailable") }
whenever(lightningService.addressInfosForType(AddressType.P2WPKH, false, 0, 200, 0u))
.thenReturn(listOf(AddressDerivationInfo("primary-receive", 8)))

receive("primary-receive")

assertEquals("primary-receive", (updates.single() as Activity.Onchain).v1.address)
assertEquals(mapOf("nativeSegwit" to 8), cacheData.value.addressSearchLastUsedReceiveIndexes)
}

@Test
fun `account listing cancellation does not fall back to derivation or write activity`() = coreTest {
whenever(lightningService.listOnchainWalletAccounts()).thenThrow(CancellationException("Cancelled"))
whenever(lightningService.listPayments()).thenReturn(listOf(payment()))
val outputs = listOf(output("primary-receive"))
details = mock { on { this.outputs }.thenReturn(outputs) }

assertFailsWith<CancellationException> { sut.handlePaymentEvent("received") }

verify(lightningService, never()).addressInfosForType(any(), any(), any(), any(), any())
assertTrue(updates.isEmpty())
assertTrue(cacheData.value.addressSearchLastUsedReceiveIndexes.isEmpty())
assertTrue(cacheData.value.addressSearchLastUsedChangeIndexes.isEmpty())
}

@Test
fun `empty loaded accounts skip derivation and the next search reads newly loaded accounts`() = coreTest {
whenever(lightningService.listOnchainWalletAccounts()).thenReturn(
emptyList(),
listOf(OnchainWalletAccount(LdkAddressType.NATIVE_SEGWIT, 0u)),
)
whenever(lightningService.addressInfosForType(AddressType.P2WPKH, false, 0, 200, 0u))
.thenReturn(listOf(AddressDerivationInfo("primary-receive", 8)))

receive("primary-receive")

assertEquals("Loading...", (updates.single() as Activity.Onchain).v1.address)
verify(lightningService, never()).addressInfosForType(any(), any(), any(), any(), any())

receive("primary-receive")

assertEquals("primary-receive", (updates.last() as Activity.Onchain).v1.address)
verify(lightningService, times(2)).listOnchainWalletAccounts()
verify(lightningService).addressInfosForType(AddressType.P2WPKH, false, 0, 200, 0u)
}

@Test
fun `current receiving address resolves without listing accounts`() = coreTest {
receive("wallet-address")

assertEquals("wallet-address", (updates.single() as Activity.Onchain).v1.address)
verify(lightningService, never()).listOnchainWalletAccounts()
verify(lightningService, never()).addressInfosForType(any(), any(), any(), any(), any())
}

@Test
Expand Down
Loading