diff --git a/lib/electrumx_rpc/electrumx_client.dart b/lib/electrumx_rpc/electrumx_client.dart index 5f824f9ad..773eae455 100644 --- a/lib/electrumx_rpc/electrumx_client.dart +++ b/lib/electrumx_rpc/electrumx_client.dart @@ -202,7 +202,7 @@ class ElectrumXClient { // ... But if the killswitch is set, then we throw an exception. throw Exception( "Tor preference and killswitch set but Tor is not enabled, not connecting to ElectrumX"); - // TODO [prio=low]: Restart Tor. Update Tor package for restart feature. + // TODO [prio=low]: Try to start Tor. } } else { // Get the proxy info from the TorService. diff --git a/lib/electrumx_rpc/subscribable_electrumx_client.dart b/lib/electrumx_rpc/subscribable_electrumx_client.dart index 1b924388f..34136a51e 100644 --- a/lib/electrumx_rpc/subscribable_electrumx_client.dart +++ b/lib/electrumx_rpc/subscribable_electrumx_client.dart @@ -69,9 +69,13 @@ class SubscribableElectrumXClient { final Mutex _torConnectingLock = Mutex(); bool _requireMutex = false; + List? failovers; + int currentFailoverIndex = -1; + SubscribableElectrumXClient({ required bool useSSL, required Prefs prefs, + required List failovers, TorService? torService, this.onConnectionStatusChanged, Duration connectionTimeout = const Duration(seconds: 5), @@ -138,11 +142,13 @@ class SubscribableElectrumXClient { factory SubscribableElectrumXClient.from({ required ElectrumXNode node, required Prefs prefs, + required List failovers, TorService? torService, }) { return SubscribableElectrumXClient( useSSL: node.useSSL, prefs: prefs, + failovers: failovers, torService: torService ?? TorService.sharedInstance, ); } @@ -161,6 +167,57 @@ class SubscribableElectrumXClient { // return client; // } + /// Check if the RPC client is connected and connect if needed. + /// + /// If Tor is enabled but not running, it will attempt to start Tor. + Future _checkRpcClient() async { + if (_prefs.useTor) { + // If we're supposed to use Tor... + if (_torService.status != TorConnectionStatus.connected) { + // ... but Tor isn't running... + if (!_prefs.torKillSwitch) { + // ... and the killswitch isn't set, then we'll just return below. + Logging.instance.log( + "Tor preference set but Tor is not enabled, killswitch not set, connecting to ElectrumX through clearnet.", + level: LogLevel.Warning, + ); + } else { + // ... but if the killswitch is set, then let's try to start Tor. + await _torService.start(); + // TODO [prio=low]: Attempt to restart Tor if needed. Update Tor package for restart feature. + + // Double-check that Tor is running. + if (_torService.status != TorConnectionStatus.connected) { + // If Tor still isn't running, then we'll throw an exception. + throw Exception("SubscribableElectrumXClient._checkRpcClient: " + "Tor preference and killswitch set but Tor not enabled and could not start, not connecting to ElectrumX."); + } + } + } + } + + // Connect if needed. + if ((!_prefs.useTor && _socket == null) || + (_prefs.useTor && _socksSocket == null)) { + if (currentFailoverIndex == -1) { + // Check if we have cached node information + if (_host == null && _port == null) { + throw Exception("SubscribableElectrumXClient._checkRpcClient: " + "No host or port provided and no cached node information."); + } + + // Connect to the server. + await connect(host: _host!, port: _port!); + } else { + // Attempt to connect to the next failover server. + await connect( + host: failovers![currentFailoverIndex].address, + port: failovers![currentFailoverIndex].port, + ); + } + } + } + /// Connect to the server. /// /// If Tor is enabled, it will attempt to connect through Tor. @@ -179,8 +236,9 @@ class SubscribableElectrumXClient { // If we're connecting to Tor, wait. if (_requireMutex) { - // Just use a dummy function that waits for the lock to be released. - await _torConnectingLock.protect(() async {}); + await _torConnectingLock.protect(() async => await _checkRpcClient()); + } else { + await _checkRpcClient(); } if (!Prefs.instance.useTor) { @@ -509,8 +567,9 @@ class SubscribableElectrumXClient { }) async { // If we're connecting to Tor, wait. if (_requireMutex) { - // Just use a dummy function that waits for the lock to be released. - await _torConnectingLock.protect(() async {}); + await _torConnectingLock.protect(() async => await _checkRpcClient()); + } else { + await _checkRpcClient(); } // Check socket is connected. @@ -574,8 +633,9 @@ class SubscribableElectrumXClient { }) async { // If we're connecting to Tor, wait. if (_requireMutex) { - // Just use a dummy function that waits for the lock to be released. - await _torConnectingLock.protect(() async {}); + await _torConnectingLock.protect(() async => await _checkRpcClient()); + } else { + await _checkRpcClient(); } // Check socket is connected. @@ -728,8 +788,9 @@ class SubscribableElectrumXClient { Future ping() async { // If we're connecting to Tor, wait. if (_requireMutex) { - // Just use a dummy function that waits for the lock to be released. - await _torConnectingLock.protect(() async {}); + await _torConnectingLock.protect(() async => await _checkRpcClient()); + } else { + await _checkRpcClient(); } // Write to the socket. diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index 491acba92..740b01968 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -948,6 +948,7 @@ mixin ElectrumXInterface on Bip39HDWallet { subscribableElectrumXClient = SubscribableElectrumXClient.from( node: newNode, prefs: prefs, + failovers: failovers, ); await subscribableElectrumXClient.connect( host: newNode.address, port: newNode.port);