switch from httpurlconnection to jetty http client to avoid spurious dns query

This commit is contained in:
Craig Raw 2023-11-10 19:16:03 +02:00
parent d84ade5b7d
commit c81c42a87c
16 changed files with 229 additions and 260 deletions

View file

@ -121,7 +121,7 @@ dependencies {
implementation('org.slf4j:jul-to-slf4j:1.7.30') { implementation('org.slf4j:jul-to-slf4j:1.7.30') {
exclude group: 'org.slf4j' exclude group: 'org.slf4j'
} }
implementation('com.sparrowwallet.nightjar:nightjar:0.2.37') implementation('com.sparrowwallet.nightjar:nightjar:0.2.38')
implementation('io.reactivex.rxjava2:rxjava:2.2.15') implementation('io.reactivex.rxjava2:rxjava:2.2.15')
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2') implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
implementation('org.apache.commons:commons-lang3:3.7') implementation('org.apache.commons:commons-lang3:3.7')
@ -508,7 +508,7 @@ extraJavaModuleInfo {
exports('co.nstant.in.cbor.model') exports('co.nstant.in.cbor.model')
exports('co.nstant.in.cbor.builder') exports('co.nstant.in.cbor.builder')
} }
module('nightjar-0.2.37.jar', 'com.sparrowwallet.nightjar', '0.2.37') { module('nightjar-0.2.38.jar', 'com.sparrowwallet.nightjar', '0.2.38') {
requires('com.google.common') requires('com.google.common')
requires('net.sourceforge.streamsupport') requires('net.sourceforge.streamsupport')
requires('org.slf4j') requires('org.slf4j')

View file

@ -24,7 +24,6 @@ import com.sparrowwallet.sparrow.control.TrayManager;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.net.*;
import com.sparrowwallet.sparrow.paynym.PayNymService;
import com.sparrowwallet.sparrow.soroban.SorobanServices; import com.sparrowwallet.sparrow.soroban.SorobanServices;
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices; import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
import javafx.application.Application; import javafx.application.Application;
@ -61,7 +60,6 @@ import java.awt.event.KeyEvent;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.*;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@ -96,7 +94,7 @@ public class AppServices {
private InteractionServices interactionServices; private InteractionServices interactionServices;
private static PayNymService payNymService; private static HttpClientService httpClientService;
private final Application application; private final Application application;
@ -247,8 +245,8 @@ public class AppServices {
versionCheckService.cancel(); versionCheckService.cancel();
} }
if(payNymService != null) { if(httpClientService != null) {
PayNymService.ShutdownService shutdownService = new PayNymService.ShutdownService(payNymService); HttpClientService.ShutdownService shutdownService = new HttpClientService.ShutdownService(httpClientService);
shutdownService.start(); shutdownService.start();
} }
@ -513,18 +511,18 @@ public class AppServices {
return get().interactionServices; return get().interactionServices;
} }
public static PayNymService getPayNymService() { public static HttpClientService getHttpClientService() {
if(payNymService == null) { if(httpClientService == null) {
HostAndPort torProxy = getTorProxy(); HostAndPort torProxy = getTorProxy();
payNymService = new PayNymService(torProxy); httpClientService = new HttpClientService(torProxy);
} else { } else {
HostAndPort torProxy = getTorProxy(); HostAndPort torProxy = getTorProxy();
if(!Objects.equals(payNymService.getTorProxy(), torProxy)) { if(!Objects.equals(httpClientService.getTorProxy(), torProxy)) {
payNymService.setTorProxy(getTorProxy()); httpClientService.setTorProxy(getTorProxy());
} }
} }
return payNymService; return httpClientService;
} }
public static HostAndPort getTorProxy() { public static HostAndPort getTorProxy() {

View file

@ -1,19 +1,17 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.nightjar.http.JavaHttpException;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.List; import java.util.List;
@ -30,7 +28,7 @@ public enum BroadcastSource {
return List.of(Network.MAINNET, Network.TESTNET); return List.of(Network.MAINNET, Network.TESTNET);
} }
protected URL getURL(Proxy proxy) throws MalformedURLException { protected URL getURL(HostAndPort proxy) throws MalformedURLException {
if(Network.get() == Network.MAINNET) { if(Network.get() == Network.MAINNET) {
return new URL(getBaseUrl(proxy) + "/api/tx"); return new URL(getBaseUrl(proxy) + "/api/tx");
} else if(Network.get() == Network.TESTNET) { } else if(Network.get() == Network.TESTNET) {
@ -51,7 +49,7 @@ public enum BroadcastSource {
return List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET); return List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET);
} }
protected URL getURL(Proxy proxy) throws MalformedURLException { protected URL getURL(HostAndPort proxy) throws MalformedURLException {
if(Network.get() == Network.MAINNET) { if(Network.get() == Network.MAINNET) {
return new URL(getBaseUrl(proxy) + "/api/tx"); return new URL(getBaseUrl(proxy) + "/api/tx");
} else if(Network.get() == Network.TESTNET) { } else if(Network.get() == Network.TESTNET) {
@ -74,7 +72,7 @@ public enum BroadcastSource {
return List.of(Network.MAINNET); return List.of(Network.MAINNET);
} }
protected URL getURL(Proxy proxy) throws MalformedURLException { protected URL getURL(HostAndPort proxy) throws MalformedURLException {
if(Network.get() == Network.MAINNET) { if(Network.get() == Network.MAINNET) {
return new URL(getBaseUrl(proxy) + "/api/tx"); return new URL(getBaseUrl(proxy) + "/api/tx");
} else if(Network.get() == Network.TESTNET) { } else if(Network.get() == Network.TESTNET) {
@ -95,7 +93,7 @@ public enum BroadcastSource {
return List.of(Network.MAINNET); return List.of(Network.MAINNET);
} }
protected URL getURL(Proxy proxy) throws MalformedURLException { protected URL getURL(HostAndPort proxy) throws MalformedURLException {
if(Network.get() == Network.MAINNET) { if(Network.get() == Network.MAINNET) {
return new URL(getBaseUrl(proxy) + "/api/tx"); return new URL(getBaseUrl(proxy) + "/api/tx");
} else if(Network.get() == Network.TESTNET) { } else if(Network.get() == Network.TESTNET) {
@ -131,7 +129,7 @@ public enum BroadcastSource {
return onionUrl; return onionUrl;
} }
public String getBaseUrl(Proxy proxy) { public String getBaseUrl(HostAndPort proxy) {
return (proxy == null ? getTlsUrl() : getOnionUrl()); return (proxy == null ? getTlsUrl() : getOnionUrl());
} }
@ -139,48 +137,30 @@ public enum BroadcastSource {
public abstract List<Network> getSupportedNetworks(); public abstract List<Network> getSupportedNetworks();
protected abstract URL getURL(Proxy proxy) throws MalformedURLException; protected abstract URL getURL(HostAndPort proxy) throws MalformedURLException;
public Sha256Hash postTransactionData(String data) throws BroadcastException { public Sha256Hash postTransactionData(String data) throws BroadcastException {
//If a Tor proxy is configured, ensure we use a new circuit by configuring a random proxy password //If a Tor proxy is configured, ensure we use a new circuit by configuring a random proxy password
Proxy proxy = AppServices.getProxy(Integer.toString(secureRandom.nextInt())); HttpClientService httpClientService = AppServices.getHttpClientService();
httpClientService.changeIdentity();
try { try {
URL url = getURL(proxy); URL url = getURL(httpClientService.getTorProxy());
if(log.isInfoEnabled()) { if(log.isInfoEnabled()) {
log.info("Broadcasting transaction to " + url); log.info("Broadcasting transaction to " + url);
} }
HttpURLConnection connection = proxy == null ? (HttpURLConnection)url.openConnection() : (HttpURLConnection)url.openConnection(proxy); String response = httpClientService.postString(url.toString(), null, "text/plain", data);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "text/plain");
connection.setDoOutput(true);
try(OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(data);
writer.flush();
}
StringBuilder response = new StringBuilder();
try(BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
String responseLine;
while((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
}
int statusCode = connection.getResponseCode();
if(statusCode < 200 || statusCode >= 300) {
throw new BroadcastException("Could not broadcast transaction, server returned " + statusCode + ": " + response);
}
try { try {
return Sha256Hash.wrap(response.toString().trim()); return Sha256Hash.wrap(response.trim());
} catch(Exception e) { } catch(Exception e) {
throw new BroadcastException("Could not retrieve txid from broadcast, server returned " + statusCode + ": " + response); throw new BroadcastException("Could not retrieve txid from broadcast, server returned: " + response);
} }
} catch(IOException e) { } catch(JavaHttpException e) {
throw new BroadcastException("Could not broadcast transaction, server returned " + e.getStatusCode() + ": " + e.getResponseBody());
} catch(Exception e) {
log.error("Could not post transaction via " + getName(), e); log.error("Could not post transaction via " + getName(), e);
throw new BroadcastException("Could not broadcast transaction via " + getName(), e); throw new BroadcastException("Could not broadcast transaction via " + getName(), e);
} }

View file

@ -18,6 +18,7 @@ import com.sparrowwallet.sparrow.io.Server;
import com.sparrowwallet.sparrow.net.cormorant.Cormorant; import com.sparrowwallet.sparrow.net.cormorant.Cormorant;
import com.sparrowwallet.sparrow.net.cormorant.bitcoind.CormorantBitcoindException; import com.sparrowwallet.sparrow.net.cormorant.bitcoind.CormorantBitcoindException;
import com.sparrowwallet.sparrow.paynym.PayNym; import com.sparrowwallet.sparrow.paynym.PayNym;
import com.sparrowwallet.sparrow.paynym.PayNymService;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
@ -1882,7 +1883,7 @@ public class ElectrumServer {
private PayNym getPayNym(PaymentCode paymentCode) { private PayNym getPayNym(PaymentCode paymentCode) {
try { try {
return AppServices.getPayNymService().getPayNym(paymentCode.toString()).blockingFirst(); return PayNymService.getPayNym(paymentCode.toString()).blockingFirst();
} catch(Exception e) { } catch(Exception e) {
//ignore //ignore
} }

View file

@ -1,6 +1,5 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.gson.Gson;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.event.ExchangeRatesUpdatedEvent; import com.sparrowwallet.sparrow.event.ExchangeRatesUpdatedEvent;
import javafx.concurrent.ScheduledService; import javafx.concurrent.ScheduledService;
@ -9,12 +8,6 @@ import javafx.concurrent.Task;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -50,15 +43,14 @@ public enum ExchangeSource {
private CoinbaseRates getRates() { private CoinbaseRates getRates() {
String url = "https://api.coinbase.com/v2/exchange-rates?currency=BTC"; String url = "https://api.coinbase.com/v2/exchange-rates?currency=BTC";
Proxy proxy = AppServices.getProxy();
if(log.isInfoEnabled()) { if(log.isInfoEnabled()) {
log.info("Requesting exchange rates from " + url); log.info("Requesting exchange rates from " + url);
} }
try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream()); Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { HttpClientService httpClientService = AppServices.getHttpClientService();
Gson gson = new Gson(); try {
return gson.fromJson(reader, CoinbaseRates.class); return httpClientService.requestJson(url, CoinbaseRates.class, null);
} catch (Exception e) { } catch (Exception e) {
if(log.isDebugEnabled()) { if(log.isDebugEnabled()) {
log.warn("Error retrieving currency rates", e); log.warn("Error retrieving currency rates", e);
@ -89,15 +81,14 @@ public enum ExchangeSource {
private CoinGeckoRates getRates() { private CoinGeckoRates getRates() {
String url = "https://api.coingecko.com/api/v3/exchange_rates"; String url = "https://api.coingecko.com/api/v3/exchange_rates";
Proxy proxy = AppServices.getProxy();
if(log.isInfoEnabled()) { if(log.isInfoEnabled()) {
log.info("Requesting exchange rates from " + url); log.info("Requesting exchange rates from " + url);
} }
try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream()); Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { HttpClientService httpClientService = AppServices.getHttpClientService();
Gson gson = new Gson(); try {
return gson.fromJson(reader, CoinGeckoRates.class); return httpClientService.requestJson(url, CoinGeckoRates.class, null);
} catch(Exception e) { } catch(Exception e) {
if(log.isDebugEnabled()) { if(log.isDebugEnabled()) {
log.warn("Error retrieving currency rates", e); log.warn("Error retrieving currency rates", e);
@ -176,22 +167,22 @@ public enum ExchangeSource {
} }
private static class CoinbaseRates { private static class CoinbaseRates {
CoinbaseData data; public CoinbaseData data = new CoinbaseData();
} }
private static class CoinbaseData { private static class CoinbaseData {
String currency; public String currency;
Map<String, Double> rates; public Map<String, Double> rates = new LinkedHashMap<>();
} }
private static class CoinGeckoRates { private static class CoinGeckoRates {
Map<String, CoinGeckoRate> rates = new LinkedHashMap<>(); public Map<String, CoinGeckoRate> rates = new LinkedHashMap<>();
} }
private static class CoinGeckoRate { private static class CoinGeckoRate {
String name; public String name;
String unit; public String unit;
Double value; public Double value;
String type; public String type;
} }
} }

View file

@ -1,16 +1,9 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.gson.Gson;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -66,16 +59,14 @@ public enum FeeRatesSource {
} }
private static Map<Integer, Double> getThreeTierFeeRates(Map<Integer, Double> defaultblockTargetFeeRates, String url) { private static Map<Integer, Double> getThreeTierFeeRates(Map<Integer, Double> defaultblockTargetFeeRates, String url) {
Proxy proxy = AppServices.getProxy();
if(log.isInfoEnabled()) { if(log.isInfoEnabled()) {
log.info("Requesting fee rates from " + url); log.info("Requesting fee rates from " + url);
} }
Map<Integer, Double> blockTargetFeeRates = new LinkedHashMap<>(); Map<Integer, Double> blockTargetFeeRates = new LinkedHashMap<>();
try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream()); Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { HttpClientService httpClientService = AppServices.getHttpClientService();
Gson gson = new Gson(); try {
ThreeTierRates threeTierRates = gson.fromJson(reader, ThreeTierRates.class); ThreeTierRates threeTierRates = httpClientService.requestJson(url, ThreeTierRates.class, null);
Double lastRate = null; Double lastRate = null;
for(Integer blockTarget : defaultblockTargetFeeRates.keySet()) { for(Integer blockTarget : defaultblockTargetFeeRates.keySet()) {
if(blockTarget < BLOCKS_IN_HALF_HOUR) { if(blockTarget < BLOCKS_IN_HALF_HOUR) {
@ -116,9 +107,9 @@ public enum FeeRatesSource {
} }
private static class ThreeTierRates { private static class ThreeTierRates {
Double fastestFee; public Double fastestFee;
Double halfHourFee; public Double halfHourFee;
Double hourFee; public Double hourFee;
Double minimumFee; public Double minimumFee;
} }
} }

View file

@ -0,0 +1,74 @@
package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.samourai.http.client.HttpUsage;
import com.samourai.http.client.IHttpClient;
import com.sparrowwallet.nightjar.http.JavaHttpClientService;
import io.reactivex.Observable;
import java8.util.Optional;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import java.util.Map;
public class HttpClientService {
private final JavaHttpClientService httpClientService;
public HttpClientService(HostAndPort torProxy) {
this.httpClientService = new JavaHttpClientService(torProxy, 120000);
}
public <T> T requestJson(String url, Class<T> responseType, Map<String, String> headers) throws Exception {
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
return httpClient.getJson(url, responseType, headers);
}
public <T> Observable<Optional<T>> postJson(String url, Class<T> responseType, Map<String, String> headers, Object body) {
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
return httpClient.postJson(url, responseType, headers, body);
}
public String postString(String url, Map<String, String> headers, String contentType, String content) throws Exception {
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
return httpClient.postString(url, headers, contentType, content);
}
public void changeIdentity() {
HostAndPort torProxy = getTorProxy();
if(torProxy != null) {
TorUtils.changeIdentity(torProxy);
}
}
public HostAndPort getTorProxy() {
return httpClientService.getTorProxy();
}
public void setTorProxy(HostAndPort torProxy) {
//Ensure all http clients are shutdown first
httpClientService.shutdown();
httpClientService.setTorProxy(torProxy);
}
public void shutdown() {
httpClientService.shutdown();
}
public static class ShutdownService extends Service<Boolean> {
private final HttpClientService httpClientService;
public ShutdownService(HttpClientService httpClientService) {
this.httpClientService = httpClientService;
}
@Override
protected Task<Boolean> createTask() {
return new Task<>() {
protected Boolean call() throws Exception {
httpClientService.shutdown();
return true;
}
};
}
}
}

View file

@ -0,0 +1,37 @@
package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.AppServices;
import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket;
public class TorUtils {
private static final Logger log = LoggerFactory.getLogger(TorUtils.class);
public static void changeIdentity(HostAndPort proxy) {
if(AppServices.isTorRunning()) {
Tor.getDefault().getTorManager().signal(TorControlSignal.Signal.NewNym, throwable -> {
log.warn("Failed to signal newnym");
}, successEvent -> {
log.info("Signalled newnym for new Tor circuit");
});
} else {
HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1);
try(Socket socket = new Socket(control.getHost(), control.getPort())) {
writeNewNym(socket);
} catch(Exception e) {
log.warn("Error connecting to " + control + ", no Tor ControlPort configured?");
}
}
}
private static void writeNewNym(Socket socket) throws IOException {
log.debug("Sending NEWNYM to " + socket);
socket.getOutputStream().write("AUTHENTICATE \"\"\r\n".getBytes());
socket.getOutputStream().write("SIGNAL NEWNYM\r\n".getBytes());
}
}

View file

@ -1,6 +1,5 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.gson.Gson;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.InvalidAddressException; import com.sparrowwallet.drongo.address.InvalidAddressException;
import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.crypto.ECKey;
@ -13,12 +12,7 @@ import javafx.concurrent.Task;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.Map; import java.util.Map;
@ -45,18 +39,15 @@ public class VersionCheckService extends ScheduledService<VersionUpdatedEvent> {
} }
private VersionCheck getVersionCheck() throws IOException { private VersionCheck getVersionCheck() throws IOException {
URL url = new URL(VERSION_CHECK_URL);
Proxy proxy = AppServices.getProxy();
if(log.isInfoEnabled()) { if(log.isInfoEnabled()) {
log.info("Requesting application version check from " + url); log.info("Requesting application version check from " + VERSION_CHECK_URL);
} }
HttpsURLConnection conn = (HttpsURLConnection)(proxy == null ? url.openConnection() : url.openConnection(proxy)); HttpClientService httpClientService = AppServices.getHttpClientService();
try {
try(InputStreamReader reader = new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)) { return httpClientService.requestJson(VERSION_CHECK_URL, VersionCheck.class, null);
Gson gson = new Gson(); } catch(Exception e) {
return gson.fromJson(reader, VersionCheck.class); throw new IOException(e);
} }
} }

View file

@ -14,7 +14,9 @@ import com.sparrowwallet.drongo.psbt.PSBTParseException;
import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.uri.BitcoinURI;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.nightjar.http.JavaHttpException;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.net.HttpClientService;
import com.sparrowwallet.sparrow.net.Protocol; import com.sparrowwallet.sparrow.net.Protocol;
import javafx.concurrent.Service; import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
@ -22,11 +24,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
public class Payjoin { public class Payjoin {
@ -78,42 +77,21 @@ public class Payjoin {
URI finalUri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), uri.getQuery() == null ? appendQuery : uri.getQuery() + "&" + appendQuery, uri.getFragment()); URI finalUri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), uri.getQuery() == null ? appendQuery : uri.getQuery() + "&" + appendQuery, uri.getFragment());
log.info("Sending PSBT to " + finalUri.toURL()); log.info("Sending PSBT to " + finalUri.toURL());
Proxy proxy = AppServices.getProxy(); HttpClientService httpClientService = AppServices.getHttpClientService();
if(httpClientService.getTorProxy() == null && Protocol.isOnionHost(finalUri.getHost())) {
if(proxy == null && Protocol.isOnionHost(finalUri.getHost())) {
throw new PayjoinReceiverException("Configure a Tor proxy to get a payjoin transaction from " + finalUri.getHost() + "."); throw new PayjoinReceiverException("Configure a Tor proxy to get a payjoin transaction from " + finalUri.getHost() + ".");
} }
HttpURLConnection connection = proxy == null ? (HttpURLConnection)finalUri.toURL().openConnection() : (HttpURLConnection)finalUri.toURL().openConnection(proxy); String response = httpClientService.postString(finalUri.toString(), null, "text/plain", base64Psbt);
connection.setRequestMethod("POST"); PSBT proposalPsbt = PSBT.fromString(response.trim());
connection.setRequestProperty("Content-Type", "text/plain");
connection.setDoOutput(true);
try(OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(base64Psbt);
writer.flush();
}
StringBuilder response = new StringBuilder();
try(BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
String responseLine;
while((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
}
int statusCode = connection.getResponseCode();
if(statusCode != 200) {
Gson gson = new Gson();
PayjoinReceiverError payjoinReceiverError = gson.fromJson(response.toString(), PayjoinReceiverError.class);
log.warn("Payjoin receiver returned an error of " + payjoinReceiverError.getErrorCode() + " (" + payjoinReceiverError.getMessage() + ")");
throw new PayjoinReceiverException(payjoinReceiverError.getSafeMessage());
}
PSBT proposalPsbt = PSBT.fromString(response.toString().trim());
checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution); checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution);
return proposalPsbt; return proposalPsbt;
} catch(JavaHttpException e) {
Gson gson = new Gson();
PayjoinReceiverError payjoinReceiverError = gson.fromJson(e.getResponseBody(), PayjoinReceiverError.class);
log.warn("Payjoin receiver returned an error of " + payjoinReceiverError.getErrorCode() + " (" + payjoinReceiverError.getMessage() + ")");
throw new PayjoinReceiverException(payjoinReceiverError.getSafeMessage());
} catch(URISyntaxException e) { } catch(URISyntaxException e) {
log.error("Invalid payjoin receiver URI", e); log.error("Invalid payjoin receiver URI", e);
throw new PayjoinReceiverException("Invalid payjoin receiver URI", e); throw new PayjoinReceiverException("Invalid payjoin receiver URI", e);
@ -126,6 +104,9 @@ public class Payjoin {
} catch(PSBTParseException e) { } catch(PSBTParseException e) {
log.error("Error parsing received PSBT", e); log.error("Error parsing received PSBT", e);
throw new PayjoinReceiverException("Payjoin receiver returned invalid PSBT", e); throw new PayjoinReceiverException("Payjoin receiver returned invalid PSBT", e);
} catch(Exception e) {
log.error("Payjoin error", e);
throw new PayjoinReceiverException("Payjoin error", e);
} }
} }

View file

@ -179,7 +179,7 @@ public class PayNymController {
} }
retrievePayNymProgress.setVisible(true); retrievePayNymProgress.setVisible(true);
AppServices.getPayNymService().getPayNym(getMasterWallet().getPaymentCode().toString()).subscribe(payNym -> { PayNymService.getPayNym(getMasterWallet().getPaymentCode().toString()).subscribe(payNym -> {
retrievePayNymProgress.setVisible(false); retrievePayNymProgress.setVisible(false);
walletPayNym = payNym; walletPayNym = payNym;
searchPayNyms.setDisable(false); searchPayNyms.setDisable(false);
@ -229,7 +229,7 @@ public class PayNymController {
followingList.setItems(FXCollections.observableList(new ArrayList<>())); followingList.setItems(FXCollections.observableList(new ArrayList<>()));
findPayNym.setVisible(true); findPayNym.setVisible(true);
AppServices.getPayNymService().getPayNym(nymIdentifier, true).subscribe(searchedPayNym -> { PayNymService.getPayNym(nymIdentifier, true).subscribe(searchedPayNym -> {
findPayNym.setVisible(false); findPayNym.setVisible(false);
List<PayNym> searchList = new ArrayList<>(); List<PayNym> searchList = new ArrayList<>();
searchList.add(searchedPayNym); searchList.add(searchedPayNym);
@ -262,15 +262,14 @@ public class PayNymController {
} }
public void retrievePayNym(ActionEvent event) { public void retrievePayNym(ActionEvent event) {
PayNymService payNymService = AppServices.getPayNymService();
Wallet masterWallet = getMasterWallet(); Wallet masterWallet = getMasterWallet();
setUsePayNym(masterWallet, true); setUsePayNym(masterWallet, true);
payNymService.createPayNym(masterWallet).subscribe(createMap -> { PayNymService.createPayNym(masterWallet).subscribe(createMap -> {
payNymName.setText((String)createMap.get("nymName")); payNymName.setText((String)createMap.get("nymName"));
payNymAvatar.setPaymentCode(masterWallet.getPaymentCode()); payNymAvatar.setPaymentCode(masterWallet.getPaymentCode());
payNymName.setVisible(true); payNymName.setVisible(true);
payNymService.claimPayNym(masterWallet, createMap, getMasterWallet().getScriptType() != ScriptType.P2PKH); PayNymService.claimPayNym(masterWallet, createMap, getMasterWallet().getScriptType() != ScriptType.P2PKH);
refresh(); refresh();
}, error -> { }, error -> {
log.error("Error retrieving PayNym", error); log.error("Error retrieving PayNym", error);
@ -282,12 +281,11 @@ public class PayNymController {
} }
public void followPayNym(PaymentCode contact) { public void followPayNym(PaymentCode contact) {
PayNymService payNymService = AppServices.getPayNymService();
Wallet masterWallet = getMasterWallet(); Wallet masterWallet = getMasterWallet();
retrievePayNymProgress.setVisible(true); retrievePayNymProgress.setVisible(true);
payNymService.getAuthToken(masterWallet, new HashMap<>()).subscribe(authToken -> { PayNymService.getAuthToken(masterWallet, new HashMap<>()).subscribe(authToken -> {
String signature = payNymService.getSignature(masterWallet, authToken); String signature = PayNymService.getSignature(masterWallet, authToken);
payNymService.followPaymentCode(contact, authToken, signature).subscribe(followMap -> { PayNymService.followPaymentCode(contact, authToken, signature).subscribe(followMap -> {
refresh(); refresh();
}, error -> { }, error -> {
retrievePayNymProgress.setVisible(false); retrievePayNymProgress.setVisible(false);

View file

@ -1,8 +1,5 @@
package com.sparrowwallet.sparrow.paynym; package com.sparrowwallet.sparrow.paynym;
import com.google.common.net.HostAndPort;
import com.samourai.http.client.HttpUsage;
import com.samourai.http.client.IHttpClient;
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException; import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException;
import com.sparrowwallet.drongo.bip47.PaymentCode; import com.sparrowwallet.drongo.bip47.PaymentCode;
import com.sparrowwallet.drongo.crypto.ChildNumber; import com.sparrowwallet.drongo.crypto.ChildNumber;
@ -10,13 +7,11 @@ import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.nightjar.http.JavaHttpClientService; import com.sparrowwallet.sparrow.AppServices;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler; import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java8.util.Optional; import java8.util.Optional;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,17 +25,15 @@ import java.util.stream.Collectors;
public class PayNymService { public class PayNymService {
private static final Logger log = LoggerFactory.getLogger(PayNymService.class); private static final Logger log = LoggerFactory.getLogger(PayNymService.class);
private final JavaHttpClientService httpClientService; private PayNymService() {
//private constructor
public PayNymService(HostAndPort torProxy) {
this.httpClientService = new JavaHttpClientService(torProxy, 120000);
} }
public Observable<Map<String, Object>> createPayNym(Wallet wallet) { public static Observable<Map<String, Object>> createPayNym(Wallet wallet) {
return createPayNym(getPaymentCode(wallet)); return createPayNym(getPaymentCode(wallet));
} }
public Observable<Map<String, Object>> createPayNym(PaymentCode paymentCode) { public static Observable<Map<String, Object>> createPayNym(PaymentCode paymentCode) {
if(paymentCode == null) { if(paymentCode == null) {
throw new IllegalStateException("Payment code is null"); throw new IllegalStateException("Payment code is null");
} }
@ -56,14 +49,13 @@ public class PayNymService {
log.info("Creating PayNym using " + url); log.info("Creating PayNym using " + url);
} }
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); return AppServices.getHttpClientService().postJson(url, Map.class, headers, body)
return httpClient.postJson(url, Map.class, headers, body)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(JavaFxScheduler.platform()) .observeOn(JavaFxScheduler.platform())
.map(Optional::get); .map(Optional::get);
} }
public Observable<Map<String, Object>> updateToken(PaymentCode paymentCode) { public static Observable<Map<String, Object>> updateToken(PaymentCode paymentCode) {
if(paymentCode == null) { if(paymentCode == null) {
throw new IllegalStateException("Payment code is null"); throw new IllegalStateException("Payment code is null");
} }
@ -79,14 +71,13 @@ public class PayNymService {
log.info("Updating PayNym token using " + url); log.info("Updating PayNym token using " + url);
} }
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); return AppServices.getHttpClientService().postJson(url, Map.class, headers, body)
return httpClient.postJson(url, Map.class, headers, body)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(JavaFxScheduler.platform()) .observeOn(JavaFxScheduler.platform())
.map(Optional::get); .map(Optional::get);
} }
public void claimPayNym(Wallet wallet, Map<String, Object> createMap, boolean segwit) { public static void claimPayNym(Wallet wallet, Map<String, Object> createMap, boolean segwit) {
if(createMap.get("claimed") == Boolean.FALSE) { if(createMap.get("claimed") == Boolean.FALSE) {
getAuthToken(wallet, createMap).subscribe(authToken -> { getAuthToken(wallet, createMap).subscribe(authToken -> {
String signature = getSignature(wallet, authToken); String signature = getSignature(wallet, authToken);
@ -116,7 +107,7 @@ public class PayNymService {
} }
} }
private Observable<Map<String, Object>> claimPayNym(String authToken, String signature) { private static Observable<Map<String, Object>> claimPayNym(String authToken, String signature) {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/json"); headers.put("content-type", "application/json");
headers.put("auth-token", authToken); headers.put("auth-token", authToken);
@ -129,14 +120,13 @@ public class PayNymService {
log.info("Claiming PayNym using " + url); log.info("Claiming PayNym using " + url);
} }
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); return AppServices.getHttpClientService().postJson(url, Map.class, headers, body)
return httpClient.postJson(url, Map.class, headers, body)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(JavaFxScheduler.platform()) .observeOn(JavaFxScheduler.platform())
.map(Optional::get); .map(Optional::get);
} }
public Observable<Map<String, Object>> addPaymentCode(PaymentCode paymentCode, String authToken, String signature, boolean segwit) { public static Observable<Map<String, Object>> addPaymentCode(PaymentCode paymentCode, String authToken, String signature, boolean segwit) {
String strPaymentCode; String strPaymentCode;
try { try {
strPaymentCode = segwit ? paymentCode.makeSamouraiPaymentCode() : paymentCode.toString(); strPaymentCode = segwit ? paymentCode.makeSamouraiPaymentCode() : paymentCode.toString();
@ -159,18 +149,17 @@ public class PayNymService {
log.info("Adding payment code to PayNym using " + url); log.info("Adding payment code to PayNym using " + url);
} }
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); return AppServices.getHttpClientService().postJson(url, Map.class, headers, body)
return httpClient.postJson(url, Map.class, headers, body)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(JavaFxScheduler.platform()) .observeOn(JavaFxScheduler.platform())
.map(Optional::get); .map(Optional::get);
} }
public Observable<Map<String, Object>> followPaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode paymentCode, String authToken, String signature) { public static Observable<Map<String, Object>> followPaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode paymentCode, String authToken, String signature) {
return followPaymentCode(PaymentCode.fromString(paymentCode.toString()), authToken, signature); return followPaymentCode(PaymentCode.fromString(paymentCode.toString()), authToken, signature);
} }
public Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) { public static Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/json"); headers.put("content-type", "application/json");
headers.put("auth-token", authToken); headers.put("auth-token", authToken);
@ -184,14 +173,13 @@ public class PayNymService {
log.info("Following payment code using " + url); log.info("Following payment code using " + url);
} }
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); return AppServices.getHttpClientService().postJson(url, Map.class, headers, body)
return httpClient.postJson(url, Map.class, headers, body)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(JavaFxScheduler.platform()) .observeOn(JavaFxScheduler.platform())
.map(Optional::get); .map(Optional::get);
} }
public Observable<Map<String, Object>> fetchPayNym(String nymIdentifier, boolean compact) { public static Observable<Map<String, Object>> fetchPayNym(String nymIdentifier, boolean compact) {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/json"); headers.put("content-type", "application/json");
@ -203,18 +191,17 @@ public class PayNymService {
log.info("Fetching PayNym using " + url); log.info("Fetching PayNym using " + url);
} }
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST); return AppServices.getHttpClientService().postJson(url, Map.class, headers, body)
return httpClient.postJson(url, Map.class, headers, body)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(JavaFxScheduler.platform()) .observeOn(JavaFxScheduler.platform())
.map(Optional::get); .map(Optional::get);
} }
public Observable<PayNym> getPayNym(String nymIdentifier) { public static Observable<PayNym> getPayNym(String nymIdentifier) {
return getPayNym(nymIdentifier, false); return getPayNym(nymIdentifier, false);
} }
public Observable<PayNym> getPayNym(String nymIdentifier, boolean compact) { public static Observable<PayNym> getPayNym(String nymIdentifier, boolean compact) {
return fetchPayNym(nymIdentifier, compact).map(nymMap -> { return fetchPayNym(nymIdentifier, compact).map(nymMap -> {
List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes"); List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes");
PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code"))); PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code")));
@ -237,7 +224,7 @@ public class PayNymService {
}); });
} }
public Observable<String> getAuthToken(Wallet wallet, Map<String, Object> map) { public static Observable<String> getAuthToken(Wallet wallet, Map<String, Object> map) {
if(map.containsKey("token")) { if(map.containsKey("token")) {
return Observable.just((String)map.get("token")); return Observable.just((String)map.get("token"));
} }
@ -245,11 +232,11 @@ public class PayNymService {
return updateToken(wallet).map(tokenMap -> (String)tokenMap.get("token")); return updateToken(wallet).map(tokenMap -> (String)tokenMap.get("token"));
} }
public Observable<Map<String, Object>> updateToken(Wallet wallet) { public static Observable<Map<String, Object>> updateToken(Wallet wallet) {
return updateToken(getPaymentCode(wallet)); return updateToken(getPaymentCode(wallet));
} }
public String getSignature(Wallet wallet, String authToken) { public static String getSignature(Wallet wallet, String authToken) {
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
Keystore keystore = masterWallet.getKeystores().get(0); Keystore keystore = masterWallet.getKeystores().get(0);
List<ChildNumber> derivation = keystore.getKeyDerivation().getDerivation(); List<ChildNumber> derivation = keystore.getKeyDerivation().getDerivation();
@ -258,48 +245,16 @@ public class PayNymService {
return notificationPrivKey.signMessage(authToken, ScriptType.P2PKH); return notificationPrivKey.signMessage(authToken, ScriptType.P2PKH);
} }
private PaymentCode getPaymentCode(Wallet wallet) { private static PaymentCode getPaymentCode(Wallet wallet) {
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
return masterWallet.getPaymentCode(); return masterWallet.getPaymentCode();
} }
public HostAndPort getTorProxy() { private static String getHostUrl() {
return httpClientService.getTorProxy(); return getHostUrl(AppServices.getHttpClientService().getTorProxy() != null);
}
public void setTorProxy(HostAndPort torProxy) {
//Ensure all http clients are shutdown first
httpClientService.shutdown();
httpClientService.setTorProxy(torProxy);
}
private String getHostUrl() {
return getHostUrl(getTorProxy() != null);
} }
public static String getHostUrl(boolean tor) { public static String getHostUrl(boolean tor) {
return tor ? "http://paynym7bwekdtb2hzgkpl6y2waqcrs2dii7lwincvxme7mdpcpxzfsad.onion" : "https://paynym.is"; return tor ? "http://paynym7bwekdtb2hzgkpl6y2waqcrs2dii7lwincvxme7mdpcpxzfsad.onion" : "https://paynym.is";
} }
public void shutdown() {
httpClientService.shutdown();
}
public static class ShutdownService extends Service<Boolean> {
private final PayNymService payNymService;
public ShutdownService(PayNymService payNymService) {
this.payNymService = payNymService;
}
@Override
protected Task<Boolean> createTask() {
return new Task<>() {
protected Boolean call() throws Exception {
payNymService.shutdown();
return true;
}
};
}
}
} }

View file

@ -268,7 +268,7 @@ public class CounterpartyController extends SorobanController {
mixingPartner.setText(code.substring(0, 12) + "..." + code.substring(code.length() - 5)); mixingPartner.setText(code.substring(0, 12) + "..." + code.substring(code.length() - 5));
if(isUsePayNym(wallet)) { if(isUsePayNym(wallet)) {
mixPartnerAvatar.setPaymentCode(paymentCodeInitiator); mixPartnerAvatar.setPaymentCode(paymentCodeInitiator);
AppServices.getPayNymService().getPayNym(paymentCodeInitiator.toString()).subscribe(payNym -> { PayNymService.getPayNym(paymentCodeInitiator.toString()).subscribe(payNym -> {
mixingPartner.setText(payNym.nymName()); mixingPartner.setText(payNym.nymName());
}, error -> { }, error -> {
//ignore, may not be a PayNym //ignore, may not be a PayNym
@ -346,10 +346,9 @@ public class CounterpartyController extends SorobanController {
private void followPaymentCode(PaymentCode paymentCodeInitiator) { private void followPaymentCode(PaymentCode paymentCodeInitiator) {
if(isUsePayNym(wallet)) { if(isUsePayNym(wallet)) {
PayNymService payNymService = AppServices.getPayNymService(); PayNymService.getAuthToken(wallet, new HashMap<>()).subscribe(authToken -> {
payNymService.getAuthToken(wallet, new HashMap<>()).subscribe(authToken -> { String signature = PayNymService.getSignature(wallet, authToken);
String signature = payNymService.getSignature(wallet, authToken); PayNymService.followPaymentCode(paymentCodeInitiator, authToken, signature).subscribe(followMap -> {
payNymService.followPaymentCode(paymentCodeInitiator, authToken, signature).subscribe(followMap -> {
log.debug("Followed payment code " + followMap.get("following")); log.debug("Followed payment code " + followMap.get("following"));
}, error -> { }, error -> {
log.warn("Could not follow payment code", error); log.warn("Could not follow payment code", error);
@ -389,13 +388,12 @@ public class CounterpartyController extends SorobanController {
public void retrievePayNym(ActionEvent event) { public void retrievePayNym(ActionEvent event) {
setUsePayNym(wallet, true); setUsePayNym(wallet, true);
PayNymService payNymService = AppServices.getPayNymService(); PayNymService.createPayNym(wallet).subscribe(createMap -> {
payNymService.createPayNym(wallet).subscribe(createMap -> {
payNym.setText((String)createMap.get("nymName")); payNym.setText((String)createMap.get("nymName"));
payNymAvatar.setPaymentCode(wallet.isMasterWallet() ? wallet.getPaymentCode() : wallet.getMasterWallet().getPaymentCode()); payNymAvatar.setPaymentCode(wallet.isMasterWallet() ? wallet.getPaymentCode() : wallet.getMasterWallet().getPaymentCode());
payNym.setVisible(true); payNym.setVisible(true);
payNymService.claimPayNym(wallet, createMap, true); PayNymService.claimPayNym(wallet, createMap, true);
}, error -> { }, error -> {
log.error("Error retrieving PayNym", error); log.error("Error retrieving PayNym", error);
Optional<ButtonType> optResponse = showErrorDialog("Error retrieving PayNym", "Could not retrieve PayNym. Try again?", ButtonType.CANCEL, ButtonType.OK); Optional<ButtonType> optResponse = showErrorDialog("Error retrieving PayNym", "Could not retrieve PayNym. Try again?", ButtonType.CANCEL, ButtonType.OK);

View file

@ -31,6 +31,7 @@ import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.paynym.PayNym; import com.sparrowwallet.sparrow.paynym.PayNym;
import com.sparrowwallet.sparrow.paynym.PayNymAddress; import com.sparrowwallet.sparrow.paynym.PayNymAddress;
import com.sparrowwallet.sparrow.paynym.PayNymDialog; import com.sparrowwallet.sparrow.paynym.PayNymDialog;
import com.sparrowwallet.sparrow.paynym.PayNymService;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler; import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
@ -325,7 +326,7 @@ public class InitiatorController extends SorobanController {
private void searchPayNyms(String identifier) { private void searchPayNyms(String identifier) {
payNymLoading.setVisible(true); payNymLoading.setVisible(true);
AppServices.getPayNymService().getPayNym(identifier).subscribe(payNym -> { PayNymService.getPayNym(identifier).subscribe(payNym -> {
payNymLoading.setVisible(false); payNymLoading.setVisible(false);
counterpartyPayNymName.set(payNym.nymName()); counterpartyPayNymName.set(payNym.nymName());
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString())); counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
@ -344,7 +345,7 @@ public class InitiatorController extends SorobanController {
private void setPayNymFollowers() { private void setPayNymFollowers() {
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
AppServices.getPayNymService().getPayNym(masterWallet.getPaymentCode().toString()).map(PayNym::following).subscribe(followerPayNyms -> { PayNymService.getPayNym(masterWallet.getPaymentCode().toString()).map(PayNym::following).subscribe(followerPayNyms -> {
findPayNym.setVisible(true); findPayNym.setVisible(true);
payNymFollowers.setItems(FXCollections.observableList(followerPayNyms)); payNymFollowers.setItems(FXCollections.observableList(followerPayNyms));
}, error -> { }, error -> {
@ -624,7 +625,7 @@ public class InitiatorController extends SorobanController {
if(counterpartyPaymentCode.get() != null) { if(counterpartyPaymentCode.get() != null) {
return Observable.just(counterpartyPaymentCode.get()); return Observable.just(counterpartyPaymentCode.get());
} else { } else {
return AppServices.getPayNymService().getPayNym(counterparty.getText()).map(payNym -> new PaymentCode(payNym.paymentCode().toString())); return PayNymService.getPayNym(counterparty.getText()).map(payNym -> new PaymentCode(payNym.paymentCode().toString()));
} }
} }

View file

@ -24,6 +24,7 @@ import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.net.*;
import com.sparrowwallet.sparrow.paynym.PayNym; import com.sparrowwallet.sparrow.paynym.PayNym;
import com.sparrowwallet.sparrow.paynym.PayNymService;
import com.sparrowwallet.sparrow.soroban.InitiatorDialog; import com.sparrowwallet.sparrow.soroban.InitiatorDialog;
import com.sparrowwallet.sparrow.paynym.PayNymAddress; import com.sparrowwallet.sparrow.paynym.PayNymAddress;
import com.sparrowwallet.sparrow.soroban.SorobanServices; import com.sparrowwallet.sparrow.soroban.SorobanServices;
@ -1282,7 +1283,7 @@ public class SendController extends WalletFormController implements Initializabl
clear(null); clear(null);
if(Config.get().isUsePayNym()) { if(Config.get().isUsePayNym()) {
proxyWorker.setMessage("Finding PayNym..."); proxyWorker.setMessage("Finding PayNym...");
AppServices.getPayNymService().getPayNym(externalPaymentCode.toString()).subscribe(payNym -> { PayNymService.getPayNym(externalPaymentCode.toString()).subscribe(payNym -> {
proxyWorker.end(); proxyWorker.end();
addChildWallets(walletTransaction.getWallet(), externalPaymentCode, transaction, payNym); addChildWallets(walletTransaction.getWallet(), externalPaymentCode, transaction, payNym);
}, error -> { }, error -> {

View file

@ -2,19 +2,10 @@ package com.sparrowwallet.sparrow.whirlpool.tor;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.samourai.tor.client.TorClientService; import com.samourai.tor.client.TorClientService;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.net.TorUtils;
import com.sparrowwallet.sparrow.net.Tor;
import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket;
public class SparrowTorClientService extends TorClientService { public class SparrowTorClientService extends TorClientService {
private static final Logger log = LoggerFactory.getLogger(SparrowTorClientService.class);
private final Whirlpool whirlpool; private final Whirlpool whirlpool;
public SparrowTorClientService(Whirlpool whirlpool) { public SparrowTorClientService(Whirlpool whirlpool) {
@ -25,26 +16,7 @@ public class SparrowTorClientService extends TorClientService {
public void changeIdentity() { public void changeIdentity() {
HostAndPort proxy = whirlpool.getTorProxy(); HostAndPort proxy = whirlpool.getTorProxy();
if(proxy != null) { if(proxy != null) {
if(AppServices.isTorRunning()) { TorUtils.changeIdentity(proxy);
Tor.getDefault().getTorManager().signal(TorControlSignal.Signal.NewNym, throwable -> {
log.warn("Failed to signal newnym");
}, successEvent -> {
log.info("Signalled newnym for new Tor circuit");
});
} else {
HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1);
try(Socket socket = new Socket(control.getHost(), control.getPort())) {
writeNewNym(socket);
} catch(Exception e) {
log.warn("Error connecting to " + control + ", no Tor ControlPort configured?");
} }
} }
}
}
private void writeNewNym(Socket socket) throws IOException {
log.debug("Sending NEWNYM to " + socket);
socket.getOutputStream().write("AUTHENTICATE \"\"\r\n".getBytes());
socket.getOutputStream().write("SIGNAL NEWNYM\r\n".getBytes());
}
} }