Skip to content

Commit 4bd661f

Browse files
authored
Merge pull request #318 from synonymdev/fix/get-detail-by-tx-id
Get transaction detail by tx id
2 parents 76a8477 + 4e640d7 commit 4bd661f

File tree

9 files changed

+80
-47
lines changed

9 files changed

+80
-47
lines changed

app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class WakeNodeWorker @AssistedInject constructor(
118118
NewTransactionSheetDetails(
119119
type = NewTransactionSheetType.LIGHTNING,
120120
direction = NewTransactionSheetDirection.RECEIVED,
121+
paymentHashOrTxId = event.paymentHash,
121122
sats = sats.toLong(),
122123
)
123124
)

app/src/main/java/to/bitkit/models/NewTransactionSheetDetails.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ private const val APP_PREFS = "bitkit_prefs"
1515
data class NewTransactionSheetDetails(
1616
val type: NewTransactionSheetType,
1717
val direction: NewTransactionSheetDirection,
18+
val paymentHashOrTxId: String? = null,
1819
val sats: Long,
20+
val isLoadingDetails: Boolean = false
1921
) {
2022
companion object {
2123
private const val BACKGROUND_TRANSACTION_KEY = "backgroundTransaction"

app/src/main/java/to/bitkit/repositories/ActivityRepo.kt

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import com.synonym.bitkitcore.ActivityFilter
66
import com.synonym.bitkitcore.PaymentType
77
import com.synonym.bitkitcore.SortDirection
88
import kotlinx.coroutines.CoroutineDispatcher
9-
import kotlinx.coroutines.delay
9+
import kotlinx.coroutines.TimeoutCancellationException
10+
import kotlinx.coroutines.flow.MutableStateFlow
1011
import kotlinx.coroutines.flow.first
1112
import kotlinx.coroutines.flow.map
1213
import kotlinx.coroutines.withContext
14+
import kotlinx.coroutines.withTimeout
1315
import org.lightningdevkit.ldknode.PaymentDetails
1416
import to.bitkit.data.CacheStore
1517
import to.bitkit.data.dto.InProgressTransfer
@@ -22,7 +24,8 @@ import to.bitkit.services.CoreService
2224
import to.bitkit.utils.Logger
2325
import javax.inject.Inject
2426
import javax.inject.Singleton
25-
import kotlin.time.Duration.Companion.seconds
27+
28+
private const val SYNC_TIMEOUT_MS = 40_000L
2629

2730
@Singleton
2831
class ActivityRepo @Inject constructor(
@@ -31,39 +34,48 @@ class ActivityRepo @Inject constructor(
3134
private val lightningRepo: LightningRepo,
3235
private val cacheStore: CacheStore,
3336
) {
34-
35-
var isSyncingLdkNodePayments = false
37+
val isSyncingLdkNodePayments = MutableStateFlow(false)
3638

3739
val inProgressTransfers = cacheStore.data.map { it.inProgressTransfers }
3840

3941
suspend fun syncActivities(): Result<Unit> = withContext(bgDispatcher) {
4042
Logger.debug("syncActivities called", context = TAG)
4143

4244
return@withContext runCatching {
43-
if (isSyncingLdkNodePayments) {
44-
Logger.warn("LDK-node payments are already being synced, skipping", context = TAG)
45-
return@withContext Result.failure(Exception())
45+
withTimeout(SYNC_TIMEOUT_MS) {
46+
Logger.debug("isSyncingLdkNodePayments = ${isSyncingLdkNodePayments.value}", context = TAG)
47+
isSyncingLdkNodePayments.first { !it }
4648
}
4749

48-
deletePendingActivities()
50+
isSyncingLdkNodePayments.value = true
4951

50-
isSyncingLdkNodePayments = true
52+
deletePendingActivities()
5153
return@withContext lightningRepo.getPayments()
5254
.onSuccess { payments ->
5355
Logger.debug("Got payments with success, syncing activities", context = TAG)
5456
syncLdkNodePayments(payments = payments)
5557
updateActivitiesMetadata()
5658
boostPendingActivities()
5759
updateInProgressTransfers()
58-
isSyncingLdkNodePayments = false
60+
isSyncingLdkNodePayments.value = false
5961
return@withContext Result.success(Unit)
6062
}.onFailure { e ->
6163
Logger.error("Failed to sync ldk-node payments", e, context = TAG)
62-
isSyncingLdkNodePayments = false
64+
isSyncingLdkNodePayments.value = false
6365
return@withContext Result.failure(e)
6466
}.map { Unit }
6567
}.onFailure { e ->
66-
Logger.error("syncLdkNodePayments error", e, context = TAG)
68+
when (e) {
69+
is TimeoutCancellationException -> {
70+
isSyncingLdkNodePayments.value = false
71+
Logger.error("Timeout waiting for sync to complete, forcing reset", e, context = TAG)
72+
}
73+
74+
else -> {
75+
isSyncingLdkNodePayments.value = false
76+
Logger.error("syncActivities error", e, context = TAG)
77+
}
78+
}
6779
}
6880
}
6981

@@ -127,9 +139,6 @@ class ActivityRepo @Inject constructor(
127139
"activity with paymentHashOrTxId:$paymentHashOrTxId not found, trying again after sync",
128140
context = TAG
129141
)
130-
Logger.debug("5 seconds delay", context = TAG)
131-
delay(5.seconds)
132-
Logger.debug("Syncing LN node called", context = TAG)
133142

134143
lightningRepo.sync().onSuccess {
135144
Logger.debug("Syncing LN node SUCCESS", context = TAG)

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.first
1717
import kotlinx.coroutines.flow.update
1818
import kotlinx.coroutines.tasks.await
1919
import kotlinx.coroutines.withContext
20+
import kotlinx.coroutines.withTimeout
2021
import kotlinx.coroutines.withTimeoutOrNull
2122
import org.lightningdevkit.ldknode.Address
2223
import org.lightningdevkit.ldknode.BalanceDetails
@@ -57,6 +58,8 @@ import kotlin.time.Duration
5758
import kotlin.time.Duration.Companion.minutes
5859
import kotlin.time.Duration.Companion.seconds
5960

61+
private const val SYNC_TIMEOUT_MS = 10_000L
62+
6063
@Singleton
6164
class LightningRepo @Inject constructor(
6265
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
@@ -258,8 +261,11 @@ class LightningRepo @Inject constructor(
258261
suspend fun sync(): Result<Unit> = executeWhenNodeRunning("Sync") {
259262
syncState()
260263
if (_lightningState.value.isSyncingWallet) {
261-
Logger.warn("Sync already in progress, waiting for existing sync.")
262-
return@executeWhenNodeRunning Result.success(Unit)
264+
Logger.warn("Sync already in progress, waiting for existing sync.", context = TAG)
265+
}
266+
267+
withTimeout(SYNC_TIMEOUT_MS) {
268+
_lightningState.first { !it.isSyncingWallet }
263269
}
264270

265271
_lightningState.update { it.copy(isSyncingWallet = true) }

app/src/main/java/to/bitkit/repositories/WalletRepo.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.synonym.bitkitcore.decode
66
import kotlinx.coroutines.CoroutineDispatcher
77
import kotlinx.coroutines.CoroutineScope
88
import kotlinx.coroutines.SupervisorJob
9+
import kotlinx.coroutines.TimeoutCancellationException
910
import kotlinx.coroutines.flow.MutableStateFlow
1011
import kotlinx.coroutines.flow.asStateFlow
1112
import kotlinx.coroutines.flow.filter
@@ -152,6 +153,9 @@ class WalletRepo @Inject constructor(
152153
syncBalances()
153154
return@withContext Result.success(Unit)
154155
}.onFailure { e ->
156+
if (e is TimeoutCancellationException) {
157+
syncBalances()
158+
}
155159
return@withContext Result.failure(e)
156160
}
157161
}

app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import to.bitkit.ui.theme.AppThemeSurface
7171
import to.bitkit.ui.theme.Colors
7272
import to.bitkit.ui.utils.copyToClipboard
7373
import to.bitkit.ui.utils.getScreenTitleRes
74+
import to.bitkit.utils.Logger
7475
import to.bitkit.viewmodels.ActivityDetailViewModel
7576
import to.bitkit.viewmodels.ActivityListViewModel
7677

@@ -85,7 +86,11 @@ fun ActivityDetailScreen(
8586
) {
8687
val activities by listViewModel.filteredActivities.collectAsStateWithLifecycle()
8788
val item = activities?.find { it.rawId() == route.id }
88-
?: return
89+
if (item == null) {
90+
Logger.error("Activity not found")
91+
return
92+
}
93+
8994

9095
val app = appViewModel ?: return
9196
val copyToastTitle = stringResource(R.string.common__copied)

app/src/main/java/to/bitkit/ui/sheets/NewTransactionSheet.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ fun NewTransactionSheet(
5757
settingsViewModel: SettingsViewModel,
5858
) {
5959
val currencies by currencyViewModel.uiState.collectAsState()
60+
val newTransaction by appViewModel.newTransaction.collectAsState()
6061

6162
CompositionLocalProvider(
6263
LocalCurrencyViewModel provides currencyViewModel,
@@ -67,10 +68,9 @@ fun NewTransactionSheet(
6768
onDismissRequest = { appViewModel.hideNewTransactionSheet() },
6869
) {
6970
NewTransactionSheetView(
70-
details = appViewModel.newTransaction,
71+
details = newTransaction,
7172
onCloseClick = { appViewModel.hideNewTransactionSheet() },
7273
onDetailClick = {
73-
appViewModel.hideNewTransactionSheet()
7474
appViewModel.onClickActivityDetail()
7575
},
7676
)
@@ -179,6 +179,8 @@ fun NewTransactionSheetView(
179179
SecondaryButton(
180180
text = stringResource(R.string.wallet__send_details),
181181
onClick = onDetailClick,
182+
enabled = details.paymentHashOrTxId != null,
183+
isLoading = details.isLoadingDetails,
182184
modifier = Modifier
183185
.weight(1f)
184186
.testTag("Details")

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

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class AppViewModel @Inject constructor(
198198
NewTransactionSheetDetails(
199199
type = NewTransactionSheetType.LIGHTNING,
200200
direction = NewTransactionSheetDirection.RECEIVED,
201+
paymentHashOrTxId = event.paymentHash,
201202
sats = (event.amountMsat / 1000u).toLong(),
202203
),
203204
event = event
@@ -241,6 +242,7 @@ class AppViewModel @Inject constructor(
241242
NewTransactionSheetDetails(
242243
type = NewTransactionSheetType.LIGHTNING,
243244
direction = NewTransactionSheetDirection.SENT,
245+
paymentHashOrTxId = event.paymentHash,
244246
sats = ((event.feePaidMsat ?: 0u) / 1000u).toLong(),
245247
),
246248
event = event
@@ -950,10 +952,12 @@ class AppViewModel @Inject constructor(
950952
NewTransactionSheetDetails(
951953
type = NewTransactionSheetType.ONCHAIN,
952954
direction = NewTransactionSheetDirection.SENT,
955+
paymentHashOrTxId = txId,
953956
sats = amount.toLong(),
954957
)
955958
)
956959
)
960+
lightningRepo.sync()
957961
}.onFailure { e ->
958962
Logger.error(msg = "Error sending onchain payment", e = e, context = TAG)
959963
toast(
@@ -1041,19 +1045,26 @@ class AppViewModel @Inject constructor(
10411045
}
10421046

10431047
fun onClickActivityDetail() {
1044-
val filter = newTransaction.type.toActivityFilter()
1045-
val paymentType = newTransaction.direction.toTxType()
1046-
1048+
val activityType = _newTransaction.value.type.toActivityFilter()
1049+
val txType = _newTransaction.value.direction.toTxType()
1050+
val paymentHashOrTxId = _newTransaction.value.paymentHashOrTxId ?: return
1051+
_newTransaction.update { it.copy(isLoadingDetails = true) }
10471052
viewModelScope.launch(bgDispatcher) {
1048-
val activity = coreService.activity.get(filter = filter, txType = paymentType, limit = 1u).firstOrNull()
1049-
1050-
if (activity == null) {
1053+
activityRepo.findActivityByPaymentId(
1054+
paymentHashOrTxId = paymentHashOrTxId,
1055+
type = activityType,
1056+
txType = txType,
1057+
retry = true
1058+
).onSuccess { activity ->
1059+
hideNewTransactionSheet()
1060+
_newTransaction.update { it.copy(isLoadingDetails = false) }
1061+
val nextRoute = Routes.ActivityDetail(activity.rawId())
1062+
mainScreenEffect(MainScreenEffect.Navigate(nextRoute))
1063+
}.onFailure { e ->
10511064
Logger.error(msg = "Activity not found", context = TAG)
1052-
return@launch
1065+
toast(e)
1066+
_newTransaction.update { it.copy(isLoadingDetails = false) }
10531067
}
1054-
1055-
val nextRoute = Routes.ActivityDetail(activity.rawId())
1056-
mainScreenEffect(MainScreenEffect.Navigate(nextRoute))
10571068
}
10581069
}
10591070

@@ -1212,14 +1223,17 @@ class AppViewModel @Inject constructor(
12121223
var showNewTransaction by mutableStateOf(false)
12131224
private set
12141225

1215-
var newTransaction by mutableStateOf(
1226+
private val _newTransaction = MutableStateFlow(
12161227
NewTransactionSheetDetails(
1217-
NewTransactionSheetType.LIGHTNING,
1218-
NewTransactionSheetDirection.RECEIVED,
1219-
0
1228+
type = NewTransactionSheetType.LIGHTNING,
1229+
direction = NewTransactionSheetDirection.RECEIVED,
1230+
paymentHashOrTxId = null,
1231+
sats = 0
12201232
)
12211233
)
12221234

1235+
val newTransaction = _newTransaction.asStateFlow()
1236+
12231237
fun setNewTransactionSheetEnabled(enabled: Boolean) {
12241238
isNewTransactionSheetEnabled = enabled
12251239
}
@@ -1248,7 +1262,7 @@ class AppViewModel @Inject constructor(
12481262
}
12491263
}
12501264

1251-
newTransaction = details
1265+
_newTransaction.update { details }
12521266
showNewTransaction = true
12531267
}
12541268

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,7 @@ class ActivityRepoTest : BaseUnitTest() {
8686
assertTrue(result.isSuccess)
8787
verify(lightningRepo).getPayments()
8888
verify(coreService.activity).syncLdkNodePayments(payments, forceUpdate = false)
89-
assertFalse(sut.isSyncingLdkNodePayments)
90-
}
91-
92-
@Test
93-
fun `syncActivities skips when already syncing`() = test {
94-
sut.isSyncingLdkNodePayments = true
95-
96-
val result = sut.syncActivities()
97-
98-
assertTrue(result.isFailure)
99-
verify(lightningRepo, never()).getPayments()
89+
assertFalse(sut.isSyncingLdkNodePayments.value)
10090
}
10191

10292
@Test
@@ -108,7 +98,7 @@ class ActivityRepoTest : BaseUnitTest() {
10898

10999
assertTrue(result.isFailure)
110100
assertEquals(exception, result.exceptionOrNull())
111-
assertFalse(sut.isSyncingLdkNodePayments)
101+
assertFalse(sut.isSyncingLdkNodePayments.value)
112102
}
113103

114104
@Test

0 commit comments

Comments
 (0)