mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 13:45:48 +00:00
fix: don't let a transient fetch failure wipe local chains/assets
ChainSyncService.syncUp() and EvmAssetsSyncService.syncEVMAssets() both diff a freshly-fetched remote list against everything currently stored locally, then apply that diff unconditionally. If the remote fetch succeeds but returns a suspiciously small or empty list (CDN hiccup, regional network filtering, a bad publish upstream) - rather than throwing, which retryUntilDone would catch and retry - the diff would delete most or all of the user's chains/assets from the local DB. This is active data loss, not a sync failure, and it self-heals on the next good sync if we just skip applying a suspicious one instead. Directly relevant: a user's live production install (unrelated to any in-flight branch work) was observed with a completely empty networks and token list after previously working fine, matching this exact failure mode.
This commit is contained in:
+14
@@ -1,7 +1,9 @@
|
||||
package io.novafoundation.nova.runtime.multiNetwork.asset
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.utils.CollectionDiffer
|
||||
import io.novafoundation.nova.common.utils.LOG_TAG
|
||||
import io.novafoundation.nova.common.utils.retryUntilDone
|
||||
import io.novafoundation.nova.core_db.dao.ChainAssetDao
|
||||
import io.novafoundation.nova.core_db.dao.ChainDao
|
||||
@@ -40,6 +42,18 @@ class EvmAssetsSyncService(
|
||||
new.copy(enabled = old?.enabled ?: ENABLED_DEFAULT_BOOL)
|
||||
}
|
||||
|
||||
// Same defensive guard as ChainSyncService: a transient upstream issue can make the fetch return
|
||||
// successfully with a suspiciously small/empty list. Diffing that against a populated local DB would
|
||||
// delete most or all of the user's ERC20 tokens (e.g. USDT-ERC20) - skip instead of wiping good data.
|
||||
if (oldAssets.isNotEmpty() && newAssets.size < oldAssets.size / 2) {
|
||||
Log.e(
|
||||
LOG_TAG,
|
||||
"Refusing to apply EVM asset sync: remote returned ${newAssets.size} assets vs ${oldAssets.size} currently stored " +
|
||||
"(would remove more than half). Likely a transient fetch issue - skipping this sync cycle."
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val diff = CollectionDiffer.findDiff(newAssets, oldAssets, forceUseNewItems = false)
|
||||
chainAssetDao.updateAssets(diff)
|
||||
}
|
||||
|
||||
+17
@@ -1,7 +1,9 @@
|
||||
package io.novafoundation.nova.runtime.multiNetwork.chain
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.utils.CollectionDiffer
|
||||
import io.novafoundation.nova.common.utils.LOG_TAG
|
||||
import io.novafoundation.nova.common.utils.retryUntilDone
|
||||
import io.novafoundation.nova.core_db.dao.ChainDao
|
||||
import io.novafoundation.nova.core_db.dao.FullAssetIdLocal
|
||||
@@ -40,6 +42,21 @@ class ChainSyncService(
|
||||
|
||||
val remoteChains = retryUntilDone { chainFetcher.getChains() }
|
||||
|
||||
// A transient upstream issue (CDN hiccup, regional network filtering, a bad publish) can make
|
||||
// chainFetcher.getChains() return successfully with a suspiciously small/empty list instead of
|
||||
// throwing. Applying that as a diff against a populated local DB would delete most or all of the
|
||||
// user's chains/assets - not a sync failure, but active data loss, for something that self-heals on
|
||||
// the next successful sync if we just skip applying it. Only guard when we HAD data: an empty result
|
||||
// on a genuinely first-ever sync is normal and must proceed.
|
||||
if (oldChains.isNotEmpty() && remoteChains.size < oldChains.size / 2) {
|
||||
Log.e(
|
||||
LOG_TAG,
|
||||
"Refusing to apply chain sync: remote returned ${remoteChains.size} chains vs ${oldChains.size} currently stored " +
|
||||
"(would remove more than half). Likely a transient fetch issue - skipping this sync cycle."
|
||||
)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
val newChains = remoteChains.map { mapRemoteChainToLocal(it, oldChainsById[it.chainId], source = ChainLocal.Source.DEFAULT, gson) }
|
||||
val newAssets = remoteChains.flatMap { chain ->
|
||||
chain.assets.map {
|
||||
|
||||
Reference in New Issue
Block a user