bip70 PRs: use aiohttp instead of requests. use proxy. small fixes.
This commit is contained in:
@@ -327,7 +327,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes):
|
|||||||
public_key.verify_message_hash(sig65[1:], h)
|
public_key.verify_message_hash(sig65[1:], h)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_error("Verification error: {0}".format(e))
|
print_error(f"Verification error: {repr(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from decimal import Decimal
|
|||||||
import base64
|
import base64
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import queue
|
import queue
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from PyQt5.QtGui import *
|
from PyQt5.QtGui import *
|
||||||
from PyQt5.QtCore import *
|
from PyQt5.QtCore import *
|
||||||
@@ -1656,10 +1657,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
self.invoices.set_paid(pr, tx.txid())
|
self.invoices.set_paid(pr, tx.txid())
|
||||||
self.invoices.save()
|
self.invoices.save()
|
||||||
self.payment_request = None
|
self.payment_request = None
|
||||||
refund_address = self.wallet.get_receiving_addresses()[0]
|
refund_address = self.wallet.get_receiving_address()
|
||||||
ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
|
coro = pr.send_payment_and_receive_paymentack(str(tx), refund_address)
|
||||||
if ack_status:
|
fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
|
||||||
msg = ack_msg
|
ack_status, ack_msg = fut.result(timeout=20)
|
||||||
|
msg += f"\n\nPayment ACK: {ack_status}.\nAck message: {ack_msg}"
|
||||||
return status, msg
|
return status, msg
|
||||||
|
|
||||||
# Capture current TL window; override might be removed on return
|
# Capture current TL window; override might be removed on return
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
|
|
||||||
|
import requests
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -38,15 +39,17 @@ except ImportError:
|
|||||||
sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'")
|
sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'")
|
||||||
|
|
||||||
from . import bitcoin, ecc, util, transaction, x509, rsakey
|
from . import bitcoin, ecc, util, transaction, x509, rsakey
|
||||||
from .util import print_error, bh2u, bfh, export_meta, import_meta
|
from .util import print_error, bh2u, bfh, export_meta, import_meta, make_aiohttp_session
|
||||||
from .crypto import sha256
|
from .crypto import sha256
|
||||||
from .bitcoin import TYPE_ADDRESS
|
from .bitcoin import TYPE_ADDRESS
|
||||||
from .transaction import TxOutput
|
from .transaction import TxOutput
|
||||||
|
from .network import Network
|
||||||
|
|
||||||
|
|
||||||
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
|
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
|
||||||
ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
|
ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
|
||||||
|
|
||||||
ca_path = requests.certs.where()
|
ca_path = requests.certs.where() # FIXME do we need to depend on requests here?
|
||||||
ca_list = None
|
ca_list = None
|
||||||
ca_keyID = None
|
ca_keyID = None
|
||||||
|
|
||||||
@@ -64,25 +67,31 @@ PR_UNKNOWN = 2 # sent but not propagated
|
|||||||
PR_PAID = 3 # send and propagated
|
PR_PAID = 3 # send and propagated
|
||||||
|
|
||||||
|
|
||||||
|
async def get_payment_request(url: str) -> 'PaymentRequest':
|
||||||
def get_payment_request(url):
|
|
||||||
u = urllib.parse.urlparse(url)
|
u = urllib.parse.urlparse(url)
|
||||||
error = None
|
error = None
|
||||||
if u.scheme in ['http', 'https']:
|
if u.scheme in ('http', 'https'):
|
||||||
|
resp_content = None
|
||||||
try:
|
try:
|
||||||
response = requests.request('GET', url, headers=REQUEST_HEADERS)
|
proxy = Network.get_instance().proxy
|
||||||
response.raise_for_status()
|
async with make_aiohttp_session(proxy, headers=REQUEST_HEADERS) as session:
|
||||||
# Guard against `bitcoin:`-URIs with invalid payment request URLs
|
async with session.get(url) as response:
|
||||||
if "Content-Type" not in response.headers \
|
resp_content = await response.read()
|
||||||
or response.headers["Content-Type"] != "application/bitcoin-paymentrequest":
|
response.raise_for_status()
|
||||||
data = None
|
# Guard against `bitcoin:`-URIs with invalid payment request URLs
|
||||||
error = "payment URL not pointing to a payment request handling server"
|
if "Content-Type" not in response.headers \
|
||||||
else:
|
or response.headers["Content-Type"] != "application/bitcoin-paymentrequest":
|
||||||
data = response.content
|
data = None
|
||||||
print_error('fetched payment request', url, len(response.content))
|
error = "payment URL not pointing to a payment request handling server"
|
||||||
except requests.exceptions.RequestException:
|
else:
|
||||||
|
data = resp_content
|
||||||
|
data_len = len(data) if data is not None else None
|
||||||
|
print_error('fetched payment request', url, data_len)
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
error = f"Error while contacting payment URL:\n{repr(e)}"
|
||||||
|
if isinstance(e, aiohttp.ClientResponseError) and e.status == 400 and resp_content:
|
||||||
|
error += "\n" + resp_content.decode("utf8")
|
||||||
data = None
|
data = None
|
||||||
error = "payment URL not pointing to a valid server"
|
|
||||||
elif u.scheme == 'file':
|
elif u.scheme == 'file':
|
||||||
try:
|
try:
|
||||||
with open(u.path, 'r', encoding='utf-8') as f:
|
with open(u.path, 'r', encoding='utf-8') as f:
|
||||||
@@ -92,7 +101,7 @@ def get_payment_request(url):
|
|||||||
error = "payment URL not pointing to a valid file"
|
error = "payment URL not pointing to a valid file"
|
||||||
else:
|
else:
|
||||||
data = None
|
data = None
|
||||||
error = "Unknown scheme for payment request. URL: {}".format(url)
|
error = f"Unknown scheme for payment request. URL: {url}"
|
||||||
pr = PaymentRequest(data, error)
|
pr = PaymentRequest(data, error)
|
||||||
return pr
|
return pr
|
||||||
|
|
||||||
@@ -255,7 +264,7 @@ class PaymentRequest:
|
|||||||
def get_outputs(self):
|
def get_outputs(self):
|
||||||
return self.outputs[:]
|
return self.outputs[:]
|
||||||
|
|
||||||
def send_ack(self, raw_tx, refund_addr):
|
async def send_payment_and_receive_paymentack(self, raw_tx, refund_addr):
|
||||||
pay_det = self.details
|
pay_det = self.details
|
||||||
if not self.details.payment_url:
|
if not self.details.payment_url:
|
||||||
return False, "no url"
|
return False, "no url"
|
||||||
@@ -267,24 +276,25 @@ class PaymentRequest:
|
|||||||
paymnt.memo = "Paid using Electrum"
|
paymnt.memo = "Paid using Electrum"
|
||||||
pm = paymnt.SerializeToString()
|
pm = paymnt.SerializeToString()
|
||||||
payurl = urllib.parse.urlparse(pay_det.payment_url)
|
payurl = urllib.parse.urlparse(pay_det.payment_url)
|
||||||
|
resp_content = None
|
||||||
try:
|
try:
|
||||||
r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=ca_path)
|
proxy = Network.get_instance().proxy
|
||||||
except requests.exceptions.SSLError:
|
async with make_aiohttp_session(proxy, headers=ACK_HEADERS) as session:
|
||||||
print("Payment Message/PaymentACK verify Failed")
|
async with session.post(payurl.geturl(), data=pm) as response:
|
||||||
try:
|
resp_content = await response.read()
|
||||||
r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=False)
|
response.raise_for_status()
|
||||||
except Exception as e:
|
try:
|
||||||
print(e)
|
paymntack = pb2.PaymentACK()
|
||||||
return False, "Payment Message/PaymentACK Failed"
|
paymntack.ParseFromString(resp_content)
|
||||||
if r.status_code >= 500:
|
except Exception:
|
||||||
return False, r.reason
|
return False, "PaymentACK could not be processed. Payment was sent; please manually verify that payment was received."
|
||||||
try:
|
print(f"PaymentACK message received: {paymntack.memo}")
|
||||||
paymntack = pb2.PaymentACK()
|
return True, paymntack.memo
|
||||||
paymntack.ParseFromString(r.content)
|
except aiohttp.ClientError as e:
|
||||||
except Exception:
|
error = f"Payment Message/PaymentACK Failed:\n{repr(e)}"
|
||||||
return False, "PaymentACK could not be processed. Payment was sent; please manually verify that payment was received."
|
if isinstance(e, aiohttp.ClientResponseError) and e.status == 400 and resp_content:
|
||||||
print("PaymentACK message received: %s" % paymntack.memo)
|
error += "\n" + resp_content.decode("utf8")
|
||||||
return True, paymntack.memo
|
return False, error
|
||||||
|
|
||||||
|
|
||||||
def make_unsigned_request(req):
|
def make_unsigned_request(req):
|
||||||
|
|||||||
@@ -788,7 +788,8 @@ class Transaction:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pay_script(self, output_type, addr):
|
def pay_script(self, output_type, addr: str) -> str:
|
||||||
|
"""Returns scriptPubKey in hex form."""
|
||||||
if output_type == TYPE_SCRIPT:
|
if output_type == TYPE_SCRIPT:
|
||||||
return addr
|
return addr
|
||||||
elif output_type == TYPE_ADDRESS:
|
elif output_type == TYPE_ADDRESS:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import os, sys, re, json
|
import os, sys, re, json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import NamedTuple, Union, TYPE_CHECKING, Tuple, Optional
|
from typing import NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import decimal
|
import decimal
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -693,7 +693,7 @@ def block_explorer_URL(config: 'SimpleConfig', kind: str, item: str) -> Optional
|
|||||||
#_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
|
#_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
|
||||||
#urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
#urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
||||||
|
|
||||||
def parse_URI(uri, on_pr=None):
|
def parse_URI(uri: str, on_pr: Callable=None) -> dict:
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
from .bitcoin import COIN
|
from .bitcoin import COIN
|
||||||
|
|
||||||
@@ -746,18 +746,17 @@ def parse_URI(uri, on_pr=None):
|
|||||||
sig = out.get('sig')
|
sig = out.get('sig')
|
||||||
name = out.get('name')
|
name = out.get('name')
|
||||||
if on_pr and (r or (name and sig)):
|
if on_pr and (r or (name and sig)):
|
||||||
def get_payment_request_thread():
|
async def get_payment_request():
|
||||||
from . import paymentrequest as pr
|
from . import paymentrequest as pr
|
||||||
if name and sig:
|
if name and sig:
|
||||||
s = pr.serialize_request(out).SerializeToString()
|
s = pr.serialize_request(out).SerializeToString()
|
||||||
request = pr.PaymentRequest(s)
|
request = pr.PaymentRequest(s)
|
||||||
else:
|
else:
|
||||||
request = pr.get_payment_request(r)
|
request = await pr.get_payment_request(r)
|
||||||
if on_pr:
|
if on_pr:
|
||||||
on_pr(request)
|
on_pr(request)
|
||||||
t = threading.Thread(target=get_payment_request_thread)
|
loop = asyncio.get_event_loop()
|
||||||
t.setDaemon(True)
|
asyncio.run_coroutine_threadsafe(get_payment_request(), loop)
|
||||||
t.start()
|
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user