diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 57f6fb86c..1165c0ebb 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -40,10 +40,10 @@ Future _fetchPrice(Map args) async { // the proxywrapper class wraps all of the complexity of retrying on clearnet / settings handling: try { httpResponse = await proxy.get( - onionUri, + onionUri: onionUri, + clearnetUri: clearnetUri, portOverride: mainThreadProxyPort, torOnly: torOnly, - clearnetUri: clearnetUri, ); responseBody = await utf8.decodeStream(httpResponse); statusCode = httpResponse.statusCode; @@ -71,7 +71,15 @@ Future _fetchPrice(Map args) async { Future _fetchPriceAsync( CryptoCurrency crypto, FiatCurrency fiat, bool torOnly, bool onionOnly) async => - compute(_fetchPrice, { + // compute(_fetchPrice, { + // 'fiat': fiat.toString(), + // 'crypto': crypto.toString(), + // 'torOnly': torOnly, + // 'onionOnly': onionOnly, + // 'port': ProxyWrapper.port, + // 'torEnabled': ProxyWrapper.enabled, + // }); + _fetchPrice({ 'fiat': fiat.toString(), 'crypto': crypto.toString(), 'torOnly': torOnly, diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 92e474316..e7a76d847 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -1,17 +1,13 @@ -import 'dart:io'; import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/view_model/settings/tor_connection.dart'; import 'package:cake_wallet/view_model/settings/tor_view_model.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:tor/tor.dart'; class WalletLoadingService { WalletLoadingService( diff --git a/lib/di.dart b/lib/di.dart index a90497cd7..bc0bcdafd 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -61,6 +61,7 @@ import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/proxy_wrapper.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; @@ -726,6 +727,7 @@ Future setup({ getIt.registerFactory(() => TrocadorProvidersViewModel(getIt.get())); getIt.registerSingleton(TorViewModel(getIt.get())); + getIt.registerSingleton(ProxyWrapper(settingsStore: getIt.get())); if (DeviceInfo.instance.isMobile && settingsStore.shouldStartTorOnLaunch) { getIt.get().startTor(); diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index 4e9dbd3d5..f567fc5bc 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -309,6 +309,10 @@ class TrocadorExchangeProvider extends ExchangeProvider { ProxyWrapper proxy = await getIt.get(); Uri onionUri = Uri.http(onionApiAuthority, path, queryParams); Uri clearnetUri = Uri.http(onionApiAuthority, path, queryParams); - return await proxy.get(onionUri, torOnly: useTorOnly, clearnetUri: clearnetUri); + return await proxy.get( + onionUri: onionUri, + clearnetUri: clearnetUri, + torOnly: useTorOnly, + ); } } diff --git a/lib/utils/proxy_wrapper.dart b/lib/utils/proxy_wrapper.dart index 304782001..8f854afb7 100644 --- a/lib/utils/proxy_wrapper.dart +++ b/lib/utils/proxy_wrapper.dart @@ -5,6 +5,16 @@ import 'package:cake_wallet/view_model/settings/tor_connection.dart'; import 'package:socks5_proxy/socks.dart'; import 'package:tor/tor.dart'; +// this is the only way to ensure we're making a non-tor connection: +class NullOverrides extends HttpOverrides { + NullOverrides(); + + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context); + } +} + class ProxyWrapper { ProxyWrapper({ required this.settingsStore, @@ -14,16 +24,11 @@ class ProxyWrapper { HttpClient? _torClient; - // Factory method to get the singleton instance of TorSingleton - // static ProxyWrapper get instance => _instance; - static int get port => Tor.instance.port; static bool get enabled => Tor.instance.enabled; bool started = false; - // bool torEnabled = false; - // bool torOnly = false; // Method to get or create the Tor proxy instance Future getProxyHttpClient({int? portOverride}) async { @@ -44,113 +49,166 @@ class ProxyWrapper { return _torClient!; } - Future get( - Uri uri, { - Map? headers, - int? portOverride, - bool torOnly = false, - Uri? clearnetUri, + Future makeGet({ + required HttpClient client, + required Uri uri, + required Map? headers, }) async { - HttpClient? client; - late bool torEnabled; - if (settingsStore.torConnectionMode == TorConnectionMode.onionOnly || - settingsStore.torConnectionMode == TorConnectionMode.enabled) { - client = await getProxyHttpClient(portOverride: portOverride); - torEnabled = true; - } else { - client = HttpClient(); - torEnabled = false; + final request = await client.getUrl(uri); + if (headers != null) { + headers.forEach((key, value) { + request.headers.add(key, value); + }); } - - if (settingsStore.torConnectionMode == TorConnectionMode.onionOnly) { - if (!uri.path.contains(".onion")) { - throw Exception("Cannot connect to clearnet"); - } - } - - HttpClientResponse? response; - - try { - final request = await client.getUrl(uri); - if (headers != null) { - headers.forEach((key, value) { - request.headers.add(key, value); - }); - } - response = await request.close(); - } catch (e) { - if (!torOnly && - torEnabled && - settingsStore.torConnectionMode != TorConnectionMode.onionOnly) { - // try again without tor: - client = HttpClient(); - final request = await client.getUrl(clearnetUri ?? uri); - if (headers != null) { - headers.forEach((key, value) { - request.headers.add(key, value); - }); - } - response = await request.close(); - } else { - throw e; - } - } - - return response; + return await request.close(); } - Future post( - Uri uri, { + Future makePost({ + required HttpClient client, + required Uri uri, + required Map? headers, + }) async { + final request = await client.postUrl(uri); + if (headers != null) { + headers.forEach((key, value) { + request.headers.add(key, value); + }); + } + return await request.close(); + } + + Future get({ Map? headers, int? portOverride, bool torOnly = false, Uri? clearnetUri, + Uri? onionUri, }) async { - HttpClient? client; + HttpClient? torClient; late bool torEnabled; if (settingsStore.torConnectionMode == TorConnectionMode.onionOnly || settingsStore.torConnectionMode == TorConnectionMode.enabled) { - client = await getProxyHttpClient(portOverride: portOverride); + torClient = await getProxyHttpClient(portOverride: portOverride); torEnabled = true; } else { - client = HttpClient(); torEnabled = false; } if (settingsStore.torConnectionMode == TorConnectionMode.onionOnly) { - if (!uri.path.contains(".onion")) { + if (onionUri == null) { throw Exception("Cannot connect to clearnet"); } } - HttpClientResponse? response; - - try { - final request = await client.postUrl(uri); - if (headers != null) { - headers.forEach((key, value) { - request.headers.add(key, value); - }); + // if tor is enabled, try to connect to the onion url first: + if (torEnabled) { + if (onionUri != null) { + try { + return makeGet( + client: torClient!, + uri: onionUri, + headers: headers, + ); + } catch (_) {} } - response = await request.close(); - } catch (e) { - if (!torOnly && - torEnabled && - settingsStore.torConnectionMode != TorConnectionMode.onionOnly) { - // try again without tor: - client = HttpClient(); - final request = await client.postUrl(clearnetUri ?? uri); - if (headers != null) { - headers.forEach((key, value) { - request.headers.add(key, value); - }); - } - response = await request.close(); - } else { - throw e; + + if (clearnetUri != null && settingsStore.torConnectionMode != TorConnectionMode.onionOnly) { + try { + return makeGet( + client: torClient!, + uri: clearnetUri, + headers: headers, + ); + } catch (_) {} } } - return response; + if (!torOnly && clearnetUri != null) { + try { + return HttpOverrides.runZoned( + () { + return makeGet( + client: HttpClient(), + uri: clearnetUri, + headers: headers, + ); + }, + createHttpClient: NullOverrides().createHttpClient, + ); + } catch (_) { + // we weren't able to get a response: + rethrow; + } + } + + throw Exception("Unable to connect to server"); + } + + Future post({ + Map? headers, + int? portOverride, + bool torOnly = false, + Uri? clearnetUri, + Uri? onionUri, + }) async { + HttpClient? torClient; + late bool torEnabled; + if (settingsStore.torConnectionMode == TorConnectionMode.onionOnly || + settingsStore.torConnectionMode == TorConnectionMode.enabled) { + torClient = await getProxyHttpClient(portOverride: portOverride); + torEnabled = true; + } else { + torEnabled = false; + } + + if (settingsStore.torConnectionMode == TorConnectionMode.onionOnly) { + if (onionUri == null) { + throw Exception("Cannot connect to clearnet"); + } + } + + // if tor is enabled, try to connect to the onion url first: + + if (torEnabled) { + if (onionUri != null) { + try { + return makePost( + client: torClient!, + uri: onionUri, + headers: headers, + ); + } catch (_) {} + } + + if (clearnetUri != null && settingsStore.torConnectionMode != TorConnectionMode.onionOnly) { + try { + return makePost( + client: torClient!, + uri: clearnetUri, + headers: headers, + ); + } catch (_) {} + } + } + + if (!torOnly && clearnetUri != null) { + try { + return HttpOverrides.runZoned( + () { + return makePost( + client: HttpClient(), + uri: clearnetUri, + headers: headers, + ); + }, + createHttpClient: NullOverrides().createHttpClient, + ); + } catch (_) { + // we weren't able to get a response: + rethrow; + } + } + + throw Exception("Unable to connect to server"); } } diff --git a/lib/view_model/settings/tor_view_model.dart b/lib/view_model/settings/tor_view_model.dart index 4ab9d75fe..77f18ba92 100644 --- a/lib/view_model/settings/tor_view_model.dart +++ b/lib/view_model/settings/tor_view_model.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/settings/tor_connection.dart'; import 'package:mobx/mobx.dart'; +import 'package:socks5_proxy/socks_client.dart'; import 'package:tor/tor.dart'; part 'tor_view_model.g.dart'; @@ -44,13 +45,26 @@ abstract class TorViewModelBase with Store { Future startTor() async { try { torConnectionStatus = TorConnectionStatus.connecting; + await Tor.init(); - await Tor.instance.enable(); + + // start only if not already running: + if (Tor.instance.port == -1) { + await Tor.instance.enable(); + } _settingsStore.shouldStartTorOnLaunch = true; torConnectionStatus = TorConnectionStatus.connected; + SocksTCPClient.setProxy(proxies: [ + ProxySettings( + InternetAddress.loopbackIPv4, + Tor.instance.port, + password: null, + ), + ]); + // connect to node through the proxy: final appStore = getIt.get(); if (appStore.wallet != null) { @@ -70,5 +84,6 @@ abstract class TorViewModelBase with Store { Tor.instance.disable(); _settingsStore.shouldStartTorOnLaunch = false; torConnectionStatus = TorConnectionStatus.disconnected; + SocksTCPClient.setProxy(proxies: null); } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 63a97b7d2..331d380e0 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -102,7 +102,11 @@ dependencies: git: url: https://github.com/cake-tech/tor.git ref: main - socks5_proxy: ^1.0.4 + # socks5_proxy: ^1.0.4 + socks5_proxy: + git: + url: https://github.com/perishllc/socks_dart.git + ref: main flutter_svg: ^2.0.9 polyseed: ^0.0.2