From 1d00f78fa92e474bbdb5ed479f4c7c5cad9f83f7 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Thu, 9 Jul 2026 02:10:40 -0700 Subject: [PATCH] fix: isolate per-chain mapping failures in currentChains, not just register/unregister mapChainLocalToChain() runs as a single mapList{} transform over the entire chain list. If it throws for even one malformed row (e.g. a JSON parse failure on the chain's `additional` blob), the whole transform throws, killing the Eagerly-shared currentChains/chainsById flows for every chain - permanently, since Eagerly sharing never restarts once its upstream dies. This is the same class of bug already fixed for registerChain/unregisterChain in the diff-processing step below, but one level upstream of it: a mapping failure here never even reaches that per-chain isolation, since it happens before the diff is computed at all. Device logs on a fresh install showed exactly this shape: 8 chains (including Pezkuwi, Pezkuwi Asset Hub, Polkadot Asset Hub) successfully completed "Constructed runtime" within the first ~2.5 seconds, then all activity for every remaining chain stopped completely for the rest of the session - no crash, no battery-saver kill (confirmed off), the process simply went silent, consistent with the shared flow dying mid-registration and never recovering. --- .../nova/runtime/multiNetwork/ChainRegistry.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt index 518d6c49..9c7885d2 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt @@ -7,7 +7,7 @@ import io.novafoundation.nova.common.utils.RuntimeContext import io.novafoundation.nova.common.utils.diffed import io.novafoundation.nova.common.utils.filterList import io.novafoundation.nova.common.utils.inBackground -import io.novafoundation.nova.common.utils.mapList +import io.novafoundation.nova.common.utils.mapListNotNull import io.novafoundation.nova.common.utils.mapNotNullToSet import io.novafoundation.nova.common.utils.provideContext import io.novafoundation.nova.common.utils.removeHexPrefix @@ -75,7 +75,16 @@ class ChainRegistry( ) : CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) { val currentChains = chainDao.joinChainInfoFlow() - .mapList { mapChainLocalToChain(it, gson) } + // mapListNotNull, not mapList: mapChainLocalToChain() can throw on a single malformed row (e.g. a + // gson.fromJson() failure on the chain's `additional` JSON blob) - since this whole step runs as ONE + // transform over the ENTIRE chain list, one bad chain would previously throw out of this operator and + // permanently kill this Eagerly-shared flow for every chain, not just the offending one. Skip and log + // instead, matching the per-chain isolation already applied to registerChain/unregisterChain below. + .mapListNotNull { chainLocal -> + runCatching { mapChainLocalToChain(chainLocal, gson) } + .onFailure { Log.e(LOG_TAG, "Failed to map chain ${chainLocal.chain.id} (${chainLocal.chain.name}) from local DB", it) } + .getOrNull() + } .diffed() .map { diff -> // Each chain's register/unregister is isolated: one malformed/leftover row (e.g. a chain persisted