diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 7543b552..69e0a773 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -73,9 +73,11 @@ public class AppServices { private static final int SERVER_PING_PERIOD_SECS = 60; private static final int PUBLIC_SERVER_RETRY_PERIOD_SECS = 3; + private static final int PRIVATE_SERVER_RETRY_PERIOD_SECS = 15; public static final int ENUMERATE_HW_PERIOD_SECS = 30; private static final int RATES_PERIOD_SECS = 5 * 60; private static final int VERSION_CHECK_PERIOD_HOURS = 24; + private static final int CONNECTION_DELAY_SECS = 2; private static final ExchangeSource DEFAULT_EXCHANGE_SOURCE = ExchangeSource.COINGECKO; private static final Currency DEFAULT_FIAT_CURRENCY = Currency.getInstance("USD"); private static final String TOR_DEFAULT_PROXY_CIRCUIT_ID = "default"; @@ -251,7 +253,7 @@ public class AppServices { private ElectrumServer.ConnectionService createConnectionService() { ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(); //Delay startup on first connection to Bitcoin Core to allow any unencrypted wallets to open first - connectionService.setDelay(Config.get().getServerType() == ServerType.BITCOIN_CORE ? Duration.seconds(2) : Duration.ZERO); + connectionService.setDelay(Config.get().getServerType() == ServerType.BITCOIN_CORE ? Duration.seconds(CONNECTION_DELAY_SECS) : Duration.ZERO); connectionService.setPeriod(Duration.seconds(SERVER_PING_PERIOD_SECS)); connectionService.setRestartOnFailure(true); EventManager.get().register(connectionService); @@ -323,6 +325,8 @@ public class AppServices { if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER) { Config.get().changePublicServer(); connectionService.setPeriod(Duration.seconds(PUBLIC_SERVER_RETRY_PERIOD_SECS)); + } else { + connectionService.setPeriod(Duration.seconds(PRIVATE_SERVER_RETRY_PERIOD_SECS)); } log.debug("Connection failed", failEvent.getSource().getException()); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/CloseableTransport.java b/src/main/java/com/sparrowwallet/sparrow/net/CloseableTransport.java new file mode 100644 index 00000000..dad899aa --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/CloseableTransport.java @@ -0,0 +1,11 @@ +package com.sparrowwallet.sparrow.net; + +import com.github.arteam.simplejsonrpc.client.Transport; + +import java.io.Closeable; + +public interface CloseableTransport extends Transport, Closeable { + void connect() throws ServerException; + boolean isConnected(); + boolean isClosed(); +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 6566378a..c8de3a5d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -47,7 +47,7 @@ public class ElectrumServer { public static final BlockTransaction UNFETCHABLE_BLOCK_TRANSACTION = new BlockTransaction(Sha256Hash.ZERO_HASH, 0, null, null, null); - private static Transport transport; + private static CloseableTransport transport; private static final Map> subscribedScriptHashes = Collections.synchronizedMap(new HashMap<>()); @@ -63,7 +63,7 @@ public class ElectrumServer { private static final Pattern RPC_WALLET_LOADING_PATTERN = Pattern.compile(".*\"(Wallet loading failed:[^\"]*)\".*"); - private static synchronized Transport getTransport() throws ServerException { + private static synchronized CloseableTransport getTransport() throws ServerException { if(transport == null) { try { String electrumServer = null; @@ -133,8 +133,8 @@ public class ElectrumServer { } public void connect() throws ServerException { - TcpTransport tcpTransport = (TcpTransport)getTransport(); - tcpTransport.connect(); + CloseableTransport closeableTransport = getTransport(); + closeableTransport.connect(); } public void ping() throws ServerException { @@ -163,12 +163,15 @@ public class ElectrumServer { } public static synchronized void closeActiveConnection() throws ServerException { + if(transport != null) { + closeConnection(transport); + transport = null; + } + } + + private static void closeConnection(Closeable closeableTransport) throws ServerException { try { - if(transport != null) { - Closeable closeableTransport = (Closeable)transport; - closeableTransport.close(); - transport = null; - } + closeableTransport.close(); } catch (IOException e) { throw new ServerException(e); } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/Protocol.java b/src/main/java/com/sparrowwallet/sparrow/net/Protocol.java index aed19ae3..a4fc1b15 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/Protocol.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/Protocol.java @@ -13,7 +13,7 @@ import java.security.cert.CertificateException; public enum Protocol { TCP { @Override - public Transport getTransport(HostAndPort server) { + public CloseableTransport getTransport(HostAndPort server) { if(isOnionAddress(server)) { return new TorTcpTransport(server); } @@ -22,24 +22,24 @@ public enum Protocol { } @Override - public Transport getTransport(HostAndPort server, File serverCert) { + public CloseableTransport getTransport(HostAndPort server, File serverCert) { return getTransport(server); } @Override - public Transport getTransport(HostAndPort server, HostAndPort proxy) { + public CloseableTransport getTransport(HostAndPort server, HostAndPort proxy) { //Avoid using a TorSocket if a proxy is specified, even if a .onion address return new TcpTransport(server, proxy); } @Override - public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) { + public CloseableTransport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) { return getTransport(server, proxy); } }, SSL { @Override - public Transport getTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public CloseableTransport getTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if(isOnionAddress(server)) { return new TorTcpOverTlsTransport(server); } @@ -48,7 +48,7 @@ public enum Protocol { } @Override - public Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public CloseableTransport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if(isOnionAddress(server)) { return new TorTcpOverTlsTransport(server, serverCert); } @@ -57,44 +57,44 @@ public enum Protocol { } @Override - public Transport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public CloseableTransport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { return new ProxyTcpOverTlsTransport(server, proxy); } @Override - public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public CloseableTransport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { return new ProxyTcpOverTlsTransport(server, serverCert, proxy); } }, HTTP { @Override - public Transport getTransport(HostAndPort server) { + public CloseableTransport getTransport(HostAndPort server) { throw new UnsupportedOperationException("No transport supported for HTTP"); } @Override - public Transport getTransport(HostAndPort server, File serverCert) { + public CloseableTransport getTransport(HostAndPort server, File serverCert) { throw new UnsupportedOperationException("No transport supported for HTTP"); } @Override - public Transport getTransport(HostAndPort server, HostAndPort proxy) { + public CloseableTransport getTransport(HostAndPort server, HostAndPort proxy) { throw new UnsupportedOperationException("No transport supported for HTTP"); } @Override - public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) { + public CloseableTransport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) { throw new UnsupportedOperationException("No transport supported for HTTP"); } }; - public abstract Transport getTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; + public abstract CloseableTransport getTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; - public abstract Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; + public abstract CloseableTransport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; - public abstract Transport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; + public abstract CloseableTransport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; - public abstract Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; + public abstract CloseableTransport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; public HostAndPort getServerHostAndPort(String url) { return HostAndPort.fromString(url.substring(this.toUrlString().length())); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java b/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java index b9dabb07..451ac189 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -public class TcpTransport implements Transport, Closeable, TimeoutCounter { +public class TcpTransport implements CloseableTransport, TimeoutCounter { private static final Logger log = LoggerFactory.getLogger(TcpTransport.class); public static final int DEFAULT_PORT = 50001; @@ -46,6 +46,7 @@ public class TcpTransport implements Transport, Closeable, TimeoutCounter { private final ReentrantLock clientRequestLock = new ReentrantLock(); private boolean running = false; private volatile boolean reading = true; + private boolean closed = false; private boolean firstRead = true; private int readTimeoutIndex; private int requestIdCount = 1; @@ -223,6 +224,7 @@ public class TcpTransport implements Transport, Closeable, TimeoutCounter { public void connect() throws ServerException { try { socket = createSocket(); + log.debug("Created " + socket); socket.setSoTimeout(SOCKET_READ_TIMEOUT_MILLIS); running = true; } catch(SSLHandshakeException e) { @@ -237,18 +239,23 @@ public class TcpTransport implements Transport, Closeable, TimeoutCounter { } public boolean isConnected() { - return socket != null && running; + return socket != null && running && !closed; } protected Socket createSocket() throws IOException { return socketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT)); } + public boolean isClosed() { + return closed; + } + @Override public void close() throws IOException { if(socket != null) { socket.close(); } + closed = true; } @Override