quick (quite quick) and dirty (very dirty) tor/clearnet/both config option for coin network/node connections

This commit is contained in:
julian 2024-11-25 18:29:58 -06:00
parent 6cfe9e9c0f
commit d6d4df7822
19 changed files with 536 additions and 45 deletions

View file

@ -77,6 +77,8 @@ class DbVersionMigrator with WalletDB {
name: e.name,
id: e.id,
useSSL: e.useSSL,
torEnabled: e.torEnabled,
clearEnabled: e.plainEnabled,
),
)
.toList();
@ -88,6 +90,8 @@ class DbVersionMigrator with WalletDB {
name: node.name,
id: node.id,
useSSL: node.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
),
prefs: prefs,
failovers: failovers,

View file

@ -3,6 +3,8 @@ import 'dart:async';
import 'package:electrum_adapter/electrum_adapter.dart';
import '../utilities/logger.dart';
import '../utilities/prefs.dart';
import '../utilities/tor_plain_net_option_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
class ClientManager {
@ -10,6 +12,7 @@ class ClientManager {
static final ClientManager sharedInstance = ClientManager._();
final Map<String, ElectrumClient> _map = {};
final Map<String, TorPlainNetworkOption> _mapNet = {};
final Map<String, int> _heights = {};
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
final Map<String, Completer<int>> _heightCompleters = {};
@ -24,18 +27,37 @@ class ClientManager {
ElectrumClient? getClient({
required CryptoCurrency cryptoCurrency,
}) =>
_map[_keyHelper(cryptoCurrency)];
required TorPlainNetworkOption netType,
}) {
final _key = _keyHelper(cryptoCurrency);
void addClient(
if (netType == _mapNet[_key]) {
return _map[_key];
} else {
return null;
}
}
Future<void> addClient(
ElectrumClient client, {
required CryptoCurrency cryptoCurrency,
}) {
required TorPlainNetworkOption netType,
}) async {
final key = _keyHelper(cryptoCurrency);
if (_map[key] != null) {
throw Exception("ElectrumX Client for $key already exists.");
if (_mapNet[key] == netType) {
throw Exception(
"ElectrumX Client for $key and $netType already exists.",
);
}
await remove(cryptoCurrency: cryptoCurrency);
_map[key] = client;
_mapNet[key] = netType;
} else {
_map[key] = client;
_mapNet[key] = netType;
}
_heightCompleters[key] = Completer<int>();
@ -68,10 +90,24 @@ class ClientManager {
);
}
if (Prefs.instance.useTor) {
if (_mapNet[key]! == TorPlainNetworkOption.clear) {
throw Exception(
"Non-TOR only client for $key found.",
);
}
} else {
if (_mapNet[key]! == TorPlainNetworkOption.tor) {
throw Exception(
"TOR only client for $key found.",
);
}
}
return _heights[key] ?? await _heightCompleters[key]!.future;
}
Future<ElectrumClient?> remove({
Future<(ElectrumClient?, TorPlainNetworkOption?)> remove({
required CryptoCurrency cryptoCurrency,
}) async {
final key = _keyHelper(cryptoCurrency);
@ -80,7 +116,7 @@ class ClientManager {
_heights.remove(key);
_heightCompleters.remove(key);
return _map.remove(key);
return (_map.remove(key), _mapNet.remove(key));
}
Future<void> closeAll() async {
@ -99,6 +135,7 @@ class ClientManager {
_heightCompleters.clear();
_heights.clear();
_subscriptions.clear();
_mapNet.clear();
_map.clear();
}
}

View file

@ -29,6 +29,7 @@ import '../utilities/amount/amount.dart';
import '../utilities/extensions/impl/string.dart';
import '../utilities/logger.dart';
import '../utilities/prefs.dart';
import '../utilities/tor_plain_net_option_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
import 'client_manager.dart';
@ -42,6 +43,10 @@ typedef SparkMempoolData = ({
class WifiOnlyException implements Exception {}
class TorOnlyException implements Exception {}
class ClearnetOnlyException implements Exception {}
class ElectrumXNode {
ElectrumXNode({
required this.address,
@ -49,12 +54,16 @@ class ElectrumXNode {
required this.name,
required this.id,
required this.useSSL,
required this.torEnabled,
required this.clearEnabled,
});
final String address;
final int port;
final String name;
final String id;
final bool useSSL;
final bool torEnabled;
final bool clearEnabled;
factory ElectrumXNode.from(ElectrumXNode node) {
return ElectrumXNode(
@ -63,6 +72,8 @@ class ElectrumXNode {
name: node.name,
id: node.id,
useSSL: node.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.clearEnabled,
);
}
@ -74,6 +85,7 @@ class ElectrumXNode {
class ElectrumXClient {
final CryptoCurrency cryptoCurrency;
final TorPlainNetworkOption netType;
String get host => _host;
late String _host;
@ -90,6 +102,7 @@ class ElectrumXClient {
ElectrumClient? getElectrumAdapter() =>
ClientManager.sharedInstance.getClient(
cryptoCurrency: cryptoCurrency,
netType: netType,
);
late Prefs _prefs;
@ -119,6 +132,7 @@ class ElectrumXClient {
required int port,
required bool useSSL,
required Prefs prefs,
required this.netType,
required List<ElectrumXNode> failovers,
required this.cryptoCurrency,
this.connectionTimeoutForSpecialCaseJsonRPCClients =
@ -168,6 +182,7 @@ class ElectrumXClient {
_electrumAdapterChannel = null;
await (await ClientManager.sharedInstance
.remove(cryptoCurrency: cryptoCurrency))
.$1
?.close();
// Also close any chain height services that are currently open.
@ -193,6 +208,10 @@ class ElectrumXClient {
failovers: failovers,
globalEventBusForTesting: globalEventBusForTesting,
cryptoCurrency: cryptoCurrency,
netType: TorPlainNetworkOption.fromNodeData(
node.torEnabled,
node.clearEnabled,
),
);
}
@ -236,6 +255,18 @@ class ElectrumXClient {
// Get the proxy info from the TorService.
proxyInfo = _torService.getProxyInfo();
}
if (netType == TorPlainNetworkOption.clear) {
_electrumAdapterChannel = null;
await ClientManager.sharedInstance
.remove(cryptoCurrency: cryptoCurrency);
}
} else {
if (netType == TorPlainNetworkOption.tor) {
_electrumAdapterChannel = null;
await ClientManager.sharedInstance
.remove(cryptoCurrency: cryptoCurrency);
}
}
// If the current ElectrumAdapterClient is closed, create a new one.
@ -288,9 +319,10 @@ class ElectrumXClient {
);
}
ClientManager.sharedInstance.addClient(
await ClientManager.sharedInstance.addClient(
newClient,
cryptoCurrency: cryptoCurrency,
netType: netType,
);
}
@ -352,6 +384,10 @@ class ElectrumXClient {
return response;
} on WifiOnlyException {
rethrow;
} on ClearnetOnlyException {
rethrow;
} on TorOnlyException {
rethrow;
} on SocketException {
// likely timed out so then retry
if (retries > 0) {
@ -442,6 +478,10 @@ class ElectrumXClient {
return response;
} on WifiOnlyException {
rethrow;
} on ClearnetOnlyException {
rethrow;
} on TorOnlyException {
rethrow;
} on SocketException {
// likely timed out so then retry
if (retries > 0) {
@ -488,10 +528,10 @@ class ElectrumXClient {
return await request(
requestID: requestID,
command: 'server.ping',
requestTimeout: const Duration(seconds: 2),
requestTimeout: const Duration(seconds: 3),
retries: retryCount,
).timeout(
const Duration(seconds: 2),
const Duration(seconds: 3),
onTimeout: () {
Logging.instance.log(
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",

View file

@ -0,0 +1 @@
class NodeTorMismatchConfigException implements Exception {}

View file

@ -18,14 +18,17 @@ import 'package:uuid/uuid.dart';
import '../../../../models/node_model.dart';
import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/active_wallet_provider.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart';
import '../../../../utilities/enums/sync_type_enum.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/test_node_connection.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/tor_plain_net_option_enum.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
@ -229,6 +232,11 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
}
}
final torEnabled = formData.netOption == TorPlainNetworkOption.tor ||
formData.netOption == TorPlainNetworkOption.both;
final plainEnabled = formData.netOption == TorPlainNetworkOption.clear ||
formData.netOption == TorPlainNetworkOption.both;
switch (viewType) {
case AddEditNodeViewType.add:
final NodeModel node = NodeModel(
@ -243,6 +251,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
isFailover: formData.isFailover!,
trusted: formData.trusted!,
isDown: false,
torEnabled: torEnabled,
plainEnabled: plainEnabled,
);
await ref.read(nodeServiceChangeNotifierProvider).add(
@ -250,6 +260,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
formData.password,
true,
);
await _notifyWalletsOfUpdatedNode();
if (mounted) {
Navigator.of(context)
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
@ -268,6 +279,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
isFailover: formData.isFailover!,
trusted: formData.trusted!,
isDown: false,
torEnabled: torEnabled,
plainEnabled: plainEnabled,
);
await ref.read(nodeServiceChangeNotifierProvider).add(
@ -275,6 +288,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
formData.password,
true,
);
await _notifyWalletsOfUpdatedNode();
if (mounted) {
Navigator.of(context)
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
@ -283,6 +297,39 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
}
}
Future<void> _notifyWalletsOfUpdatedNode() async {
final wallets =
ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin);
final prefs = ref.read(prefsChangeNotifierProvider);
switch (prefs.syncType) {
case SyncingType.currentWalletOnly:
for (final wallet in wallets) {
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
} else {
unawaited(wallet.updateNode());
}
}
break;
case SyncingType.selectedWalletsAtStartup:
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
for (final wallet in wallets) {
if (walletIdsToSync.contains(wallet.walletId)) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
} else {
unawaited(wallet.updateNode());
}
}
break;
case SyncingType.allWalletsOnStartup:
for (final wallet in wallets) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
}
break;
}
}
@override
void initState() {
isDesktop = Util.isDesktop;
@ -568,10 +615,11 @@ class NodeFormData {
String? name, host, login, password;
int? port;
bool? useSSL, isFailover, trusted;
TorPlainNetworkOption? netOption;
@override
String toString() {
return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted }";
return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted, netOption: $netOption }";
}
}
@ -615,6 +663,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
bool _trusted = false;
int? port;
late bool enableSSLCheckbox;
late TorPlainNetworkOption netOption;
late final bool enableAuthFields;
@ -672,6 +721,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
ref.read(nodeFormDataProvider).useSSL = _useSSL;
ref.read(nodeFormDataProvider).isFailover = _isFailover;
ref.read(nodeFormDataProvider).trusted = _trusted;
ref.read(nodeFormDataProvider).netOption = netOption;
}
@override
@ -704,6 +754,15 @@ class _NodeFormState extends ConsumerState<NodeForm> {
_useSSL = node.useSSL;
_isFailover = node.isFailover;
_trusted = node.trusted ?? false;
if (node.torEnabled && !node.plainEnabled) {
netOption = TorPlainNetworkOption.tor;
} else if (node.plainEnabled && !node.torEnabled) {
netOption = TorPlainNetworkOption.clear;
} else {
netOption = TorPlainNetworkOption.both;
}
if (widget.coin is Epiccash) {
enableSSLCheckbox = !node.host.startsWith("http");
} else {
@ -716,6 +775,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
});
} else {
enableSSLCheckbox = true;
netOption = TorPlainNetworkOption.both;
// default to port 3413
// _portController.text = "3413";
}
@ -1168,7 +1228,139 @@ class _NodeFormState extends ConsumerState<NodeForm> {
),
],
),
const SizedBox(
height: 16,
),
Row(
children: [
RadioTextButton(
label: "Only TOR traffic",
enabled: !widget.readOnly,
value: TorPlainNetworkOption.tor,
groupValue: netOption,
onChanged: (value) {
if (!widget.readOnly) {
setState(
() => netOption = TorPlainNetworkOption.tor,
);
_updateState();
}
},
),
],
),
const SizedBox(
height: 8,
),
Row(
children: [
RadioTextButton(
label: "Only non-TOR traffic",
enabled: !widget.readOnly,
value: TorPlainNetworkOption.clear,
groupValue: netOption,
onChanged: (value) {
if (!widget.readOnly) {
setState(
() => netOption = TorPlainNetworkOption.clear,
);
_updateState();
}
},
),
],
),
const SizedBox(
height: 8,
),
Row(
children: [
RadioTextButton(
label: "Allow both",
enabled: !widget.readOnly,
value: TorPlainNetworkOption.both,
groupValue: netOption,
onChanged: (value) {
if (!widget.readOnly) {
setState(
() => netOption = TorPlainNetworkOption.both,
);
_updateState();
}
},
),
],
),
],
);
}
}
class RadioTextButton<T> extends StatelessWidget {
const RadioTextButton({
super.key,
required this.value,
required this.label,
required this.groupValue,
required this.onChanged,
this.enabled = true,
});
final T value;
final String label;
final T groupValue;
final bool enabled;
final void Function(T) onChanged;
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => MouseRegion(
cursor: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () {
if (value != groupValue) {
onChanged.call(value);
}
},
child: Container(
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio<T>(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: value,
groupValue: groupValue,
onChanged: !enabled
? null
: (_) {
if (value != groupValue) {
onChanged.call(value);
}
},
),
),
const SizedBox(
width: 14,
),
Text(
label,
style: STextStyles.w500_14(context),
),
],
),
),
),
);
}
}

View file

@ -13,15 +13,19 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tuple/tuple.dart';
import '../../../../notifications/show_flush_bar.dart';
import 'add_edit_node_view.dart';
import '../../../../providers/global/active_wallet_provider.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart';
import '../../../../utilities/enums/sync_type_enum.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/test_node_connection.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/tor_plain_net_option_enum.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../widgets/background.dart';
@ -31,7 +35,7 @@ import '../../../../widgets/desktop/delete_button.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import 'package:tuple/tuple.dart';
import 'add_edit_node_view.dart';
class NodeDetailsView extends ConsumerStatefulWidget {
const NodeDetailsView({
@ -59,6 +63,39 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
bool _desktopReadOnly = true;
Future<void> _notifyWalletsOfUpdatedNode() async {
final wallets =
ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin);
final prefs = ref.read(prefsChangeNotifierProvider);
switch (prefs.syncType) {
case SyncingType.currentWalletOnly:
for (final wallet in wallets) {
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
} else {
unawaited(wallet.updateNode());
}
}
break;
case SyncingType.selectedWalletsAtStartup:
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
for (final wallet in wallets) {
if (walletIdsToSync.contains(wallet.walletId)) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
} else {
unawaited(wallet.updateNode());
}
}
break;
case SyncingType.allWalletsOnStartup:
for (final wallet in wallets) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
}
break;
}
}
@override
initState() {
secureStore = ref.read(secureStoreProvider);
@ -265,6 +302,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
.read(nodeServiceChangeNotifierProvider)
.getNodeById(id: nodeId)!;
final TorPlainNetworkOption netOption;
if (ref.read(nodeFormDataProvider).netOption != null) {
netOption = ref.read(nodeFormDataProvider).netOption!;
} else {
netOption = TorPlainNetworkOption.fromNodeData(
node.torEnabled,
node.plainEnabled,
);
}
final nodeFormData = NodeFormData()
..useSSL = node.useSSL
..trusted = node.trusted
@ -272,7 +319,8 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
..host = node.host
..login = node.loginName
..port = node.port
..isFailover = node.isFailover;
..isFailover = node.isFailover
..netOption = netOption;
nodeFormData.password = await node.getPassword(
ref.read(secureStoreProvider),
);
@ -338,6 +386,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
loginName: ref.read(nodeFormDataProvider).login,
isFailover:
ref.read(nodeFormDataProvider).isFailover,
torEnabled:
ref.read(nodeFormDataProvider).netOption ==
TorPlainNetworkOption.tor ||
ref.read(nodeFormDataProvider).netOption ==
TorPlainNetworkOption.both,
plainEnabled:
ref.read(nodeFormDataProvider).netOption ==
TorPlainNetworkOption.clear ||
ref.read(nodeFormDataProvider).netOption ==
TorPlainNetworkOption.both,
);
await ref
@ -347,6 +405,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
ref.read(nodeFormDataProvider).password,
true,
);
await _notifyWalletsOfUpdatedNode();
}
},
)

View file

@ -59,6 +59,8 @@ class NodeService extends ChangeNotifier {
enabled: savedNode.enabled,
isFailover: savedNode.isFailover,
trusted: savedNode.trusted,
torEnabled: savedNode.torEnabled,
plainEnabled: savedNode.plainEnabled,
),
);
}
@ -75,6 +77,8 @@ class NodeService extends ChangeNotifier {
enabled: primaryNode.enabled,
isFailover: primaryNode.isFailover,
trusted: primaryNode.trusted,
torEnabled: primaryNode.torEnabled,
plainEnabled: primaryNode.plainEnabled,
),
);
}

View file

@ -11,23 +11,23 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import '../app_config.dart';
import '../db/hive/db.dart';
import '../electrumx_rpc/electrumx_client.dart';
import '../exceptions/electrumx/no_such_transaction.dart';
import '../models/exchange/response_objects/trade.dart';
import '../models/notification_model.dart';
import '../utilities/logger.dart';
import '../utilities/prefs.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import 'exchange/exchange.dart';
import 'exchange/exchange_response.dart';
import 'node_service.dart';
import 'notifications_api.dart';
import 'trade_service.dart';
import 'wallets.dart';
import '../utilities/logger.dart';
import '../utilities/prefs.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import 'exchange/exchange.dart';
class NotificationsService extends ChangeNotifier {
late NodeService nodeService;
@ -136,12 +136,26 @@ class NotificationsService extends ChangeNotifier {
final node = nodeService.getPrimaryNodeFor(currency: coin);
if (node != null) {
if (wallet is ElectrumXInterface) {
if (prefs.useTor) {
if (node.plainEnabled && !node.torEnabled) {
// just ignore I guess??
return;
}
} else {
if (node.torEnabled && !node.plainEnabled) {
// just ignore I guess??
return;
}
}
final eNode = ElectrumXNode(
address: node.host,
port: node.port,
name: node.name,
id: node.id,
useSSL: node.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
);
final failovers = nodeService
.failoverNodesFor(currency: coin)
@ -152,6 +166,8 @@ class NotificationsService extends ChangeNotifier {
name: e.name,
id: e.id,
useSSL: e.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
),
)
.toList();

View file

@ -5,7 +5,6 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:on_chain/ada/ada.dart';
import 'package:on_chain/ada/src/provider/provider/provider.dart';
import 'package:socks5_proxy/socks.dart';
import '../networking/http.dart';
@ -25,6 +24,7 @@ import 'test_epic_box_connection.dart';
import 'test_eth_node_connection.dart';
import 'test_monero_node_connection.dart';
import 'test_stellar_node_connection.dart';
import 'tor_plain_net_option_enum.dart';
Future<bool> _xmrHelper(
NodeFormData nodeFormData,
@ -45,7 +45,6 @@ Future<bool> _xmrHelper(
final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path";
if (proxyInfo == null && uri.host.endsWith(".onion")) {
return false;
}
@ -93,6 +92,24 @@ Future<bool> testNodeConnection({
}) async {
final formData = nodeFormData;
if (ref.read(prefsChangeNotifierProvider).useTor) {
if (formData.netOption! == TorPlainNetworkOption.clear) {
Logging.instance.log(
"This node is configured for non-TOR only but TOR is enabled",
level: LogLevel.Warning,
);
return false;
}
} else {
if (formData.netOption! == TorPlainNetworkOption.tor) {
Logging.instance.log(
"This node is configured for TOR only but TOR is disabled",
level: LogLevel.Warning,
);
return false;
}
}
bool testPassed = false;
switch (cryptoCurrency) {
@ -111,9 +128,7 @@ Future<bool> testNodeConnection({
case CryptonoteCurrency():
try {
final proxyInfo = ref
.read(prefsChangeNotifierProvider)
.useTor
final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor
? ref.read(pTorService).getProxyInfo()
: null;
@ -202,9 +217,7 @@ Future<bool> testNodeConnection({
"action": "version",
},
),
proxyInfo: ref
.read(prefsChangeNotifierProvider)
.useTor
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
? ref.read(pTorService).getProxyInfo()
: null,
);
@ -245,9 +258,7 @@ Future<bool> testNodeConnection({
case Cardano():
try {
final client = HttpClient();
if (ref
.read(prefsChangeNotifierProvider)
.useTor) {
if (ref.read(prefsChangeNotifierProvider).useTor) {
final proxyInfo = TorService.sharedInstance.getProxyInfo();
final proxySettings = ProxySettings(
proxyInfo.host,

View file

@ -0,0 +1,23 @@
enum TorPlainNetworkOption {
tor,
clear,
both;
bool allowsTor() => this == tor || this == both;
bool allowsClear() => this == clear || this == both;
static TorPlainNetworkOption fromNodeData(
bool torEnabled,
bool clearEnabled,
) {
if (clearEnabled && torEnabled) {
return TorPlainNetworkOption.both;
} else if (torEnabled) {
return TorPlainNetworkOption.tor;
} else if (clearEnabled) {
return TorPlainNetworkOption.clear;
} else {
return TorPlainNetworkOption.both;
}
}
}

View file

@ -1338,6 +1338,8 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
name: node.name,
useSSL: node.useSSL,
id: node.id,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
);
}
@ -1352,6 +1354,8 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
name: e.name,
id: e.id,
useSSL: e.useSSL,
torEnabled: e.torEnabled,
clearEnabled: e.plainEnabled,
),
)
.toList();

View file

@ -467,7 +467,22 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
final host = Uri.parse(node.host).host;
({InternetAddress host, int port})? proxy;
if (prefs.useTor) {
if (node.plainEnabled && !node.torEnabled) {
libMoneroWallet?.stopAutoSaving();
libMoneroWallet?.stopListeners();
libMoneroWallet?.stopSyncing();
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
throw Exception("TOR - clearnet mismatch");
}
proxy = TorService.sharedInstance.getProxyInfo();
} else {
if (!node.plainEnabled && node.torEnabled) {
libMoneroWallet?.stopAutoSaving();
libMoneroWallet?.stopListeners();
libMoneroWallet?.stopSyncing();
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
throw Exception("TOR - clearnet mismatch");
}
}
_setSyncStatus(lib_monero_compat.ConnectingSyncStatus());
@ -495,6 +510,9 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
proxy == null ? null : "${proxy.host.address}:${proxy.port}",
);
}
libMoneroWallet?.startSyncing();
libMoneroWallet?.startListeners();
libMoneroWallet?.startAutoSaving();
_setSyncStatus(lib_monero_compat.ConnectedSyncStatus());
} catch (e, s) {
@ -1020,6 +1038,26 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
return;
}
final node = getCurrentNode();
if (prefs.useTor) {
if (node.plainEnabled && !node.torEnabled) {
libMoneroWallet?.stopAutoSaving();
libMoneroWallet?.stopListeners();
libMoneroWallet?.stopSyncing();
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
throw Exception("TOR - clearnet mismatch");
}
} else {
if (!node.plainEnabled && node.torEnabled) {
libMoneroWallet?.stopAutoSaving();
libMoneroWallet?.stopListeners();
libMoneroWallet?.stopSyncing();
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
throw Exception("TOR - clearnet mismatch");
}
}
// this acquire should be almost instant due to above check.
// Slight possibility of race but should be irrelevant
await refreshMutex.acquire();

View file

@ -906,6 +906,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
name: node.name,
useSSL: node.useSSL,
id: node.id,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
);
}
@ -919,6 +921,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
name: e.name,
id: e.id,
useSSL: e.useSSL,
torEnabled: e.torEnabled,
clearEnabled: e.plainEnabled,
),
)
.toList();

View file

@ -13,6 +13,8 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tuple/tuple.dart';
import '../notifications/show_flush_bar.dart';
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import '../pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
@ -26,6 +28,7 @@ import '../utilities/default_nodes.dart';
import '../utilities/enums/sync_type_enum.dart';
import '../utilities/test_node_connection.dart';
import '../utilities/text_styles.dart';
import '../utilities/tor_plain_net_option_enum.dart';
import '../utilities/util.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import 'conditional_parent.dart';
@ -33,7 +36,6 @@ import 'custom_buttons/blue_text_button.dart';
import 'expandable.dart';
import 'node_options_sheet.dart';
import 'rounded_white_container.dart';
import 'package:tuple/tuple.dart';
class NodeCard extends ConsumerStatefulWidget {
const NodeCard({
@ -165,6 +167,15 @@ class _NodeCardState extends ConsumerState<NodeCard> {
text: "Connect",
enabled: _status == "Disconnected",
onTap: () async {
final TorPlainNetworkOption netOption;
if (_node.torEnabled && !_node.plainEnabled) {
netOption = TorPlainNetworkOption.tor;
} else if (_node.plainEnabled && !_node.torEnabled) {
netOption = TorPlainNetworkOption.clear;
} else {
netOption = TorPlainNetworkOption.both;
}
final nodeFormData = NodeFormData()
..useSSL = _node.useSSL
..trusted = _node.trusted
@ -172,6 +183,7 @@ class _NodeCardState extends ConsumerState<NodeCard> {
..host = _node.host
..login = _node.loginName
..port = _node.port
..netOption = netOption
..isFailover = _node.isFailover;
nodeFormData.password = await _node.getPassword(
ref.read(secureStoreProvider),

View file

@ -27,6 +27,7 @@ import '../utilities/default_nodes.dart';
import '../utilities/enums/sync_type_enum.dart';
import '../utilities/test_node_connection.dart';
import '../utilities/text_styles.dart';
import '../utilities/tor_plain_net_option_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import 'rounded_white_container.dart';
@ -256,6 +257,15 @@ class NodeOptionsSheet extends ConsumerWidget {
ref.read(secureStoreProvider),
);
if (context.mounted) {
final TorPlainNetworkOption netOption;
if (node.torEnabled && !node.plainEnabled) {
netOption = TorPlainNetworkOption.tor;
} else if (node.plainEnabled &&
!node.torEnabled) {
netOption = TorPlainNetworkOption.clear;
} else {
netOption = TorPlainNetworkOption.both;
}
final canConnect = await testNodeConnection(
context: context,
nodeFormData: NodeFormData()
@ -266,6 +276,7 @@ class NodeOptionsSheet extends ConsumerWidget {
..port = node.port
..useSSL = node.useSSL
..isFailover = node.isFailover
..netOption = netOption
..trusted = node.trusted,
cryptoCurrency: coin,
ref: ref,

View file

@ -168,6 +168,8 @@ void main() {
name: "some name",
id: "some ID",
useSSL: true,
torEnabled: true,
clearEnabled: true,
);
final client =

View file

@ -1,10 +1,10 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:hive/hive.dart';
import 'package:hive_test/hive_test.dart';
import 'package:stackwallet/app_config.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/app_config.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
@ -48,6 +48,8 @@ void main() {
coinName: "bitcoin",
isFailover: true,
isDown: false,
torEnabled: true,
plainEnabled: true,
);
await service.setPrimaryNodeFor(
coin: Bitcoin(CryptoCurrencyNetwork.main),
@ -129,6 +131,8 @@ void main() {
coinName: "bitcoin",
isFailover: true,
isDown: false,
torEnabled: true,
plainEnabled: true,
);
final nodeB = NodeModel(
host: "host2",
@ -140,6 +144,8 @@ void main() {
coinName: "monero",
isFailover: true,
isDown: false,
torEnabled: true,
plainEnabled: true,
);
final nodeC = NodeModel(
host: "host3",
@ -151,6 +157,8 @@ void main() {
coinName: "epicCash",
isFailover: true,
isDown: false,
torEnabled: true,
plainEnabled: true,
);
setUp(() async {

View file

@ -37,6 +37,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -51,6 +53,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -112,6 +116,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -126,6 +132,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -188,6 +196,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -202,6 +212,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);

View file

@ -25,8 +25,8 @@ void main() {
final mockPrefs = MockPrefs();
final mockNodeService = MockNodeService();
when(mockNodeService.getNodeById(id: "node id")).thenAnswer(
(realInvocation) => NodeModel(
when(mockNodeService.getNodeById(id: "node id"))
.thenAnswer((realInvocation) => NodeModel(
host: "127.0.0.1",
port: 2000,
name: "Some other name",
@ -35,7 +35,10 @@ void main() {
enabled: true,
coinName: "Bitcoin",
isFailover: false,
isDown: false));
isDown: false,
torEnabled: true,
plainEnabled: true,
));
when(mockNodeService.getPrimaryNodeFor(
currency: Bitcoin(CryptoCurrencyNetwork.main)))
@ -48,6 +51,8 @@ void main() {
enabled: true,
coinName: "Bitcoin",
isFailover: false,
torEnabled: true,
plainEnabled: true,
isDown: false));
await tester.pumpWidget(
@ -109,6 +114,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -125,6 +132,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -186,6 +195,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);
@ -202,6 +213,8 @@ void main() {
coinName: "Bitcoin",
isFailover: false,
isDown: false,
torEnabled: true,
plainEnabled: true,
),
);