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, name: e.name,
id: e.id, id: e.id,
useSSL: e.useSSL, useSSL: e.useSSL,
torEnabled: e.torEnabled,
clearEnabled: e.plainEnabled,
), ),
) )
.toList(); .toList();
@ -88,6 +90,8 @@ class DbVersionMigrator with WalletDB {
name: node.name, name: node.name,
id: node.id, id: node.id,
useSSL: node.useSSL, useSSL: node.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
), ),
prefs: prefs, prefs: prefs,
failovers: failovers, failovers: failovers,

View file

@ -3,6 +3,8 @@ import 'dart:async';
import 'package:electrum_adapter/electrum_adapter.dart'; import 'package:electrum_adapter/electrum_adapter.dart';
import '../utilities/logger.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/crypto_currency.dart';
class ClientManager { class ClientManager {
@ -10,6 +12,7 @@ class ClientManager {
static final ClientManager sharedInstance = ClientManager._(); static final ClientManager sharedInstance = ClientManager._();
final Map<String, ElectrumClient> _map = {}; final Map<String, ElectrumClient> _map = {};
final Map<String, TorPlainNetworkOption> _mapNet = {};
final Map<String, int> _heights = {}; final Map<String, int> _heights = {};
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {}; final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
final Map<String, Completer<int>> _heightCompleters = {}; final Map<String, Completer<int>> _heightCompleters = {};
@ -24,18 +27,37 @@ class ClientManager {
ElectrumClient? getClient({ ElectrumClient? getClient({
required CryptoCurrency cryptoCurrency, required CryptoCurrency cryptoCurrency,
}) => required TorPlainNetworkOption netType,
_map[_keyHelper(cryptoCurrency)]; }) {
final _key = _keyHelper(cryptoCurrency);
void addClient( if (netType == _mapNet[_key]) {
return _map[_key];
} else {
return null;
}
}
Future<void> addClient(
ElectrumClient client, { ElectrumClient client, {
required CryptoCurrency cryptoCurrency, required CryptoCurrency cryptoCurrency,
}) { required TorPlainNetworkOption netType,
}) async {
final key = _keyHelper(cryptoCurrency); final key = _keyHelper(cryptoCurrency);
if (_map[key] != null) { 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 { } else {
_map[key] = client; _map[key] = client;
_mapNet[key] = netType;
} }
_heightCompleters[key] = Completer<int>(); _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; return _heights[key] ?? await _heightCompleters[key]!.future;
} }
Future<ElectrumClient?> remove({ Future<(ElectrumClient?, TorPlainNetworkOption?)> remove({
required CryptoCurrency cryptoCurrency, required CryptoCurrency cryptoCurrency,
}) async { }) async {
final key = _keyHelper(cryptoCurrency); final key = _keyHelper(cryptoCurrency);
@ -80,7 +116,7 @@ class ClientManager {
_heights.remove(key); _heights.remove(key);
_heightCompleters.remove(key); _heightCompleters.remove(key);
return _map.remove(key); return (_map.remove(key), _mapNet.remove(key));
} }
Future<void> closeAll() async { Future<void> closeAll() async {
@ -99,6 +135,7 @@ class ClientManager {
_heightCompleters.clear(); _heightCompleters.clear();
_heights.clear(); _heights.clear();
_subscriptions.clear(); _subscriptions.clear();
_mapNet.clear();
_map.clear(); _map.clear();
} }
} }

View file

@ -29,6 +29,7 @@ import '../utilities/amount/amount.dart';
import '../utilities/extensions/impl/string.dart'; import '../utilities/extensions/impl/string.dart';
import '../utilities/logger.dart'; import '../utilities/logger.dart';
import '../utilities/prefs.dart'; import '../utilities/prefs.dart';
import '../utilities/tor_plain_net_option_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart'; import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
import 'client_manager.dart'; import 'client_manager.dart';
@ -42,6 +43,10 @@ typedef SparkMempoolData = ({
class WifiOnlyException implements Exception {} class WifiOnlyException implements Exception {}
class TorOnlyException implements Exception {}
class ClearnetOnlyException implements Exception {}
class ElectrumXNode { class ElectrumXNode {
ElectrumXNode({ ElectrumXNode({
required this.address, required this.address,
@ -49,12 +54,16 @@ class ElectrumXNode {
required this.name, required this.name,
required this.id, required this.id,
required this.useSSL, required this.useSSL,
required this.torEnabled,
required this.clearEnabled,
}); });
final String address; final String address;
final int port; final int port;
final String name; final String name;
final String id; final String id;
final bool useSSL; final bool useSSL;
final bool torEnabled;
final bool clearEnabled;
factory ElectrumXNode.from(ElectrumXNode node) { factory ElectrumXNode.from(ElectrumXNode node) {
return ElectrumXNode( return ElectrumXNode(
@ -63,6 +72,8 @@ class ElectrumXNode {
name: node.name, name: node.name,
id: node.id, id: node.id,
useSSL: node.useSSL, useSSL: node.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.clearEnabled,
); );
} }
@ -74,6 +85,7 @@ class ElectrumXNode {
class ElectrumXClient { class ElectrumXClient {
final CryptoCurrency cryptoCurrency; final CryptoCurrency cryptoCurrency;
final TorPlainNetworkOption netType;
String get host => _host; String get host => _host;
late String _host; late String _host;
@ -90,6 +102,7 @@ class ElectrumXClient {
ElectrumClient? getElectrumAdapter() => ElectrumClient? getElectrumAdapter() =>
ClientManager.sharedInstance.getClient( ClientManager.sharedInstance.getClient(
cryptoCurrency: cryptoCurrency, cryptoCurrency: cryptoCurrency,
netType: netType,
); );
late Prefs _prefs; late Prefs _prefs;
@ -119,6 +132,7 @@ class ElectrumXClient {
required int port, required int port,
required bool useSSL, required bool useSSL,
required Prefs prefs, required Prefs prefs,
required this.netType,
required List<ElectrumXNode> failovers, required List<ElectrumXNode> failovers,
required this.cryptoCurrency, required this.cryptoCurrency,
this.connectionTimeoutForSpecialCaseJsonRPCClients = this.connectionTimeoutForSpecialCaseJsonRPCClients =
@ -168,6 +182,7 @@ class ElectrumXClient {
_electrumAdapterChannel = null; _electrumAdapterChannel = null;
await (await ClientManager.sharedInstance await (await ClientManager.sharedInstance
.remove(cryptoCurrency: cryptoCurrency)) .remove(cryptoCurrency: cryptoCurrency))
.$1
?.close(); ?.close();
// Also close any chain height services that are currently open. // Also close any chain height services that are currently open.
@ -193,6 +208,10 @@ class ElectrumXClient {
failovers: failovers, failovers: failovers,
globalEventBusForTesting: globalEventBusForTesting, globalEventBusForTesting: globalEventBusForTesting,
cryptoCurrency: cryptoCurrency, cryptoCurrency: cryptoCurrency,
netType: TorPlainNetworkOption.fromNodeData(
node.torEnabled,
node.clearEnabled,
),
); );
} }
@ -236,6 +255,18 @@ class ElectrumXClient {
// Get the proxy info from the TorService. // Get the proxy info from the TorService.
proxyInfo = _torService.getProxyInfo(); 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. // If the current ElectrumAdapterClient is closed, create a new one.
@ -288,9 +319,10 @@ class ElectrumXClient {
); );
} }
ClientManager.sharedInstance.addClient( await ClientManager.sharedInstance.addClient(
newClient, newClient,
cryptoCurrency: cryptoCurrency, cryptoCurrency: cryptoCurrency,
netType: netType,
); );
} }
@ -352,6 +384,10 @@ class ElectrumXClient {
return response; return response;
} on WifiOnlyException { } on WifiOnlyException {
rethrow; rethrow;
} on ClearnetOnlyException {
rethrow;
} on TorOnlyException {
rethrow;
} on SocketException { } on SocketException {
// likely timed out so then retry // likely timed out so then retry
if (retries > 0) { if (retries > 0) {
@ -442,6 +478,10 @@ class ElectrumXClient {
return response; return response;
} on WifiOnlyException { } on WifiOnlyException {
rethrow; rethrow;
} on ClearnetOnlyException {
rethrow;
} on TorOnlyException {
rethrow;
} on SocketException { } on SocketException {
// likely timed out so then retry // likely timed out so then retry
if (retries > 0) { if (retries > 0) {
@ -488,10 +528,10 @@ class ElectrumXClient {
return await request( return await request(
requestID: requestID, requestID: requestID,
command: 'server.ping', command: 'server.ping',
requestTimeout: const Duration(seconds: 2), requestTimeout: const Duration(seconds: 3),
retries: retryCount, retries: retryCount,
).timeout( ).timeout(
const Duration(seconds: 2), const Duration(seconds: 3),
onTimeout: () { onTimeout: () {
Logging.instance.log( Logging.instance.log(
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host", "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 '../../../../models/node_model.dart';
import '../../../../notifications/show_flush_bar.dart'; import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/active_wallet_provider.dart';
import '../../../../providers/global/secure_store_provider.dart'; import '../../../../providers/global/secure_store_provider.dart';
import '../../../../providers/providers.dart'; import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart'; import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart'; import '../../../../utilities/constants.dart';
import '../../../../utilities/enums/sync_type_enum.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart'; import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/test_node_connection.dart'; import '../../../../utilities/test_node_connection.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../utilities/tor_plain_net_option_enum.dart';
import '../../../../utilities/util.dart'; import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../wallets/crypto_currency/intermediate/cryptonote_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) { switch (viewType) {
case AddEditNodeViewType.add: case AddEditNodeViewType.add:
final NodeModel node = NodeModel( final NodeModel node = NodeModel(
@ -243,6 +251,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
isFailover: formData.isFailover!, isFailover: formData.isFailover!,
trusted: formData.trusted!, trusted: formData.trusted!,
isDown: false, isDown: false,
torEnabled: torEnabled,
plainEnabled: plainEnabled,
); );
await ref.read(nodeServiceChangeNotifierProvider).add( await ref.read(nodeServiceChangeNotifierProvider).add(
@ -250,6 +260,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
formData.password, formData.password,
true, true,
); );
await _notifyWalletsOfUpdatedNode();
if (mounted) { if (mounted) {
Navigator.of(context) Navigator.of(context)
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete)); .popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
@ -268,6 +279,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
isFailover: formData.isFailover!, isFailover: formData.isFailover!,
trusted: formData.trusted!, trusted: formData.trusted!,
isDown: false, isDown: false,
torEnabled: torEnabled,
plainEnabled: plainEnabled,
); );
await ref.read(nodeServiceChangeNotifierProvider).add( await ref.read(nodeServiceChangeNotifierProvider).add(
@ -275,6 +288,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
formData.password, formData.password,
true, true,
); );
await _notifyWalletsOfUpdatedNode();
if (mounted) { if (mounted) {
Navigator.of(context) Navigator.of(context)
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete)); .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 @override
void initState() { void initState() {
isDesktop = Util.isDesktop; isDesktop = Util.isDesktop;
@ -568,10 +615,11 @@ class NodeFormData {
String? name, host, login, password; String? name, host, login, password;
int? port; int? port;
bool? useSSL, isFailover, trusted; bool? useSSL, isFailover, trusted;
TorPlainNetworkOption? netOption;
@override @override
String toString() { 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; bool _trusted = false;
int? port; int? port;
late bool enableSSLCheckbox; late bool enableSSLCheckbox;
late TorPlainNetworkOption netOption;
late final bool enableAuthFields; late final bool enableAuthFields;
@ -672,6 +721,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
ref.read(nodeFormDataProvider).useSSL = _useSSL; ref.read(nodeFormDataProvider).useSSL = _useSSL;
ref.read(nodeFormDataProvider).isFailover = _isFailover; ref.read(nodeFormDataProvider).isFailover = _isFailover;
ref.read(nodeFormDataProvider).trusted = _trusted; ref.read(nodeFormDataProvider).trusted = _trusted;
ref.read(nodeFormDataProvider).netOption = netOption;
} }
@override @override
@ -704,6 +754,15 @@ class _NodeFormState extends ConsumerState<NodeForm> {
_useSSL = node.useSSL; _useSSL = node.useSSL;
_isFailover = node.isFailover; _isFailover = node.isFailover;
_trusted = node.trusted ?? false; _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) { if (widget.coin is Epiccash) {
enableSSLCheckbox = !node.host.startsWith("http"); enableSSLCheckbox = !node.host.startsWith("http");
} else { } else {
@ -716,6 +775,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
}); });
} else { } else {
enableSSLCheckbox = true; enableSSLCheckbox = true;
netOption = TorPlainNetworkOption.both;
// default to port 3413 // default to port 3413
// _portController.text = "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/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:tuple/tuple.dart';
import '../../../../notifications/show_flush_bar.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/global/secure_store_provider.dart';
import '../../../../providers/providers.dart'; import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart'; import '../../../../utilities/assets.dart';
import '../../../../utilities/enums/sync_type_enum.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart'; import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/test_node_connection.dart'; import '../../../../utilities/test_node_connection.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../utilities/tor_plain_net_option_enum.dart';
import '../../../../utilities/util.dart'; import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../widgets/background.dart'; import '../../../../widgets/background.dart';
@ -31,7 +35,7 @@ import '../../../../widgets/desktop/delete_button.dart';
import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart';
import 'package:tuple/tuple.dart'; import 'add_edit_node_view.dart';
class NodeDetailsView extends ConsumerStatefulWidget { class NodeDetailsView extends ConsumerStatefulWidget {
const NodeDetailsView({ const NodeDetailsView({
@ -59,6 +63,39 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
bool _desktopReadOnly = true; 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 @override
initState() { initState() {
secureStore = ref.read(secureStoreProvider); secureStore = ref.read(secureStoreProvider);
@ -265,6 +302,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
.read(nodeServiceChangeNotifierProvider) .read(nodeServiceChangeNotifierProvider)
.getNodeById(id: nodeId)!; .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() final nodeFormData = NodeFormData()
..useSSL = node.useSSL ..useSSL = node.useSSL
..trusted = node.trusted ..trusted = node.trusted
@ -272,7 +319,8 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
..host = node.host ..host = node.host
..login = node.loginName ..login = node.loginName
..port = node.port ..port = node.port
..isFailover = node.isFailover; ..isFailover = node.isFailover
..netOption = netOption;
nodeFormData.password = await node.getPassword( nodeFormData.password = await node.getPassword(
ref.read(secureStoreProvider), ref.read(secureStoreProvider),
); );
@ -338,6 +386,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
loginName: ref.read(nodeFormDataProvider).login, loginName: ref.read(nodeFormDataProvider).login,
isFailover: isFailover:
ref.read(nodeFormDataProvider).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 await ref
@ -347,6 +405,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
ref.read(nodeFormDataProvider).password, ref.read(nodeFormDataProvider).password,
true, true,
); );
await _notifyWalletsOfUpdatedNode();
} }
}, },
) )

View file

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

View file

@ -11,23 +11,23 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../app_config.dart'; import '../app_config.dart';
import '../db/hive/db.dart'; import '../db/hive/db.dart';
import '../electrumx_rpc/electrumx_client.dart'; import '../electrumx_rpc/electrumx_client.dart';
import '../exceptions/electrumx/no_such_transaction.dart'; import '../exceptions/electrumx/no_such_transaction.dart';
import '../models/exchange/response_objects/trade.dart'; import '../models/exchange/response_objects/trade.dart';
import '../models/notification_model.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 'exchange/exchange_response.dart';
import 'node_service.dart'; import 'node_service.dart';
import 'notifications_api.dart'; import 'notifications_api.dart';
import 'trade_service.dart'; import 'trade_service.dart';
import 'wallets.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 { class NotificationsService extends ChangeNotifier {
late NodeService nodeService; late NodeService nodeService;
@ -136,12 +136,26 @@ class NotificationsService extends ChangeNotifier {
final node = nodeService.getPrimaryNodeFor(currency: coin); final node = nodeService.getPrimaryNodeFor(currency: coin);
if (node != null) { if (node != null) {
if (wallet is ElectrumXInterface) { 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( final eNode = ElectrumXNode(
address: node.host, address: node.host,
port: node.port, port: node.port,
name: node.name, name: node.name,
id: node.id, id: node.id,
useSSL: node.useSSL, useSSL: node.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
); );
final failovers = nodeService final failovers = nodeService
.failoverNodesFor(currency: coin) .failoverNodesFor(currency: coin)
@ -152,6 +166,8 @@ class NotificationsService extends ChangeNotifier {
name: e.name, name: e.name,
id: e.id, id: e.id,
useSSL: e.useSSL, useSSL: e.useSSL,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
), ),
) )
.toList(); .toList();

View file

@ -5,7 +5,6 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:on_chain/ada/ada.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 'package:socks5_proxy/socks.dart';
import '../networking/http.dart'; import '../networking/http.dart';
@ -25,6 +24,7 @@ import 'test_epic_box_connection.dart';
import 'test_eth_node_connection.dart'; import 'test_eth_node_connection.dart';
import 'test_monero_node_connection.dart'; import 'test_monero_node_connection.dart';
import 'test_stellar_node_connection.dart'; import 'test_stellar_node_connection.dart';
import 'tor_plain_net_option_enum.dart';
Future<bool> _xmrHelper( Future<bool> _xmrHelper(
NodeFormData nodeFormData, NodeFormData nodeFormData,
@ -45,7 +45,6 @@ Future<bool> _xmrHelper(
final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path"; final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path";
if (proxyInfo == null && uri.host.endsWith(".onion")) { if (proxyInfo == null && uri.host.endsWith(".onion")) {
return false; return false;
} }
@ -93,6 +92,24 @@ Future<bool> testNodeConnection({
}) async { }) async {
final formData = nodeFormData; 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; bool testPassed = false;
switch (cryptoCurrency) { switch (cryptoCurrency) {
@ -111,9 +128,7 @@ Future<bool> testNodeConnection({
case CryptonoteCurrency(): case CryptonoteCurrency():
try { try {
final proxyInfo = ref final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor
.read(prefsChangeNotifierProvider)
.useTor
? ref.read(pTorService).getProxyInfo() ? ref.read(pTorService).getProxyInfo()
: null; : null;
@ -202,9 +217,7 @@ Future<bool> testNodeConnection({
"action": "version", "action": "version",
}, },
), ),
proxyInfo: ref proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
.read(prefsChangeNotifierProvider)
.useTor
? ref.read(pTorService).getProxyInfo() ? ref.read(pTorService).getProxyInfo()
: null, : null,
); );
@ -245,9 +258,7 @@ Future<bool> testNodeConnection({
case Cardano(): case Cardano():
try { try {
final client = HttpClient(); final client = HttpClient();
if (ref if (ref.read(prefsChangeNotifierProvider).useTor) {
.read(prefsChangeNotifierProvider)
.useTor) {
final proxyInfo = TorService.sharedInstance.getProxyInfo(); final proxyInfo = TorService.sharedInstance.getProxyInfo();
final proxySettings = ProxySettings( final proxySettings = ProxySettings(
proxyInfo.host, 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, name: node.name,
useSSL: node.useSSL, useSSL: node.useSSL,
id: node.id, id: node.id,
torEnabled: node.torEnabled,
clearEnabled: node.plainEnabled,
); );
} }
@ -1352,6 +1354,8 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
name: e.name, name: e.name,
id: e.id, id: e.id,
useSSL: e.useSSL, useSSL: e.useSSL,
torEnabled: e.torEnabled,
clearEnabled: e.plainEnabled,
), ),
) )
.toList(); .toList();

View file

@ -467,7 +467,22 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
final host = Uri.parse(node.host).host; final host = Uri.parse(node.host).host;
({InternetAddress host, int port})? proxy; ({InternetAddress host, int port})? proxy;
if (prefs.useTor) { 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(); 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()); _setSyncStatus(lib_monero_compat.ConnectingSyncStatus());
@ -495,6 +510,9 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
proxy == null ? null : "${proxy.host.address}:${proxy.port}", proxy == null ? null : "${proxy.host.address}:${proxy.port}",
); );
} }
libMoneroWallet?.startSyncing();
libMoneroWallet?.startListeners();
libMoneroWallet?.startAutoSaving();
_setSyncStatus(lib_monero_compat.ConnectedSyncStatus()); _setSyncStatus(lib_monero_compat.ConnectedSyncStatus());
} catch (e, s) { } catch (e, s) {
@ -1020,6 +1038,26 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
return; 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. // this acquire should be almost instant due to above check.
// Slight possibility of race but should be irrelevant // Slight possibility of race but should be irrelevant
await refreshMutex.acquire(); await refreshMutex.acquire();

View file

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

View file

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

View file

@ -27,6 +27,7 @@ import '../utilities/default_nodes.dart';
import '../utilities/enums/sync_type_enum.dart'; import '../utilities/enums/sync_type_enum.dart';
import '../utilities/test_node_connection.dart'; import '../utilities/test_node_connection.dart';
import '../utilities/text_styles.dart'; import '../utilities/text_styles.dart';
import '../utilities/tor_plain_net_option_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart'; import '../wallets/crypto_currency/crypto_currency.dart';
import 'rounded_white_container.dart'; import 'rounded_white_container.dart';
@ -256,6 +257,15 @@ class NodeOptionsSheet extends ConsumerWidget {
ref.read(secureStoreProvider), ref.read(secureStoreProvider),
); );
if (context.mounted) { 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( final canConnect = await testNodeConnection(
context: context, context: context,
nodeFormData: NodeFormData() nodeFormData: NodeFormData()
@ -266,6 +276,7 @@ class NodeOptionsSheet extends ConsumerWidget {
..port = node.port ..port = node.port
..useSSL = node.useSSL ..useSSL = node.useSSL
..isFailover = node.isFailover ..isFailover = node.isFailover
..netOption = netOption
..trusted = node.trusted, ..trusted = node.trusted,
cryptoCurrency: coin, cryptoCurrency: coin,
ref: ref, ref: ref,

View file

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

View file

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

View file

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

View file

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