Browse Source

Add 60s timeout fallback to BIP78 payjoins

Prior to this commit, the BIP78 payjoin would broadcast
a fallback transaction in case of the server returning an
error, but would not do so in case of server non-response.
After this commit, we add a 60 second timeout after which
the non-payjoin fallback is broadcast client side; see
BIP78 for an explanation of this behaviour.
master
Adam Gibson 5 years ago
parent
commit
347cb7a2d4
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 28
      jmclient/jmclient/payjoin.py

28
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)

Loading…
Cancel
Save