diff --git a/jmclient/jmclient/payjoin.py b/jmclient/jmclient/payjoin.py index 9a0ec23..79d40f0 100644 --- a/jmclient/jmclient/payjoin.py +++ b/jmclient/jmclient/payjoin.py @@ -4,6 +4,7 @@ from twisted.web.client import (Agent, readBody, ResponseFailed, BrowserLikePolicyForHTTPS) from twisted.web.iweb import IPolicyForHTTPS from twisted.internet.ssl import CertificateOptions +from twisted.internet.error import ConnectionRefusedError from twisted.web.http_headers import Headers import urllib.parse as urlparse from urllib.parse import urlencode @@ -444,7 +445,9 @@ def send_payjoin(manager, accept_callback=None, return (False, "could not create non-payjoin payment") manager.set_payment_tx_and_psbt(payment_psbt) - # TODO add delayed call to broadcast this after 1 minute + + # add delayed call to broadcast this after 1 minute + reactor.callLater(60, fallback_nonpayjoin_broadcast, manager, b"timeout") # Now we send the request to the server, with the encoded # payment PSBT @@ -484,9 +487,7 @@ def send_payjoin(manager, accept_callback=None, destination_url = manager.server.encode("utf-8") url_parts = list(urlparse.urlparse(destination_url)) - print("From destination url: ", destination_url, " got urlparts: ", url_parts) url_parts[4] = urlencode(params).encode("utf-8") - print("after insertion, url_parts is: ", url_parts) destination_url = urlparse.urlunparse(url_parts) # TODO what to use as user agent? d = agent.request(b"POST", destination_url, @@ -499,22 +500,35 @@ def send_payjoin(manager, accept_callback=None, # by a server rejection (which is accompanied by a non-200 # status code returned), but by failure to communicate. def noResponse(failure): - failure.trap(ResponseFailed) - log.error(failure.value.reasons[0].getTraceback()) - reactor.stop() + failure.trap(ResponseFailed, ConnectionRefusedError) + log.error(failure.value) + fallback_nonpayjoin_broadcast(manager, b"connection refused") d.addErrback(noResponse) return (True, None) def fallback_nonpayjoin_broadcast(manager, err): + """ Sends the non-coinjoin payment onto the network, + assuming that the payjoin failed. The reason for failure is + `err` and will usually be communicated by the server, and must + be a bytestring. + Note that the reactor is shutdown after sending the payment (one-shot + processing). + """ assert isinstance(manager, JMPayjoinManager) + def quit(): + for dc in reactor.getDelayedCalls(): + dc.cancel() + reactor.stop() log.warn("Payjoin did not succeed, falling back to non-payjoin payment.") log.warn("Error message was: " + err.decode("utf-8")) original_tx = manager.initial_psbt.extract_transaction() if not jm_single().bc_interface.pushtx(original_tx.serialize()): log.error("Unable to broadcast original payment. The payment is NOT made.") + quit() + return log.info("We paid without coinjoin. Transaction: ") log.info(btc.human_readable_transaction(original_tx)) - reactor.stop() + quit() def receive_payjoin_proposal_from_server(response, manager): assert isinstance(manager, JMPayjoinManager)