mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-24 22:48:02 +00:00
Add graceful error handling for RPC connection failures
- StatemineAssetBalance: Handle connection errors in queryAccountBalance, existentialDeposit, subscribeAccountBalanceUpdatePoint, and startSyncingBalance - NativeAssetBalance: Handle connection errors in all balance query and sync methods - Return safe defaults (zero balance, empty flows) instead of crashing - Log errors for debugging without interrupting user experience
This commit is contained in:
+70
-40
@@ -1,6 +1,8 @@
|
||||
package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.statemine
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance
|
||||
import io.novafoundation.nova.common.utils.LOG_TAG
|
||||
import io.novafoundation.nova.common.utils.decodeValue
|
||||
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
|
||||
import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal
|
||||
@@ -27,6 +29,7 @@ import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.storage
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -51,28 +54,40 @@ class StatemineAssetBalance(
|
||||
}
|
||||
|
||||
override fun isSelfSufficient(chainAsset: Chain.Asset): Boolean {
|
||||
return chainAsset.requireStatemine().isSufficient
|
||||
return runCatching {
|
||||
chainAsset.requireStatemine().isSufficient
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
|
||||
override suspend fun existentialDeposit(chainAsset: Chain.Asset): BigInteger {
|
||||
return queryAssetDetails(chainAsset).minimumBalance
|
||||
return runCatching {
|
||||
queryAssetDetails(chainAsset).minimumBalance
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to query existential deposit for ${chainAsset.symbol}: ${error.message}")
|
||||
BigInteger.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): ChainAssetBalance {
|
||||
val statemineType = chainAsset.requireStatemine()
|
||||
return runCatching {
|
||||
val statemineType = chainAsset.requireStatemine()
|
||||
|
||||
val assetAccount = remoteStorage.query(chain.id) {
|
||||
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
||||
val assetAccount = remoteStorage.query(chain.id) {
|
||||
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
||||
|
||||
runtime.metadata.statemineModule(statemineType).storage("Account").query(
|
||||
encodableId,
|
||||
accountId,
|
||||
binding = ::bindAssetAccountOrEmpty
|
||||
)
|
||||
runtime.metadata.statemineModule(statemineType).storage("Account").query(
|
||||
encodableId,
|
||||
accountId,
|
||||
binding = ::bindAssetAccountOrEmpty
|
||||
)
|
||||
}
|
||||
|
||||
val accountBalance = assetAccount.toAccountBalance()
|
||||
ChainAssetBalance.default(chainAsset, accountBalance)
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to query balance for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
ChainAssetBalance.fromFree(chainAsset, BigInteger.ZERO)
|
||||
}
|
||||
|
||||
val accountBalance = assetAccount.toAccountBalance()
|
||||
return ChainAssetBalance.default(chainAsset, accountBalance)
|
||||
}
|
||||
|
||||
override suspend fun subscribeAccountBalanceUpdatePoint(
|
||||
@@ -80,18 +95,25 @@ class StatemineAssetBalance(
|
||||
chainAsset: Chain.Asset,
|
||||
accountId: AccountId,
|
||||
): Flow<TransferableBalanceUpdatePoint> {
|
||||
val statemineType = chainAsset.requireStatemine()
|
||||
return runCatching {
|
||||
val statemineType = chainAsset.requireStatemine()
|
||||
|
||||
return remoteStorage.subscribe(chain.id) {
|
||||
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
||||
remoteStorage.subscribe(chain.id) {
|
||||
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
||||
|
||||
runtime.metadata.statemineModule(statemineType).storage("Account").observeWithRaw(
|
||||
encodableId,
|
||||
accountId,
|
||||
binding = ::bindAssetAccountOrEmpty
|
||||
).map {
|
||||
TransferableBalanceUpdatePoint(it.at!!)
|
||||
runtime.metadata.statemineModule(statemineType).storage("Account").observeWithRaw(
|
||||
encodableId,
|
||||
accountId,
|
||||
binding = ::bindAssetAccountOrEmpty
|
||||
).map {
|
||||
TransferableBalanceUpdatePoint(it.at!!)
|
||||
}
|
||||
}.catch { error ->
|
||||
Log.e(LOG_TAG, "Balance subscription failed for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
}
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to setup balance subscription for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,32 +138,40 @@ class StatemineAssetBalance(
|
||||
accountId: AccountId,
|
||||
subscriptionBuilder: SharedRequestsBuilder
|
||||
): Flow<BalanceSyncUpdate> {
|
||||
val runtime = chainRegistry.getRuntime(chain.id)
|
||||
return runCatching {
|
||||
val runtime = chainRegistry.getRuntime(chain.id)
|
||||
|
||||
val statemineType = chainAsset.requireStatemine()
|
||||
val encodableAssetId = statemineType.prepareIdForEncoding(runtime)
|
||||
val statemineType = chainAsset.requireStatemine()
|
||||
val encodableAssetId = statemineType.prepareIdForEncoding(runtime)
|
||||
|
||||
val module = runtime.metadata.statemineModule(statemineType)
|
||||
val module = runtime.metadata.statemineModule(statemineType)
|
||||
|
||||
val assetAccountStorage = module.storage("Account")
|
||||
val assetAccountKey = assetAccountStorage.storageKey(runtime, encodableAssetId, accountId)
|
||||
val assetAccountStorage = module.storage("Account")
|
||||
val assetAccountKey = assetAccountStorage.storageKey(runtime, encodableAssetId, accountId)
|
||||
|
||||
val assetDetailsFlow = statemineAssetsRepository.subscribeAndSyncAssetDetails(chain.id, statemineType, subscriptionBuilder)
|
||||
val assetDetailsFlow = statemineAssetsRepository.subscribeAndSyncAssetDetails(chain.id, statemineType, subscriptionBuilder)
|
||||
|
||||
return combine(
|
||||
subscriptionBuilder.subscribe(assetAccountKey),
|
||||
assetDetailsFlow.map { it.status.transfersFrozen }
|
||||
) { balanceStorageChange, isAssetFrozen ->
|
||||
val assetAccountDecoded = assetAccountStorage.decodeValue(balanceStorageChange.value, runtime)
|
||||
val assetAccount = bindAssetAccountOrEmpty(assetAccountDecoded)
|
||||
combine(
|
||||
subscriptionBuilder.subscribe(assetAccountKey),
|
||||
assetDetailsFlow.map { it.status.transfersFrozen }
|
||||
) { balanceStorageChange, isAssetFrozen ->
|
||||
val assetAccountDecoded = assetAccountStorage.decodeValue(balanceStorageChange.value, runtime)
|
||||
val assetAccount = bindAssetAccountOrEmpty(assetAccountDecoded)
|
||||
|
||||
val assetChanged = updateAssetBalance(metaAccount.id, chainAsset, isAssetFrozen, assetAccount)
|
||||
val assetChanged = updateAssetBalance(metaAccount.id, chainAsset, isAssetFrozen, assetAccount)
|
||||
|
||||
if (assetChanged) {
|
||||
BalanceSyncUpdate.CauseFetchable(balanceStorageChange.block)
|
||||
} else {
|
||||
BalanceSyncUpdate.NoCause
|
||||
if (assetChanged) {
|
||||
BalanceSyncUpdate.CauseFetchable(balanceStorageChange.block)
|
||||
} else {
|
||||
BalanceSyncUpdate.NoCause
|
||||
}
|
||||
}.catch { error ->
|
||||
Log.e(LOG_TAG, "Balance sync failed for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emit(BalanceSyncUpdate.NoCause)
|
||||
}
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to start balance sync for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+58
-22
@@ -42,6 +42,7 @@ import io.novasama.substrate_sdk_android.runtime.metadata.storage
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
|
||||
import io.novasama.substrate_sdk_android.runtime.metadata.storageOrNull
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -63,15 +64,22 @@ class NativeAssetBalance(
|
||||
accountId: AccountId,
|
||||
subscriptionBuilder: SharedRequestsBuilder
|
||||
): Flow<*> {
|
||||
return remoteStorage.subscribe(chain.id, subscriptionBuilder) {
|
||||
combine(
|
||||
metadata.balances.locks.observe(accountId),
|
||||
metadata.balances.freezes.observe(accountId)
|
||||
) { locks, freezes ->
|
||||
val all = locks.orEmpty() + freezes.orEmpty()
|
||||
return runCatching {
|
||||
remoteStorage.subscribe(chain.id, subscriptionBuilder) {
|
||||
combine(
|
||||
metadata.balances.locks.observe(accountId),
|
||||
metadata.balances.freezes.observe(accountId)
|
||||
) { locks, freezes ->
|
||||
val all = locks.orEmpty() + freezes.orEmpty()
|
||||
|
||||
lockDao.updateLocks(all, metaAccount.id, chain.id, chainAsset.id)
|
||||
lockDao.updateLocks(all, metaAccount.id, chain.id, chainAsset.id)
|
||||
}
|
||||
}.catch { error ->
|
||||
Log.e(LOG_TAG, "Balance locks sync failed for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
}
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to start balance locks sync for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,15 +90,23 @@ class NativeAssetBalance(
|
||||
accountId: AccountId,
|
||||
subscriptionBuilder: SharedRequestsBuilder
|
||||
): Flow<*> {
|
||||
val runtime = chainRegistry.getRuntime(chain.id)
|
||||
val storage = runtime.metadata.balances().storageOrNull("Holds") ?: return emptyFlow<Nothing>()
|
||||
val key = storage.storageKey(runtime, accountId)
|
||||
return runCatching {
|
||||
val runtime = chainRegistry.getRuntime(chain.id)
|
||||
val storage = runtime.metadata.balances().storageOrNull("Holds") ?: return emptyFlow<Nothing>()
|
||||
val key = storage.storageKey(runtime, accountId)
|
||||
|
||||
return subscriptionBuilder.subscribe(key)
|
||||
.map { change ->
|
||||
val holds = bindBalanceHolds(storage.decodeValue(change.value, runtime)).orEmpty()
|
||||
holdsDao.updateHolds(holds, metaAccount.id, chain.id, chainAsset.id)
|
||||
}
|
||||
subscriptionBuilder.subscribe(key)
|
||||
.map { change ->
|
||||
val holds = bindBalanceHolds(storage.decodeValue(change.value, runtime)).orEmpty()
|
||||
holdsDao.updateHolds(holds, metaAccount.id, chain.id, chainAsset.id)
|
||||
}
|
||||
.catch { error ->
|
||||
Log.e(LOG_TAG, "Balance holds sync failed for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
}
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to start balance holds sync for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
|
||||
override fun isSelfSufficient(chainAsset: Chain.Asset): Boolean {
|
||||
@@ -98,13 +114,22 @@ class NativeAssetBalance(
|
||||
}
|
||||
|
||||
override suspend fun existentialDeposit(chainAsset: Chain.Asset): BigInteger {
|
||||
val runtime = chainRegistry.getRuntime(chainAsset.chainId)
|
||||
|
||||
return runtime.metadata.balances().numberConstant("ExistentialDeposit", runtime)
|
||||
return runCatching {
|
||||
val runtime = chainRegistry.getRuntime(chainAsset.chainId)
|
||||
runtime.metadata.balances().numberConstant("ExistentialDeposit", runtime)
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to query existential deposit for ${chainAsset.symbol}: ${error.message}")
|
||||
BigInteger.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): ChainAssetBalance {
|
||||
return accountInfoRepository.getAccountInfo(chain.id, accountId).data.toChainAssetBalance(chainAsset)
|
||||
return runCatching {
|
||||
accountInfoRepository.getAccountInfo(chain.id, accountId).data.toChainAssetBalance(chainAsset)
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to query balance for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
ChainAssetBalance.fromFree(chainAsset, BigInteger.ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun subscribeAccountBalanceUpdatePoint(
|
||||
@@ -112,10 +137,17 @@ class NativeAssetBalance(
|
||||
chainAsset: Chain.Asset,
|
||||
accountId: AccountId,
|
||||
): Flow<TransferableBalanceUpdatePoint> {
|
||||
return remoteStorage.subscribe(chain.id) {
|
||||
metadata.system.account.observeWithRaw(accountId).map {
|
||||
TransferableBalanceUpdatePoint(it.at!!)
|
||||
return runCatching {
|
||||
remoteStorage.subscribe(chain.id) {
|
||||
metadata.system.account.observeWithRaw(accountId).map {
|
||||
TransferableBalanceUpdatePoint(it.at!!)
|
||||
}
|
||||
}.catch { error ->
|
||||
Log.e(LOG_TAG, "Balance subscription failed for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
}
|
||||
}.getOrElse { error ->
|
||||
Log.e(LOG_TAG, "Failed to setup balance subscription for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +179,10 @@ class NativeAssetBalance(
|
||||
BalanceSyncUpdate.NoCause
|
||||
}
|
||||
}
|
||||
.catch { error ->
|
||||
Log.e(LOG_TAG, "Balance sync failed for ${chainAsset.symbol} on ${chain.name}: ${error.message}")
|
||||
emit(BalanceSyncUpdate.NoCause)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindBalanceHolds(dynamicInstance: Any?): List<BlockchainHold>? {
|
||||
|
||||
Reference in New Issue
Block a user