mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-10-20 18:56:25 +00:00
fix: retry Tron balance polling on failure instead of permanently dying
Real user on a fresh install reported TRX and USDT-TRC20 never appearing anywhere in the Tokens list (not "zero balance", genuinely absent - including from the multi-chain USDT chain picker). Traced the full pipeline; every layer up to and including TypeBasedAssetSourceRegistry, ChainRegistry, address derivation, and TronGridApi wiring was already correct. Root cause: pollingBalanceFlow() had no retry around fetch() - any single transient failure (DNS hiccup, timeout, momentary connectivity loss during app cold start) threw out of the `while(true)` loop, killing the flow permanently. The collector (FullSyncPaymentUpdater.syncAsset()) only logs and gives up on failure, it doesn't resubscribe. Since the asset's first balance write never happens, AssetCache never inserts a DB row for it, and every UI surface (main list, multi-chain picker, search) reads exclusively via an INNER JOIN that requires that row to exist - so the asset is invisible everywhere, permanently, with zero user-visible error. Swallow fetch() failures and retry on the next interval instead of letting them escape the loop - one bad poll no longer blackholes the asset for the rest of the app session. Also fixes a related but separate gap found while tracing this: RealSecretsMetaAccount.multiChainEncryptionIn() had no isTronBased branch (only isEthereumBased), unlike DefaultMetaAccount's hasAccountIn/accountIdIn which already handle Tron correctly. This didn't cause the missing-tokens bug, but would have broken "export account" (JSON key backup) for Tron - fixed by routing Tron through MultiChainEncryption.Ethereum, matching what RealTronTransactionService already does for actual transaction signing (same secp256k1 keypair).
This commit is contained in:
1 parent
15aa33000b
commit
21d2896493
2 files changed
+18
-3
No files matched your search
+5
-1
@@ -49,7 +49,9 @@ class RealSecretsMetaAccount(
|
|||||||
hasChainAccountIn(chain.id) -> {
|
hasChainAccountIn(chain.id) -> {
|
||||||
val cryptoType = chainAccounts.getValue(chain.id).cryptoType ?: return null
|
val cryptoType = chainAccounts.getValue(chain.id).cryptoType ?: return null
|
||||||
|
|
||||||
if (chain.isEthereumBased) {
|
// Tron reuses the same secp256k1 keypair/signing as Ethereum - see
|
||||||
|
// RealTronTransactionService's use of Signer.sign(MultiChainEncryption.Ethereum, ...).
|
||||||
|
if (chain.isEthereumBased || chain.isTronBased) {
|
||||||
MultiChainEncryption.Ethereum
|
MultiChainEncryption.Ethereum
|
||||||
} else {
|
} else {
|
||||||
MultiChainEncryption.substrateFrom(cryptoType)
|
MultiChainEncryption.substrateFrom(cryptoType)
|
||||||
@@ -58,6 +60,8 @@ class RealSecretsMetaAccount(
|
|||||||
|
|
||||||
chain.isEthereumBased -> MultiChainEncryption.Ethereum
|
chain.isEthereumBased -> MultiChainEncryption.Ethereum
|
||||||
|
|
||||||
|
chain.isTronBased -> MultiChainEncryption.Ethereum
|
||||||
|
|
||||||
else -> substrateCryptoType?.let(MultiChainEncryption.Companion::substrateFrom)
|
else -> substrateCryptoType?.let(MultiChainEncryption.Companion::substrateFrom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -1,17 +1,26 @@
|
|||||||
package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.tronNative
|
package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.tronNative
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
private const val TRON_BALANCE_POLLING_INTERVAL_MS = 30_000L
|
private const val TRON_BALANCE_POLLING_INTERVAL_MS = 30_000L
|
||||||
|
private const val LOG_TAG = "TronBalancePolling"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TronGrid is a plain REST API with no push/subscription mechanism (unlike Ethereum nodes, which expose a
|
* TronGrid is a plain REST API with no push/subscription mechanism (unlike Ethereum nodes, which expose a
|
||||||
* `newHeads`-style websocket subscription EVM balance sync piggybacks on). So balance updates for Tron-based
|
* `newHeads`-style websocket subscription EVM balance sync piggybacks on). So balance updates for Tron-based
|
||||||
* assets are polled instead of pushed: fetch immediately, then re-fetch on an interval, only emitting when the
|
* assets are polled instead of pushed: fetch immediately, then re-fetch on an interval, only emitting when the
|
||||||
* balance actually changed.
|
* balance actually changed.
|
||||||
|
*
|
||||||
|
* A failed fetch() must not escape this loop: any uncaught exception here cancels the whole flow permanently
|
||||||
|
* (the collector - FullSyncPaymentUpdater - only logs and gives up, it doesn't resubscribe), which meant a
|
||||||
|
* single transient failure (DNS hiccup, timeout, momentary connectivity loss during app cold start) could
|
||||||
|
* silently and permanently blackhole a Tron asset - no balance write ever happens, so it never even gets a row
|
||||||
|
* in the local DB and disappears from every UI surface with no visible error. Swallow and retry next interval
|
||||||
|
* instead.
|
||||||
*/
|
*/
|
||||||
internal fun pollingBalanceFlow(
|
internal fun pollingBalanceFlow(
|
||||||
intervalMs: Long = TRON_BALANCE_POLLING_INTERVAL_MS,
|
intervalMs: Long = TRON_BALANCE_POLLING_INTERVAL_MS,
|
||||||
@@ -20,9 +29,11 @@ internal fun pollingBalanceFlow(
|
|||||||
var lastEmitted: Balance? = null
|
var lastEmitted: Balance? = null
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val latest = fetch()
|
val latest = runCatching { fetch() }
|
||||||
|
.onFailure { Log.e(LOG_TAG, "Tron balance fetch failed, will retry in ${intervalMs}ms", it) }
|
||||||
|
.getOrNull()
|
||||||
|
|
||||||
if (latest != lastEmitted) {
|
if (latest != null && latest != lastEmitted) {
|
||||||
lastEmitted = latest
|
lastEmitted = latest
|
||||||
emit(latest)
|
emit(latest)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user