refactor electrumserver into net package

This commit is contained in:
Craig Raw 2020-08-10 09:38:15 +02:00
parent 7d8774c94c
commit bb635a6c68
19 changed files with 479 additions and 403 deletions

View file

@ -18,6 +18,7 @@ import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
import com.sparrowwallet.sparrow.transaction.TransactionController;
import com.sparrowwallet.sparrow.transaction.TransactionView;

View file

@ -140,13 +140,17 @@ public class TitledDescriptionPane extends TitledPane {
}
private void removeArrow() {
removeArrow(0);
}
private void removeArrow(int count) {
Platform.runLater(() -> {
Node arrow = this.lookup(".arrow");
if (arrow != null) {
arrow.setVisible(false);
arrow.setManaged(false);
} else {
removeArrow();
} else if(count < 20) {
removeArrow(count+1);
}
});
}

View file

@ -3,7 +3,7 @@ package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import java.util.List;

View file

@ -0,0 +1,18 @@
package com.sparrowwallet.sparrow.net;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.BlockHeader;
class BlockHeaderTip {
public int height;
public String hex;
public BlockHeader getBlockHeader() {
if(hex == null) {
return null;
}
byte[] blockHeaderBytes = Utils.hexToBytes(hex);
return new BlockHeader(blockHeaderBytes);
}
}

View file

@ -1,47 +1,27 @@
package com.sparrowwallet.sparrow.io;
package com.sparrowwallet.sparrow.net;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.github.arteam.simplejsonrpc.client.*;
import com.github.arteam.simplejsonrpc.client.builder.BatchRequestBuilder;
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcBatchException;
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
import com.github.arteam.simplejsonrpc.server.JsonRpcServer;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.ConnectionEvent;
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
import com.sparrowwallet.sparrow.event.NewBlockEvent;
import com.sparrowwallet.sparrow.event.WalletNodeHistoryChangedEvent;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.ServerException;
import com.sparrowwallet.sparrow.wallet.SendController;
import javafx.application.Platform;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.SocketFactory;
import javax.net.ssl.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
public class ElectrumServer {
@ -630,283 +610,8 @@ public class ElectrumServer {
return Utils.bytesToHex(reversed);
}
private static class ScriptHashTx {
public static final ScriptHashTx ERROR_TX = new ScriptHashTx() {
@Override
public BlockTransactionHash getBlockchainTransactionHash() {
return UNFETCHABLE_BLOCK_TRANSACTION;
}
};
public int height;
public String tx_hash;
public long fee;
public BlockTransactionHash getBlockchainTransactionHash() {
Sha256Hash hash = Sha256Hash.wrap(tx_hash);
return new BlockTransaction(hash, height, null, fee, null);
}
@Override
public String toString() {
return "ScriptHashTx{height=" + height + ", tx_hash='" + tx_hash + '\'' + ", fee=" + fee + '}';
}
}
private static class BlockHeaderTip {
public int height;
public String hex;
public BlockHeader getBlockHeader() {
if(hex == null) {
return null;
}
byte[] blockHeaderBytes = Utils.hexToBytes(hex);
return new BlockHeader(blockHeaderBytes);
}
}
@JsonIgnoreProperties(ignoreUnknown=true)
private static class VerboseTransaction {
public String blockhash;
public long blocktime;
public int confirmations;
public String hash;
public String hex;
public int locktime;
public long size;
public String txid;
public int version;
public int getHeight() {
Integer currentHeight = AppController.getCurrentBlockHeight();
if(currentHeight != null) {
return currentHeight - confirmations + 1;
}
return -1;
}
public Date getDate() {
return new Date(blocktime * 1000);
}
public BlockTransaction getBlockTransaction() {
return new BlockTransaction(Sha256Hash.wrap(txid), getHeight(), getDate(), 0L, new Transaction(Utils.hexToBytes(hex)), blockhash == null ? null : Sha256Hash.wrap(blockhash));
}
}
@JsonRpcService
public static class SubscriptionService {
@JsonRpcMethod("blockchain.headers.subscribe")
public void newBlockHeaderTip(@JsonRpcParam("header") final BlockHeaderTip header) {
Platform.runLater(() -> EventManager.get().post(new NewBlockEvent(header.height, header.getBlockHeader())));
}
@JsonRpcMethod("blockchain.scripthash.subscribe")
public void scriptHashStatusUpdated(@JsonRpcParam("scripthash") final String scriptHash, @JsonRpcParam("status") final String status) {
String oldStatus = subscribedScriptHashes.put(scriptHash, status);
if(Objects.equals(oldStatus, status)) {
log.warn("Received script hash status update, but status has not changed");
}
Platform.runLater(() -> EventManager.get().post(new WalletNodeHistoryChangedEvent(scriptHash)));
}
}
public static class TcpTransport implements Transport, Closeable {
public static final int DEFAULT_PORT = 50001;
protected final HostAndPort server;
protected final SocketFactory socketFactory;
private Socket socket;
private String response;
private final ReentrantLock clientRequestLock = new ReentrantLock();
private boolean running = false;
private boolean reading = true;
private final JsonRpcServer jsonRpcServer = new JsonRpcServer();
private final SubscriptionService subscriptionService = new SubscriptionService();
public TcpTransport(HostAndPort server) {
this.server = server;
this.socketFactory = SocketFactory.getDefault();
}
@Override
public @NotNull String pass(@NotNull String request) throws IOException {
clientRequestLock.lock();
try {
writeRequest(request);
return readResponse();
} finally {
clientRequestLock.unlock();
}
}
private void writeRequest(String request) throws IOException {
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
out.println(request);
out.flush();
}
private synchronized String readResponse() {
while(reading) {
try {
wait();
} catch (InterruptedException e) {
//Restore interrupt status and continue
Thread.currentThread().interrupt();
}
}
reading = true;
notifyAll();
return response;
}
public synchronized void readInputLoop() throws ServerException {
while(running) {
try {
String received = readInputStream();
if(received.contains("method")) {
//Handle subscription notification
jsonRpcServer.handle(received, subscriptionService);
} else {
//Handle client's response
response = received;
reading = false;
notifyAll();
wait();
}
} catch(InterruptedException e) {
//Restore interrupt status and continue
Thread.currentThread().interrupt();
} catch(IOException e) {
if(running) {
throw new ServerException(e);
}
}
}
}
protected String readInputStream() throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String response = in.readLine();
if(response == null) {
throw new IOException("Could not connect to server at " + Config.get().getElectrumServer());
}
return response;
}
public void connect() throws ServerException {
try {
socket = createSocket();
running = true;
} catch (IOException e) {
throw new ServerException(e);
}
}
public boolean isConnected() {
return socket != null && running;
}
protected Socket createSocket() throws IOException {
return socketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT));
}
@Override
public void close() throws IOException {
if(socket != null) {
running = false;
socket.close();
}
}
}
public static class TcpOverTlsTransport extends TcpTransport {
public static final int DEFAULT_PORT = 50002;
protected final SSLSocketFactory sslSocketFactory;
public TcpOverTlsTransport(HostAndPort server) throws NoSuchAlgorithmException, KeyManagementException {
super(server);
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
this.sslSocketFactory = sslContext.getSocketFactory();
}
public TcpOverTlsTransport(HostAndPort server, File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server);
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("electrumx", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
sslSocketFactory = sslContext.getSocketFactory();
}
protected Socket createSocket() throws IOException {
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT));
sslSocket.startHandshake();
return sslSocket;
}
}
public static class ProxyTcpOverTlsTransport extends TcpOverTlsTransport {
public static final int DEFAULT_PROXY_PORT = 1080;
private final HostAndPort proxy;
public ProxyTcpOverTlsTransport(HostAndPort server, HostAndPort proxy) throws KeyManagementException, NoSuchAlgorithmException {
super(server);
this.proxy = proxy;
}
public ProxyTcpOverTlsTransport(HostAndPort server, File crtFile, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server, crtFile);
this.proxy = proxy;
}
@Override
protected Socket createSocket() throws IOException {
InetSocketAddress proxyAddr = new InetSocketAddress(proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT));
Socket underlying = new Socket(new Proxy(Proxy.Type.SOCKS, proxyAddr));
underlying.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(DEFAULT_PORT)));
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(underlying, proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT), true);
sslSocket.startHandshake();
return sslSocket;
}
static Map<String, String> getSubscribedScriptHashes() {
return subscribedScriptHashes;
}
public static class ServerVersionService extends Service<List<String>> {
@ -1180,88 +885,4 @@ public class ElectrumServer {
};
}
}
public enum Protocol {
TCP {
@Override
public Transport getTransport(HostAndPort server) {
return new TcpTransport(server);
}
@Override
public Transport getTransport(HostAndPort server, File serverCert) {
return new TcpTransport(server);
}
@Override
public Transport getTransport(HostAndPort server, HostAndPort proxy) {
throw new UnsupportedOperationException("TCP protocol does not support proxying");
}
@Override
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) {
throw new UnsupportedOperationException("TCP protocol does not support proxying");
}
},
SSL{
@Override
public Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException {
return new TcpOverTlsTransport(server);
}
@Override
public Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return new TcpOverTlsTransport(server, serverCert);
}
@Override
public Transport getTransport(HostAndPort server, HostAndPort proxy) throws NoSuchAlgorithmException, KeyManagementException {
return new ProxyTcpOverTlsTransport(server, proxy);
}
@Override
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return new ProxyTcpOverTlsTransport(server, serverCert, proxy);
}
};
public abstract Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException;
public abstract Transport 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 Transport 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()));
}
public String toUrlString() {
return toString().toLowerCase() + "://";
}
public String toUrlString(String host) {
return toUrlString(HostAndPort.fromHost(host));
}
public String toUrlString(String host, int port) {
return toUrlString(HostAndPort.fromParts(host, port));
}
public String toUrlString(HostAndPort hostAndPort) {
return toUrlString() + hostAndPort.toString();
}
public static Protocol getProtocol(String url) {
if(url.startsWith("tcp://")) {
return TCP;
}
if(url.startsWith("ssl://")) {
return SSL;
}
return null;
}
}
}

View file

@ -0,0 +1,95 @@
package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.Transport;
import com.google.common.net.HostAndPort;
import java.io.File;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
public enum Protocol {
TCP {
@Override
public Transport getTransport(HostAndPort server) {
return new TcpTransport(server);
}
@Override
public Transport getTransport(HostAndPort server, File serverCert) {
return new TcpTransport(server);
}
@Override
public Transport getTransport(HostAndPort server, HostAndPort proxy) {
throw new UnsupportedOperationException("TCP protocol does not support proxying");
}
@Override
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) {
throw new UnsupportedOperationException("TCP protocol does not support proxying");
}
},
SSL {
@Override
public Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException {
return new TcpOverTlsTransport(server);
}
@Override
public Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return new TcpOverTlsTransport(server, serverCert);
}
@Override
public Transport getTransport(HostAndPort server, HostAndPort proxy) throws NoSuchAlgorithmException, KeyManagementException {
return new ProxyTcpOverTlsTransport(server, proxy);
}
@Override
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return new ProxyTcpOverTlsTransport(server, serverCert, proxy);
}
};
public abstract Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException;
public abstract Transport 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 Transport 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()));
}
public String toUrlString() {
return toString().toLowerCase() + "://";
}
public String toUrlString(String host) {
return toUrlString(HostAndPort.fromHost(host));
}
public String toUrlString(String host, int port) {
return toUrlString(HostAndPort.fromParts(host, port));
}
public String toUrlString(HostAndPort hostAndPort) {
return toUrlString() + hostAndPort.toString();
}
public static Protocol getProtocol(String url) {
if(url.startsWith("tcp://")) {
return TCP;
}
if(url.startsWith("ssl://")) {
return SSL;
}
return null;
}
}

View file

@ -0,0 +1,41 @@
package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import javax.net.ssl.SSLSocket;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
public class ProxyTcpOverTlsTransport extends TcpOverTlsTransport {
public static final int DEFAULT_PROXY_PORT = 1080;
private final HostAndPort proxy;
public ProxyTcpOverTlsTransport(HostAndPort server, HostAndPort proxy) throws KeyManagementException, NoSuchAlgorithmException {
super(server);
this.proxy = proxy;
}
public ProxyTcpOverTlsTransport(HostAndPort server, File crtFile, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server, crtFile);
this.proxy = proxy;
}
@Override
protected Socket createSocket() throws IOException {
InetSocketAddress proxyAddr = new InetSocketAddress(proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT));
Socket underlying = new Socket(new Proxy(Proxy.Type.SOCKS, proxyAddr));
underlying.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(DEFAULT_PORT)));
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(underlying, proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT), true);
sslSocket.startHandshake();
return sslSocket;
}
}

View file

@ -0,0 +1,28 @@
package com.sparrowwallet.sparrow.net;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
class ScriptHashTx {
public static final ScriptHashTx ERROR_TX = new ScriptHashTx() {
@Override
public BlockTransactionHash getBlockchainTransactionHash() {
return ElectrumServer.UNFETCHABLE_BLOCK_TRANSACTION;
}
};
public int height;
public String tx_hash;
public long fee;
public BlockTransactionHash getBlockchainTransactionHash() {
Sha256Hash hash = Sha256Hash.wrap(tx_hash);
return new BlockTransaction(hash, height, null, fee, null);
}
@Override
public String toString() {
return "ScriptHashTx{height=" + height + ", tx_hash='" + tx_hash + '\'' + ", fee=" + fee + '}';
}
}

View file

@ -0,0 +1,33 @@
package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.NewBlockEvent;
import com.sparrowwallet.sparrow.event.WalletNodeHistoryChangedEvent;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
@JsonRpcService
public class SubscriptionService {
private static final Logger log = LoggerFactory.getLogger(SubscriptionService.class);
@JsonRpcMethod("blockchain.headers.subscribe")
public void newBlockHeaderTip(@JsonRpcParam("header") final BlockHeaderTip header) {
Platform.runLater(() -> EventManager.get().post(new NewBlockEvent(header.height, header.getBlockHeader())));
}
@JsonRpcMethod("blockchain.scripthash.subscribe")
public void scriptHashStatusUpdated(@JsonRpcParam("scripthash") final String scriptHash, @JsonRpcParam("status") final String status) {
String oldStatus = ElectrumServer.getSubscribedScriptHashes().put(scriptHash, status);
if(Objects.equals(oldStatus, status)) {
log.warn("Received script hash status update, but status has not changed");
}
Platform.runLater(() -> EventManager.get().post(new WalletNodeHistoryChangedEvent(scriptHash)));
}
}

View file

@ -0,0 +1,68 @@
package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class TcpOverTlsTransport extends TcpTransport {
public static final int DEFAULT_PORT = 50002;
protected final SSLSocketFactory sslSocketFactory;
public TcpOverTlsTransport(HostAndPort server) throws NoSuchAlgorithmException, KeyManagementException {
super(server);
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
this.sslSocketFactory = sslContext.getSocketFactory();
}
public TcpOverTlsTransport(HostAndPort server, File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server);
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("electrumx", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
sslSocketFactory = sslContext.getSocketFactory();
}
protected Socket createSocket() throws IOException {
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT));
sslSocket.startHandshake();
return sslSocket;
}
}

View file

@ -0,0 +1,130 @@
package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.Transport;
import com.github.arteam.simplejsonrpc.server.JsonRpcServer;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.ServerException;
import org.jetbrains.annotations.NotNull;
import javax.net.SocketFactory;
import java.io.*;
import java.net.Socket;
import java.util.concurrent.locks.ReentrantLock;
public class TcpTransport implements Transport, Closeable {
public static final int DEFAULT_PORT = 50001;
protected final HostAndPort server;
protected final SocketFactory socketFactory;
private Socket socket;
private String response;
private final ReentrantLock clientRequestLock = new ReentrantLock();
private boolean running = false;
private boolean reading = true;
private final JsonRpcServer jsonRpcServer = new JsonRpcServer();
private final SubscriptionService subscriptionService = new SubscriptionService();
public TcpTransport(HostAndPort server) {
this.server = server;
this.socketFactory = SocketFactory.getDefault();
}
@Override
public @NotNull String pass(@NotNull String request) throws IOException {
clientRequestLock.lock();
try {
writeRequest(request);
return readResponse();
} finally {
clientRequestLock.unlock();
}
}
private void writeRequest(String request) throws IOException {
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
out.println(request);
out.flush();
}
private synchronized String readResponse() {
while(reading) {
try {
wait();
} catch(InterruptedException e) {
//Restore interrupt status and continue
Thread.currentThread().interrupt();
}
}
reading = true;
notifyAll();
return response;
}
public synchronized void readInputLoop() throws ServerException {
while(running) {
try {
String received = readInputStream();
if(received.contains("method")) {
//Handle subscription notification
jsonRpcServer.handle(received, subscriptionService);
} else {
//Handle client's response
response = received;
reading = false;
notifyAll();
wait();
}
} catch(InterruptedException e) {
//Restore interrupt status and continue
Thread.currentThread().interrupt();
} catch(IOException e) {
if(running) {
throw new ServerException(e);
}
}
}
}
protected String readInputStream() throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String response = in.readLine();
if(response == null) {
throw new IOException("Could not connect to server at " + Config.get().getElectrumServer());
}
return response;
}
public void connect() throws ServerException {
try {
socket = createSocket();
running = true;
} catch(IOException e) {
throw new ServerException(e);
}
}
public boolean isConnected() {
return socket != null && running;
}
protected Socket createSocket() throws IOException {
return socketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT));
}
@Override
public void close() throws IOException {
if(socket != null) {
running = false;
socket.close();
}
}
}

View file

@ -0,0 +1,40 @@
package com.sparrowwallet.sparrow.net;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.sparrow.AppController;
import java.util.Date;
@JsonIgnoreProperties(ignoreUnknown = true)
class VerboseTransaction {
public String blockhash;
public long blocktime;
public int confirmations;
public String hash;
public String hex;
public int locktime;
public long size;
public String txid;
public int version;
public int getHeight() {
Integer currentHeight = AppController.getCurrentBlockHeight();
if(currentHeight != null) {
return currentHeight - confirmations + 1;
}
return -1;
}
public Date getDate() {
return new Date(blocktime * 1000);
}
public BlockTransaction getBlockTransaction() {
return new BlockTransaction(Sha256Hash.wrap(txid), getHeight(), getDate(), 0L, new Transaction(Utils.hexToBytes(hex)), blockhash == null ? null : Sha256Hash.wrap(blockhash));
}
}

View file

@ -6,7 +6,8 @@ import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
import com.sparrowwallet.sparrow.event.ConnectionEvent;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.net.Protocol;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.WorkerStateEvent;
@ -154,10 +155,10 @@ public class ServerPreferencesController extends PreferencesDetailController {
String electrumServer = config.getElectrumServer();
if(electrumServer != null) {
ElectrumServer.Protocol protocol = ElectrumServer.Protocol.getProtocol(electrumServer);
Protocol protocol = Protocol.getProtocol(electrumServer);
if(protocol != null) {
boolean ssl = protocol.equals(ElectrumServer.Protocol.SSL);
boolean ssl = protocol.equals(Protocol.SSL);
useSsl.setSelected(ssl);
certificate.setDisable(!ssl);
certificateSelect.setDisable(!ssl);
@ -270,8 +271,8 @@ public class ServerPreferencesController extends PreferencesDetailController {
};
}
private ElectrumServer.Protocol getProtocol() {
return (useSsl.isSelected() ? ElectrumServer.Protocol.SSL : ElectrumServer.Protocol.TCP);
private Protocol getProtocol() {
return (useSsl.isSelected() ? Protocol.SSL : Protocol.TCP);
}
private String getHost(String text) {

View file

@ -12,9 +12,8 @@ import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
import com.sparrowwallet.sparrow.io.Device;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.wallet.TransactionEntry;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@ -647,9 +646,7 @@ public class HeadersController extends TransactionFormController implements Init
return;
}
if(signingDevices.isEmpty()) {
signingDevices = AppController.getDevices().stream().filter(device -> device.getNeedsPinSent() || device.getNeedsPassphraseSent()).collect(Collectors.toList());
}
signingDevices.addAll(AppController.getDevices().stream().filter(device -> device.getNeedsPinSent() || device.getNeedsPassphraseSent()).collect(Collectors.toList()));
DeviceSignDialog dlg = new DeviceSignDialog(signingDevices.isEmpty() ? null : signingDevices, headersForm.getPsbt());
dlg.initModality(Modality.NONE);

View file

@ -4,7 +4,7 @@ import com.sparrowwallet.drongo.protocol.TransactionInput;
import com.sparrowwallet.drongo.protocol.TransactionOutput;
import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;

View file

@ -14,12 +14,11 @@ import com.sparrowwallet.sparrow.control.ScriptArea;
import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent;
import com.sparrowwallet.sparrow.event.BlockTransactionOutputsFetchedEvent;
import com.sparrowwallet.sparrow.event.ViewTransactionEvent;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import org.fxmisc.richtext.CodeArea;
import tornadofx.control.Field;
import tornadofx.control.Fieldset;

View file

@ -1,6 +1,6 @@
package com.sparrowwallet.sparrow.transaction;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import javafx.scene.Node;
import java.io.IOException;

View file

@ -10,7 +10,7 @@ import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.TransactionHexArea;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

View file

@ -7,7 +7,7 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.io.Storage;
import javafx.application.Platform;
import org.slf4j.Logger;