mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-06-22 15:01:03 +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
|
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.data.network.runtime.binding.AccountBalance
|
||||||
|
import io.novafoundation.nova.common.utils.LOG_TAG
|
||||||
import io.novafoundation.nova.common.utils.decodeValue
|
import io.novafoundation.nova.common.utils.decodeValue
|
||||||
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
|
import io.novafoundation.nova.core.updater.SharedRequestsBuilder
|
||||||
import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal
|
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.storage
|
||||||
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
|
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -51,28 +54,40 @@ class StatemineAssetBalance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun isSelfSufficient(chainAsset: Chain.Asset): Boolean {
|
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 {
|
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 {
|
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 assetAccount = remoteStorage.query(chain.id) {
|
||||||
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
||||||
|
|
||||||
runtime.metadata.statemineModule(statemineType).storage("Account").query(
|
runtime.metadata.statemineModule(statemineType).storage("Account").query(
|
||||||
encodableId,
|
encodableId,
|
||||||
accountId,
|
accountId,
|
||||||
binding = ::bindAssetAccountOrEmpty
|
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(
|
override suspend fun subscribeAccountBalanceUpdatePoint(
|
||||||
@@ -80,18 +95,25 @@ class StatemineAssetBalance(
|
|||||||
chainAsset: Chain.Asset,
|
chainAsset: Chain.Asset,
|
||||||
accountId: AccountId,
|
accountId: AccountId,
|
||||||
): Flow<TransferableBalanceUpdatePoint> {
|
): Flow<TransferableBalanceUpdatePoint> {
|
||||||
val statemineType = chainAsset.requireStatemine()
|
return runCatching {
|
||||||
|
val statemineType = chainAsset.requireStatemine()
|
||||||
|
|
||||||
return remoteStorage.subscribe(chain.id) {
|
remoteStorage.subscribe(chain.id) {
|
||||||
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
val encodableId = statemineType.prepareIdForEncoding(runtime)
|
||||||
|
|
||||||
runtime.metadata.statemineModule(statemineType).storage("Account").observeWithRaw(
|
runtime.metadata.statemineModule(statemineType).storage("Account").observeWithRaw(
|
||||||
encodableId,
|
encodableId,
|
||||||
accountId,
|
accountId,
|
||||||
binding = ::bindAssetAccountOrEmpty
|
binding = ::bindAssetAccountOrEmpty
|
||||||
).map {
|
).map {
|
||||||
TransferableBalanceUpdatePoint(it.at!!)
|
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,
|
accountId: AccountId,
|
||||||
subscriptionBuilder: SharedRequestsBuilder
|
subscriptionBuilder: SharedRequestsBuilder
|
||||||
): Flow<BalanceSyncUpdate> {
|
): Flow<BalanceSyncUpdate> {
|
||||||
val runtime = chainRegistry.getRuntime(chain.id)
|
return runCatching {
|
||||||
|
val runtime = chainRegistry.getRuntime(chain.id)
|
||||||
|
|
||||||
val statemineType = chainAsset.requireStatemine()
|
val statemineType = chainAsset.requireStatemine()
|
||||||
val encodableAssetId = statemineType.prepareIdForEncoding(runtime)
|
val encodableAssetId = statemineType.prepareIdForEncoding(runtime)
|
||||||
|
|
||||||
val module = runtime.metadata.statemineModule(statemineType)
|
val module = runtime.metadata.statemineModule(statemineType)
|
||||||
|
|
||||||
val assetAccountStorage = module.storage("Account")
|
val assetAccountStorage = module.storage("Account")
|
||||||
val assetAccountKey = assetAccountStorage.storageKey(runtime, encodableAssetId, accountId)
|
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(
|
combine(
|
||||||
subscriptionBuilder.subscribe(assetAccountKey),
|
subscriptionBuilder.subscribe(assetAccountKey),
|
||||||
assetDetailsFlow.map { it.status.transfersFrozen }
|
assetDetailsFlow.map { it.status.transfersFrozen }
|
||||||
) { balanceStorageChange, isAssetFrozen ->
|
) { balanceStorageChange, isAssetFrozen ->
|
||||||
val assetAccountDecoded = assetAccountStorage.decodeValue(balanceStorageChange.value, runtime)
|
val assetAccountDecoded = assetAccountStorage.decodeValue(balanceStorageChange.value, runtime)
|
||||||
val assetAccount = bindAssetAccountOrEmpty(assetAccountDecoded)
|
val assetAccount = bindAssetAccountOrEmpty(assetAccountDecoded)
|
||||||
|
|
||||||
val assetChanged = updateAssetBalance(metaAccount.id, chainAsset, isAssetFrozen, assetAccount)
|
val assetChanged = updateAssetBalance(metaAccount.id, chainAsset, isAssetFrozen, assetAccount)
|
||||||
|
|
||||||
if (assetChanged) {
|
if (assetChanged) {
|
||||||
BalanceSyncUpdate.CauseFetchable(balanceStorageChange.block)
|
BalanceSyncUpdate.CauseFetchable(balanceStorageChange.block)
|
||||||
} else {
|
} else {
|
||||||
BalanceSyncUpdate.NoCause
|
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.storageKey
|
||||||
import io.novasama.substrate_sdk_android.runtime.metadata.storageOrNull
|
import io.novasama.substrate_sdk_android.runtime.metadata.storageOrNull
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -63,15 +64,22 @@ class NativeAssetBalance(
|
|||||||
accountId: AccountId,
|
accountId: AccountId,
|
||||||
subscriptionBuilder: SharedRequestsBuilder
|
subscriptionBuilder: SharedRequestsBuilder
|
||||||
): Flow<*> {
|
): Flow<*> {
|
||||||
return remoteStorage.subscribe(chain.id, subscriptionBuilder) {
|
return runCatching {
|
||||||
combine(
|
remoteStorage.subscribe(chain.id, subscriptionBuilder) {
|
||||||
metadata.balances.locks.observe(accountId),
|
combine(
|
||||||
metadata.balances.freezes.observe(accountId)
|
metadata.balances.locks.observe(accountId),
|
||||||
) { locks, freezes ->
|
metadata.balances.freezes.observe(accountId)
|
||||||
val all = locks.orEmpty() + freezes.orEmpty()
|
) { 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,
|
accountId: AccountId,
|
||||||
subscriptionBuilder: SharedRequestsBuilder
|
subscriptionBuilder: SharedRequestsBuilder
|
||||||
): Flow<*> {
|
): Flow<*> {
|
||||||
val runtime = chainRegistry.getRuntime(chain.id)
|
return runCatching {
|
||||||
val storage = runtime.metadata.balances().storageOrNull("Holds") ?: return emptyFlow<Nothing>()
|
val runtime = chainRegistry.getRuntime(chain.id)
|
||||||
val key = storage.storageKey(runtime, accountId)
|
val storage = runtime.metadata.balances().storageOrNull("Holds") ?: return emptyFlow<Nothing>()
|
||||||
|
val key = storage.storageKey(runtime, accountId)
|
||||||
|
|
||||||
return subscriptionBuilder.subscribe(key)
|
subscriptionBuilder.subscribe(key)
|
||||||
.map { change ->
|
.map { change ->
|
||||||
val holds = bindBalanceHolds(storage.decodeValue(change.value, runtime)).orEmpty()
|
val holds = bindBalanceHolds(storage.decodeValue(change.value, runtime)).orEmpty()
|
||||||
holdsDao.updateHolds(holds, metaAccount.id, chain.id, chainAsset.id)
|
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 {
|
override fun isSelfSufficient(chainAsset: Chain.Asset): Boolean {
|
||||||
@@ -98,13 +114,22 @@ class NativeAssetBalance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun existentialDeposit(chainAsset: Chain.Asset): BigInteger {
|
override suspend fun existentialDeposit(chainAsset: Chain.Asset): BigInteger {
|
||||||
val runtime = chainRegistry.getRuntime(chainAsset.chainId)
|
return runCatching {
|
||||||
|
val runtime = chainRegistry.getRuntime(chainAsset.chainId)
|
||||||
return runtime.metadata.balances().numberConstant("ExistentialDeposit", runtime)
|
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 {
|
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(
|
override suspend fun subscribeAccountBalanceUpdatePoint(
|
||||||
@@ -112,10 +137,17 @@ class NativeAssetBalance(
|
|||||||
chainAsset: Chain.Asset,
|
chainAsset: Chain.Asset,
|
||||||
accountId: AccountId,
|
accountId: AccountId,
|
||||||
): Flow<TransferableBalanceUpdatePoint> {
|
): Flow<TransferableBalanceUpdatePoint> {
|
||||||
return remoteStorage.subscribe(chain.id) {
|
return runCatching {
|
||||||
metadata.system.account.observeWithRaw(accountId).map {
|
remoteStorage.subscribe(chain.id) {
|
||||||
TransferableBalanceUpdatePoint(it.at!!)
|
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
|
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>? {
|
private fun bindBalanceHolds(dynamicInstance: Any?): List<BlockchainHold>? {
|
||||||
|
|||||||
Reference in New Issue
Block a user