From 894e5dac2253b51f173e62a8d9577c551cbe135a Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Fri, 27 Feb 2026 13:41:26 +0300 Subject: [PATCH] fix: prevent staking dashboard hang when exposure flag not cached isPagedExposuresUsed() called storageCache.getEntry() which suspends forever if the entry doesn't exist. The flag is only written by ValidatorExposureUpdater (staking detail flow), so the dashboard would hang indefinitely waiting for it. Check cache first with isFullKeyInCache() and default to paged exposures when the flag is absent. Also remove debug log statements. --- .../nova/common/data/memory/RealComputationalCache.kt | 11 ----------- .../data/repository/PezkuwiDashboardRepository.kt | 1 - .../network/updaters/StakingDashboardUpdateSystem.kt | 1 - .../data/repository/StakingRepositoryImpl.kt | 10 ++++++++++ .../dashboard/RealStakingDashboardInteractor.kt | 8 -------- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt b/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt index cbcf632..8349425 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt @@ -1,7 +1,5 @@ package io.novafoundation.nova.common.data.memory -import android.util.Log -import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.invokeOnCompletion @@ -75,17 +73,12 @@ internal class RealComputationalCache : ComputationalCache, CoroutineScope by Co val awaitable = mutex.withLock { val existing = memory[key] if (existing != null && existing.aggregateScope.isActive) { - Log.d(LOG_TAG, "Key $key requested - already present") - existing.dependents += scope existing.awaitable } else { if (existing != null) { - Log.d(LOG_TAG, "Key $key requested - stale (aggregateScope cancelled), recreating") memory.remove(key) - } else { - Log.d(LOG_TAG, "Key $key requested - creating new operation") } val aggregateScope = CoroutineScope(Dispatchers.Default) @@ -104,13 +97,9 @@ internal class RealComputationalCache : ComputationalCache, CoroutineScope by Co entry.dependents -= scope if (entry.dependents.isEmpty()) { - Log.d(this@RealComputationalCache.LOG_TAG, "Key $key - last scope cancelled") - memory.remove(key) entry.aggregateScope.cancel() - } else { - Log.d(this@RealComputationalCache.LOG_TAG, "Key $key - scope cancelled, ${entry.dependents.size} remaining") } } } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt index 0cfcda2..4c7f492 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt @@ -156,7 +156,6 @@ class PezkuwiDashboardRepository( } kycModule.storage("KycStatuses").query(accountId, binding = { decoded -> val enumName = decoded?.castToDictEnum()?.name - Log.d("PezkuwiDashboard", "KYC status raw enum: '$enumName' (decoded=$decoded)") when (enumName) { "PendingReferral" -> CitizenshipStatus.PENDING_REFERRAL "ReferrerApproved" -> CitizenshipStatus.REFERRER_APPROVED diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/dashboard/network/updaters/StakingDashboardUpdateSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/dashboard/network/updaters/StakingDashboardUpdateSystem.kt index e4d663c..912daed 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/dashboard/network/updaters/StakingDashboardUpdateSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/dashboard/network/updaters/StakingDashboardUpdateSystem.kt @@ -130,7 +130,6 @@ class RealStakingDashboardUpdateSystem( val stats = runCatching { stakingStatsDataSource.fetchStakingStats(stakingAccounts, stakingChains) }.getOrElse { - Log.d("StakingDashboardUpdateSystem", "Failed to fetch staking stats after retries", it) emptyMap() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt index 1a0dc29..ab6c01c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt @@ -405,6 +405,16 @@ class StakingRepositoryImpl( } private suspend fun isPagedExposuresUsed(chainId: ChainId): Boolean { + // Check if the flag is already in cache before calling getEntry, which suspends + // forever if the entry doesn't exist. The flag is written by ValidatorExposureUpdater + // which only runs in the staking detail flow, not the dashboard. + val isCached = storageCache.isFullKeyInCache(ValidatorExposureUpdater.STORAGE_KEY_PAGED_EXPOSURES, chainId) + if (!isCached) { + // Default to paged exposures (modern chains). If paged returns empty, + // getElectedValidatorsExposure will return an empty map gracefully. + return true + } + val isPagedExposuresValue = storageCache.getEntry(ValidatorExposureUpdater.STORAGE_KEY_PAGED_EXPOSURES, chainId) return ValidatorExposureUpdater.decodeIsPagedExposuresValue(isPagedExposuresValue.content) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/dashboard/RealStakingDashboardInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/dashboard/RealStakingDashboardInteractor.kt index 7404bfb..eb9fafd 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/dashboard/RealStakingDashboardInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/dashboard/RealStakingDashboardInteractor.kt @@ -225,14 +225,6 @@ class RealStakingDashboardInteractor( noPriceStakingDashboard: NoPriceStakingDashboard, assets: Map, ): ExtendedLoadingState { - Log.d( - "StakingDashboard", - "addPricesToDashboard: hasStake=${noPriceStakingDashboard.hasStake.size}, " + - "noStake=${noPriceStakingDashboard.noStake.size}, " + - "notYetResolved=${noPriceStakingDashboard.notYetResolved.size}, " + - "assets=${assets.size}" - ) - val hasStakeOptions = noPriceStakingDashboard.hasStake.mapNotNull { addPriceToHasStakeItem(it, assets) } val noStakeOptions = noPriceStakingDashboard.noStake.mapNotNull { addAssetInfoToNoStakeItem(it, assets) } val notYetResolvedOptions = noPriceStakingDashboard.notYetResolved.mapNotNull { addAssetInfoToNotYetResolvedItem(it, assets) }