Cw 868 fix false synchronised status when the socket connection fails due to network issues (#1892)

* prevent setting Synced status when the connection is lost * fallback for UTXO fetch failures * minor fix

Serhii committed Dec 25, 2024 at 21:27 UTC b79ef988c80a6e7000b2d906b49791a69e6733f7
2 files changed +64 -11
cw_bitcoin/lib/electrum.dart
+2 -2
@@ -235,7 +235,7 @@ class ElectrumClient {
235 return [];
236 });
237
238 - Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) async {
238 + Future<List<Map<String, dynamic>>?> getListUnspent(String scriptHash) async {
239 final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]);
240
241 if (result is List) {
@@ -248,7 +248,7 @@ class ElectrumClient {
248 }).toList();
249 }
250
251 - return [];
251 + return null;
252 }
253
254 Future<List<Map<String, dynamic>>> getMempool(String scriptHash) =>
cw_bitcoin/lib/electrum_wallet.dart
+62 -9
@@ -478,6 +478,7 @@ abstract class ElectrumWalletBase
478 if (alwaysScan == true) {
479 _setListeners(walletInfo.restoreHeight);
480 } else {
481 + if (syncStatus is LostConnectionSyncStatus) return;
482 syncStatus = SyncedSyncStatus();
483 }
484 } catch (e, stacktrace) {
@@ -1361,6 +1362,10 @@ abstract class ElectrumWalletBase
1362 Future<void> updateAllUnspents() async {
1363 List<BitcoinUnspent> updatedUnspentCoins = [];
1364
1365 + final previousUnspentCoins = List<BitcoinUnspent>.from(unspentCoins.where((utxo) =>
1366 + utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb &&
1367 + utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord));
1368 +
1369 if (hasSilentPaymentsScanning) {
1370 // Update unspents stored from scanned silent payment transactions
1371 transactionHistory.transactions.values.forEach((tx) {
@@ -1377,13 +1382,27 @@ abstract class ElectrumWalletBase
1382 if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
1383 });
1384
1380 - await Future.wait(walletAddresses.allAddresses
1385 + final addressFutures = walletAddresses.allAddresses
1386 .where((element) => element.type != SegwitAddresType.mweb)
1382 - .map((address) async {
1383 - updatedUnspentCoins.addAll(await fetchUnspent(address));
1384 - }));
1387 + .map((address) => fetchUnspent(address))
1388 + .toList();
1389
1386 - unspentCoins = updatedUnspentCoins;
1390 + final results = await Future.wait(addressFutures);
1391 + final failedCount = results.where((result) => result == null).length;
1392 +
1393 + if (failedCount == 0) {
1394 + for (final result in results) {
1395 + updatedUnspentCoins.addAll(result!);
1396 + }
1397 + unspentCoins = updatedUnspentCoins;
1398 + } else {
1399 + unspentCoins = handleFailedUtxoFetch(
1400 + failedCount: failedCount,
1401 + previousUnspentCoins: previousUnspentCoins,
1402 + updatedUnspentCoins: updatedUnspentCoins,
1403 + results: results,
1404 + );
1405 + }
1406
1407 final currentWalletUnspentCoins =
1408 unspentCoinsInfo.values.where((element) => element.walletId == id);
@@ -1396,6 +1415,38 @@ abstract class ElectrumWalletBase
1415 await _refreshUnspentCoinsInfo();
1416 }
1417
1418 + List<BitcoinUnspent> handleFailedUtxoFetch({
1419 + required int failedCount,
1420 + required List<BitcoinUnspent> previousUnspentCoins,
1421 + required List<BitcoinUnspent> updatedUnspentCoins,
1422 + required List<List<BitcoinUnspent>?> results,
1423 + }) {
1424 +
1425 + if (failedCount == results.length) {
1426 + printV("All UTXOs failed to fetch, falling back to previous UTXOs");
1427 + return previousUnspentCoins;
1428 + }
1429 +
1430 + final successfulUtxos = <BitcoinUnspent>[];
1431 + for (final result in results) {
1432 + if (result != null) {
1433 + successfulUtxos.addAll(result);
1434 + }
1435 + }
1436 +
1437 + if (failedCount > 0 && successfulUtxos.isEmpty) {
1438 + printV("Some UTXOs failed, but no successful UTXOs, falling back to previous UTXOs");
1439 + return previousUnspentCoins;
1440 + }
1441 +
1442 + if (failedCount > 0) {
1443 + printV("Some UTXOs failed, updating with successful UTXOs");
1444 + updatedUnspentCoins.addAll(successfulUtxos);
1445 + }
1446 +
1447 + return updatedUnspentCoins;
1448 + }
1449 +
1450 Future<void> updateCoins(List<BitcoinUnspent> newUnspentCoins) async {
1451 if (newUnspentCoins.isEmpty) {
1452 return;
@@ -1427,15 +1478,17 @@ abstract class ElectrumWalletBase
1478 @action
1479 Future<void> updateUnspentsForAddress(BitcoinAddressRecord address) async {
1480 final newUnspentCoins = await fetchUnspent(address);
1430 - await updateCoins(newUnspentCoins);
1481 + await updateCoins(newUnspentCoins ?? []);
1482 }
1483
1484 @action
1434 - Future<List<BitcoinUnspent>> fetchUnspent(BitcoinAddressRecord address) async {
1435 - List<Map<String, dynamic>> unspents = [];
1485 + Future<List<BitcoinUnspent>?> fetchUnspent(BitcoinAddressRecord address) async {
1486 List<BitcoinUnspent> updatedUnspentCoins = [];
1487
1438 - unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
1488 + final unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
1489 +
1490 + // Failed to fetch unspents
1491 + if (unspents == null) return null;
1492
1493 await Future.wait(unspents.map((unspent) async {
1494 try {