mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 12:35:49 +00:00
fix: retry balance sync on failure for ALL chains, not just Tron
Generalizes today's Tron balance-polling fix - the same silent-death
bug exists in the shared Substrate sync path used by every chain
(Interlay, Kintsugi, Karura, Acala, Hydration, Polkadex, and any other
orml/statemine asset), not just Tron-specific code.
FullSyncPaymentUpdater.syncAsset() previously used runCatching around
the one-time startSyncingBalance() call and a separate .catch on the
resulting flow - either path failing (a transient WSS hiccup during
setup, an orml currencyId-decode edge case, a node not supporting a
specific RPC method during round-robin, a dropped connection later)
permanently ended that asset's sync for the Updater's lifetime: no
retry, only a Log.e line, and mapNotNull silently dropped the null
result. Since no balance update flow ever emits, AssetCache never
creates a DB row for that asset, and every UI surface (main list,
multi-chain picker) reads via an INNER JOIN that requires that row -
so the asset stays invisible with zero user-visible error until app
restart, which has the same odds of failing again.
Live-tested: Interlay/Kintsugi's node URLs are all reachable (manual
WSS handshake test, 101 Switching Protocols), and none of these chains
are blacklisted - so this wasn't a connectivity or config issue, it
was this retry gap.
Wraps both the initial subscription call and the ongoing flow in one
flow{} builder with retryWhen (30s fixed interval, matching Tron's
polling interval) instead of runCatching + .catch, so a transient
failure at either point just gets retried instead of permanently
killing sync for that asset.
This commit is contained in:
+30
-14
@@ -22,9 +22,14 @@ import io.novafoundation.nova.runtime.ext.enabledAssets
|
||||
import io.novafoundation.nova.runtime.ext.localId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
|
||||
private const val SYNC_RETRY_DELAY_MS = 30_000L
|
||||
|
||||
internal class FullSyncPaymentUpdater(
|
||||
private val operationDao: OperationDao,
|
||||
@@ -41,32 +46,43 @@ internal class FullSyncPaymentUpdater(
|
||||
): Flow<Updater.SideEffect> {
|
||||
val accountId = scopeValue.requireAccountIdIn(chain)
|
||||
|
||||
return chain.enabledAssets().mapNotNull { chainAsset ->
|
||||
return chain.enabledAssets().map { chainAsset ->
|
||||
syncAsset(chainAsset, scopeValue, accountId, storageSubscriptionBuilder)
|
||||
}
|
||||
.mergeIfMultiple()
|
||||
.noSideAffects()
|
||||
}
|
||||
|
||||
private suspend fun syncAsset(
|
||||
/**
|
||||
* Wraps both the initial `startSyncingBalance()` call and the resulting flow in a single retry
|
||||
* boundary. Without this, any transient failure - during initial subscription setup (a WSS
|
||||
* hiccup, an orml currencyId-decode edge case, a node not supporting a specific RPC method
|
||||
* during round-robin) or later in the flow (a dropped connection) - would permanently and
|
||||
* silently kill sync for that one asset: no DB row ever gets created/updated for it, so it
|
||||
* vanishes from every UI screen with nothing but a logcat line as evidence, until the app is
|
||||
* restarted (and even then, with the same odds of failing again). Retrying indefinitely on a
|
||||
* fixed interval matches the same fix already applied to Tron's balance polling.
|
||||
*/
|
||||
private fun syncAsset(
|
||||
chainAsset: Chain.Asset,
|
||||
metaAccount: MetaAccount,
|
||||
accountId: AccountId,
|
||||
storageSubscriptionBuilder: SharedRequestsBuilder
|
||||
): Flow<BalanceSyncUpdate>? {
|
||||
): Flow<BalanceSyncUpdate> {
|
||||
val assetSource = assetSourceRegistry.sourceFor(chainAsset)
|
||||
|
||||
val assetUpdateFlow = runCatching {
|
||||
assetSource.balance.startSyncingBalance(chain, chainAsset, metaAccount, accountId, storageSubscriptionBuilder)
|
||||
return flow {
|
||||
val assetUpdateFlow = assetSource.balance.startSyncingBalance(chain, chainAsset, metaAccount, accountId, storageSubscriptionBuilder)
|
||||
emitAll(assetUpdateFlow)
|
||||
}
|
||||
.onFailure { logSyncError(chain, chainAsset, error = it) }
|
||||
.getOrNull()
|
||||
?: return null
|
||||
|
||||
return assetUpdateFlow.onEach { balanceUpdate ->
|
||||
assetSource.history.syncOperationsForBalanceChange(chainAsset, balanceUpdate, accountId)
|
||||
}
|
||||
.catch { logSyncError(chain, chainAsset, error = it) }
|
||||
.onEach { balanceUpdate ->
|
||||
assetSource.history.syncOperationsForBalanceChange(chainAsset, balanceUpdate, accountId)
|
||||
}
|
||||
.retryWhen { cause, _ ->
|
||||
logSyncError(chain, chainAsset, error = cause)
|
||||
delay(SYNC_RETRY_DELAY_MS)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun logSyncError(chain: Chain, chainAsset: Chain.Asset, error: Throwable) {
|
||||
|
||||
Reference in New Issue
Block a user