From 7d5cb72884d770c26b9af027b5dd042e23dc8d37 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 1 Oct 2024 11:50:31 -0600 Subject: [PATCH 1/4] enable swap --- scripts/app_config/configure_campfire.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/app_config/configure_campfire.sh b/scripts/app_config/configure_campfire.sh index ae6bba36d..2b92a9911 100755 --- a/scripts/app_config/configure_campfire.sh +++ b/scripts/app_config/configure_campfire.sh @@ -49,7 +49,9 @@ const _appDataDirName = "campfire"; const _shortDescriptionText = "Your privacy. Your wallet. Your Firo."; const _commitHash = "$BUILT_COMMIT_HASH"; -const Set _features = {}; +const Set _features = { + AppFeature.swap +}; const ({String light, String dark})? _appIconAsset = ( light: "assets/in_app_logo_icons/campfire-icon_light.svg", From e0d2c8ec740a1a72d837b6b38e1e644139516901 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 1 Oct 2024 12:30:14 -0600 Subject: [PATCH 2/4] add some more logging and possible sync recovery fix? --- lib/electrumx_rpc/client_manager.dart | 20 ++++++++++++------ lib/networking/http.dart | 5 +++++ lib/wallets/wallet/wallet.dart | 11 ++++++++++ pubspec.lock | 30 +++++++++++++-------------- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/lib/electrumx_rpc/client_manager.dart b/lib/electrumx_rpc/client_manager.dart index 662c218ea..fb8b920cc 100644 --- a/lib/electrumx_rpc/client_manager.dart +++ b/lib/electrumx_rpc/client_manager.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:electrum_adapter/electrum_adapter.dart'; + +import '../utilities/logger.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; class ClientManager { @@ -37,13 +39,19 @@ class ClientManager { } _heightCompleters[key] = Completer(); - _subscriptions[key] = client.subscribeHeaders().listen((event) { - _heights[key] = event.height; + _subscriptions[key] = client.subscribeHeaders().listen( + (event) { + _heights[key] = event.height; - if (!_heightCompleters[key]!.isCompleted) { - _heightCompleters[key]!.complete(event.height); - } - }); + if (!_heightCompleters[key]!.isCompleted) { + _heightCompleters[key]!.complete(event.height); + } + }, + onError: (Object err, StackTrace s) => Logging.instance.log( + "ClientManager listen: $err\n$s", + level: LogLevel.Error, + ), + ); } Future getChainHeightFor(CryptoCurrency cryptoCurrency) async { diff --git a/lib/networking/http.dart b/lib/networking/http.dart index a4f25c225..ae2a3b97b 100644 --- a/lib/networking/http.dart +++ b/lib/networking/http.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:socks5_proxy/socks_client.dart'; + import '../utilities/logger.dart'; // WIP wrapper layer @@ -118,6 +119,10 @@ class HTTP { onDone: () => completer.complete( Uint8List.fromList(bytes), ), + onError: (Object err, StackTrace s) => Logging.instance.log( + "Http wrapper layer listen: $err\n$s", + level: LogLevel.Error, + ), ); return completer.future; } diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 1e711f19d..29a3f62b6 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -411,6 +411,17 @@ abstract class Wallet { ); _isConnected = hasNetwork; + + if (status == NodeConnectionStatus.disconnected) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + cryptoCurrency, + ), + ); + } + if (hasNetwork) { unawaited(refresh()); } diff --git a/pubspec.lock b/pubspec.lock index 81a0d1400..48bab8292 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -370,10 +370,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" connectivity_plus: dependency: "direct main" description: @@ -1142,18 +1142,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1718,7 +1718,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" socks5_proxy: dependency: "direct main" description: @@ -1854,10 +1854,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" string_validator: dependency: "direct main" description: @@ -1886,26 +1886,26 @@ packages: dependency: transitive description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.4" tezart: dependency: "direct main" description: From 44fbab715a87b804e2ae33e22d3ee8ed3961c7f7 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 1 Oct 2024 12:55:50 -0600 Subject: [PATCH 3/4] app config swap field defaults --- lib/app_config.dart | 2 ++ lib/services/exchange/exchange_data_loading_service.dart | 5 +++-- scripts/app_config/configure_campfire.sh | 2 ++ scripts/app_config/configure_stack_duo.sh | 2 ++ scripts/app_config/configure_stack_wallet.sh | 2 ++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/app_config.dart b/lib/app_config.dart index 5f9a95829..62ce560c3 100644 --- a/lib/app_config.dart +++ b/lib/app_config.dart @@ -27,6 +27,8 @@ abstract class AppConfig { static List get coins => _supportedCoins; + static ({String from, String to}) get swapDefaults => _swapDefaults; + static bool get isSingleCoinApp => coins.length == 1; static CryptoCurrency? getCryptoCurrencyFor(String coinIdentifier) { diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index cfb01e150..811949d6e 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart'; import 'package:isar/isar.dart'; import 'package:tuple/tuple.dart'; +import '../../app_config.dart'; import '../../db/hive/db.dart'; import '../../models/exchange/active_pair.dart'; import '../../models/exchange/aggregate_currency.dart'; @@ -79,7 +80,7 @@ class ExchangeDataLoadingService { if (await isar.currencies.count() > 0) { pair?.setSend( await getAggregateCurrency( - "BTC", + AppConfig.swapDefaults.from, rateType, null, ), @@ -88,7 +89,7 @@ class ExchangeDataLoadingService { pair?.setReceive( await getAggregateCurrency( - "XMR", + AppConfig.swapDefaults.to, rateType, null, ), diff --git a/scripts/app_config/configure_campfire.sh b/scripts/app_config/configure_campfire.sh index 2b92a9911..883d67fa0 100755 --- a/scripts/app_config/configure_campfire.sh +++ b/scripts/app_config/configure_campfire.sh @@ -62,4 +62,6 @@ final List _supportedCoins = List.unmodifiable([ Firo(CryptoCurrencyNetwork.main), ]); +final ({String from, String to}) _swapDefaults = (from: "BTC", to: "FIRO"); + EOF \ No newline at end of file diff --git a/scripts/app_config/configure_stack_duo.sh b/scripts/app_config/configure_stack_duo.sh index 143faf644..7d1a7665a 100755 --- a/scripts/app_config/configure_stack_duo.sh +++ b/scripts/app_config/configure_stack_duo.sh @@ -64,4 +64,6 @@ final List _supportedCoins = List.unmodifiable([ BitcoinFrost(CryptoCurrencyNetwork.test4), ]); +final ({String from, String to}) _swapDefaults = (from: "BTC", to: "XMR"); + EOF \ No newline at end of file diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 6f4a87981..0fd8e5e8a 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -85,4 +85,6 @@ final List _supportedCoins = List.unmodifiable([ Stellar(CryptoCurrencyNetwork.test), ]); +final ({String from, String to}) _swapDefaults = (from: "BTC", to: "XMR"); + EOF \ No newline at end of file From fec5f5883f201565458ec28dac5a76c75bc39a60 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 1 Oct 2024 16:13:36 -0600 Subject: [PATCH 4/4] dirty --- lib/electrumx_rpc/electrumx_client.dart | 10 +- lib/wallets/wallet/wallet.dart | 146 ++++++++++++++++++------ 2 files changed, 117 insertions(+), 39 deletions(-) diff --git a/lib/electrumx_rpc/electrumx_client.dart b/lib/electrumx_rpc/electrumx_client.dart index c31a8eac5..bceb03fac 100644 --- a/lib/electrumx_rpc/electrumx_client.dart +++ b/lib/electrumx_rpc/electrumx_client.dart @@ -490,7 +490,15 @@ class ElectrumXClient { command: 'server.ping', requestTimeout: const Duration(seconds: 2), retries: retryCount, - ).timeout(const Duration(seconds: 2)) as bool; + ).timeout( + const Duration(seconds: 2), + onTimeout: () { + Logging.instance.log( + "ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host", + level: LogLevel.Debug, + ); + }, + ) as bool; } catch (e) { rethrow; } diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 29a3f62b6..7e7384267 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -487,6 +487,61 @@ abstract class Wallet { // Should fire events Future refresh() async { + final refreshCompleter = Completer(); + final future = refreshCompleter.future.then( + (_) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + cryptoCurrency, + ), + ); + + if (shouldAutoSync) { + _periodicRefreshTimer ??= + Timer.periodic(const Duration(seconds: 150), (timer) async { + // chain height check currently broken + // if ((await chainHeight) != (await storedChainHeight)) { + + // TODO: [prio=med] some kind of quick check if wallet needs to refresh to replace the old refreshIfThereIsNewData call + // if (await refreshIfThereIsNewData()) { + unawaited(refresh()); + + // } + // } + }); + } + }, + onError: (Object error, StackTrace strace) { + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + cryptoCurrency, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + cryptoCurrency, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error, + ); + }, + ); + + unawaited(_refresh(refreshCompleter)); + + return future; + } + + // Should fire events + Future _refresh(Completer completer) async { // Awaiting this lock could be dangerous. // Since refresh is periodic (generally) if (refreshMutex.isLocked) { @@ -494,6 +549,22 @@ abstract class Wallet { } final start = DateTime.now(); + bool tAlive = true; + final t = Timer.periodic(const Duration(seconds: 1), (timer) async { + if (tAlive) { + final pingSuccess = await pingCheck(); + if (!pingSuccess) { + tAlive = false; + } + } else { + timer.cancel(); + } + }); + + void _checkAlive() { + if (!tAlive) throw Exception("refresh alive ping failure"); + } + try { // this acquire should be almost instant due to above check. // Slight possibility of race but should be irrelevant @@ -507,136 +578,135 @@ abstract class Wallet { ), ); + _checkAlive(); + // add some small buffer before making calls. // this can probably be removed in the future but was added as a // debugging feature await Future.delayed(const Duration(milliseconds: 300)); + _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. final Set codesToCheck = {}; + _checkAlive(); if (this is PaynymInterface) { // isSegwit does not matter here at all final myCode = await (this as PaynymInterface).getPaymentCode(isSegwit: false); + _checkAlive(); final nym = await PaynymIsApi().nym(myCode.toString()); + _checkAlive(); if (nym.value != null) { for (final follower in nym.value!.followers) { codesToCheck.add(follower.code); } + _checkAlive(); for (final following in nym.value!.following) { codesToCheck.add(following.code); } } + _checkAlive(); } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + _checkAlive(); await updateChainHeight(); + _checkAlive(); if (this is BitcoinFrostWallet) { await (this as BitcoinFrostWallet).lookAhead(); } + _checkAlive(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is MultiAddressInterface) { if (info.otherData[WalletInfoKeys.reuseAddress] != true) { await (this as MultiAddressInterface) .checkReceivingAddressForTransactions(); } + _checkAlive(); } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is MultiAddressInterface) { await (this as MultiAddressInterface) .checkChangeAddressForTransactions(); } + _checkAlive(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); if (this is SparkInterface) { // this should be called before updateTransactions() await (this as SparkInterface).refreshSparkData(); } + _checkAlive(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); + _checkAlive(); final fetchFuture = updateTransactions(); + _checkAlive(); final utxosRefreshFuture = updateUTXOs(); // if (currentHeight != storedHeight) { GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); + _checkAlive(); await utxosRefreshFuture; GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); + _checkAlive(); await fetchFuture; // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is PaynymInterface && codesToCheck.isNotEmpty) { + _checkAlive(); await (this as PaynymInterface) .checkForNotificationTransactionsTo(codesToCheck); // check utxos again for notification outputs + _checkAlive(); await updateUTXOs(); } + _checkAlive(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); // await getAllTxsToWatch(); + _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is LelantusInterface) { if (info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ?? false) { await (this as LelantusInterface).refreshLelantusData(); + _checkAlive(); } } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); + _checkAlive(); await updateBalance(); + _checkAlive(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - cryptoCurrency, - ), - ); - if (shouldAutoSync) { - _periodicRefreshTimer ??= - Timer.periodic(const Duration(seconds: 150), (timer) async { - // chain height check currently broken - // if ((await chainHeight) != (await storedChainHeight)) { + tAlive = false; // interrupt timer as its not needed anymore - // TODO: [prio=med] some kind of quick check if wallet needs to refresh to replace the old refreshIfThereIsNewData call - // if (await refreshIfThereIsNewData()) { - unawaited(refresh()); - - // } - // } - }); - } + completer.complete(); } catch (error, strace) { - GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - NodeConnectionStatus.disconnected, - walletId, - cryptoCurrency, - ), - ); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - cryptoCurrency, - ), - ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error, - ); + completer.completeError(error, strace); } finally { + t.cancel(); refreshMutex.release(); + if (!completer.isCompleted) { + completer.completeError( + "finally block hit before completer completed", + StackTrace.current, + ); + } Logging.instance.log( "Refresh for "