Skip to content

Commit

Permalink
request payjoin transactions (over tor if available) in background th…
Browse files Browse the repository at this point in the history
…read
  • Loading branch information
craigraw committed May 19, 2021
1 parent 53a447c commit 1677c47
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 20 deletions.
67 changes: 53 additions & 14 deletions src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,23 @@
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 {
Expand Down Expand Up @@ -74,30 +82,42 @@ public PSBT requestPayjoinPSBT(boolean allowOutputSubstitution) throws PayjoinRe
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<String> 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) {
Expand Down Expand Up @@ -318,4 +338,23 @@ public String getSafeMessage() {
return (message == null ? "Unknown Error" : message);
}
}

public static class RequestPayjoinPSBTService extends Service<PSBT> {
private final Payjoin payjoin;
private final boolean allowOutputSubstitution;

public RequestPayjoinPSBTService(Payjoin payjoin, boolean allowOutputSubstitution) {
this.payjoin = payjoin;
this.allowOutputSubstitution = allowOutputSubstitution;
}

@Override
protected Task<PSBT> createTask() {
return new Task<>() {
protected PSBT call() throws PayjoinReceiverException {
return payjoin.requestPayjoinPSBT(allowOutputSubstitution);
}
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -903,13 +903,16 @@ public void getPayjoinTransaction(ActionEvent event) {
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
Expand Down

0 comments on commit 1677c47

Please sign in to comment.