From 1677c475001481cb9f5b99bd041314e65ad4a8bf Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 19 May 2021 12:24:46 +0200 Subject: [PATCH] request payjoin transactions (over tor if available) in background thread --- .../sparrow/payjoin/Payjoin.java | 67 +++++++++++++++---- .../transaction/HeadersController.java | 15 +++-- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java b/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java index f6e95c87..d9ccacf0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java +++ b/src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java @@ -14,15 +14,23 @@ import com.sparrowwallet.drongo.psbt.PSBTParseException; import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletNode; +import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent; +import com.sparrowwallet.sparrow.net.ElectrumServer; +import com.sparrowwallet.sparrow.net.MempoolRateSize; +import com.sparrowwallet.sparrow.net.ServerException; +import com.sparrowwallet.sparrow.wallet.SendController; +import javafx.concurrent.Service; +import javafx.concurrent.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; +import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.util.*; public class Payjoin { @@ -74,30 +82,42 @@ public class Payjoin { 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()); - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(finalUri) - .header("Content-Type", "text/plain") - .POST(HttpRequest.BodyPublishers.ofString(base64Psbt)) - .build(); + Proxy proxy = AppServices.getProxy(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpURLConnection connection = proxy == null ? (HttpURLConnection)finalUri.toURL().openConnection() : (HttpURLConnection)finalUri.toURL().openConnection(proxy); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "text/plain"); + connection.setDoOutput(true); - if(response.statusCode() != 200) { + 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.body(), PayjoinReceiverError.class); + 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.body()); + PSBT proposalPsbt = PSBT.fromString(response.toString().trim()); checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution); return proposalPsbt; } catch(URISyntaxException e) { log.error("Invalid payjoin receiver URI", e); throw new PayjoinReceiverException("Invalid payjoin receiver URI", e); - } catch(IOException | InterruptedException e) { + } catch(IOException e) { log.error("Payjoin receiver error", e); throw new PayjoinReceiverException("Payjoin receiver error", e); } catch(PSBTParseException e) { @@ -318,4 +338,23 @@ public class Payjoin { return (message == null ? "Unknown Error" : message); } } + + public static class RequestPayjoinPSBTService extends Service { + private final Payjoin payjoin; + private final boolean allowOutputSubstitution; + + public RequestPayjoinPSBTService(Payjoin payjoin, boolean allowOutputSubstitution) { + this.payjoin = payjoin; + this.allowOutputSubstitution = allowOutputSubstitution; + } + + @Override + protected Task createTask() { + return new Task<>() { + protected PSBT call() throws PayjoinReceiverException { + return payjoin.requestPayjoinPSBT(allowOutputSubstitution); + } + }; + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index d1472184..f165a0b5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -903,13 +903,16 @@ public class HeadersController extends TransactionFormController implements Init throw new IllegalStateException("No valid Payjoin URI"); } - try { - Payjoin payjoin = new Payjoin(payjoinURI, headersForm.getSigningWallet(), headersForm.getPsbt()); - PSBT proposalPsbt = payjoin.requestPayjoinPSBT(true); + Payjoin payjoin = new Payjoin(payjoinURI, headersForm.getSigningWallet(), headersForm.getPsbt()); + Payjoin.RequestPayjoinPSBTService requestPayjoinPSBTService = new Payjoin.RequestPayjoinPSBTService(payjoin, true); + requestPayjoinPSBTService.setOnSucceeded(successEvent -> { + PSBT proposalPsbt = requestPayjoinPSBTService.getValue(); EventManager.get().post(new ViewPSBTEvent(payjoinButton.getScene().getWindow(), headersForm.getName() + " Payjoin", null, proposalPsbt)); - } catch(PayjoinReceiverException e) { - AppServices.showErrorDialog("Invalid Payjoin Transaction", e.getMessage()); - } + }); + requestPayjoinPSBTService.setOnFailed(failedEvent -> { + AppServices.showErrorDialog("Error Requesting Payjoin Transaction", failedEvent.getSource().getException().getMessage()); + }); + requestPayjoinPSBTService.start(); } @Override