URI: 
       tbip70 PRs: use aiohttp instead of requests. use proxy. small fixes. - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 1686a97ece31aaa1d2c0e6c7042137a5e8a04943
   DIR parent 1b46866e34b7b95ff1c5e0df59b5b6337b4ff68f
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Mon,  5 Nov 2018 19:31:17 +0100
       
       bip70 PRs: use aiohttp instead of requests. use proxy. small fixes.
       
       Diffstat:
         M electrum/ecc.py                     |       2 +-
         M electrum/gui/qt/main_window.py      |      10 ++++++----
         M electrum/paymentrequest.py          |      84 +++++++++++++++++--------------
         M electrum/transaction.py             |       3 ++-
         M electrum/util.py                    |      13 ++++++-------
       
       5 files changed, 62 insertions(+), 50 deletions(-)
       ---
   DIR diff --git a/electrum/ecc.py b/electrum/ecc.py
       t@@ -327,7 +327,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes):
                public_key.verify_message_hash(sig65[1:], h)
                return True
            except Exception as e:
       -        print_error("Verification error: {0}".format(e))
       +        print_error(f"Verification error: {repr(e)}")
                return False
        
        
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -36,6 +36,7 @@ from decimal import Decimal
        import base64
        from functools import partial
        import queue
       +import asyncio
        
        from PyQt5.QtGui import *
        from PyQt5.QtCore import *
       t@@ -1656,10 +1657,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                        self.invoices.set_paid(pr, tx.txid())
                        self.invoices.save()
                        self.payment_request = None
       -                refund_address = self.wallet.get_receiving_addresses()[0]
       -                ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
       -                if ack_status:
       -                    msg = ack_msg
       +                refund_address = self.wallet.get_receiving_address()
       +                coro = pr.send_payment_and_receive_paymentack(str(tx), refund_address)
       +                fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
       +                ack_status, ack_msg = fut.result(timeout=20)
       +                msg += f"\n\nPayment ACK: {ack_status}.\nAck message: {ack_msg}"
                    return status, msg
        
                # Capture current TL window; override might be removed on return
   DIR diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py
       t@@ -27,9 +27,10 @@ import sys
        import time
        import traceback
        import json
       -import requests
        
       +import requests
        import urllib.parse
       +import aiohttp
        
        
        try:
       t@@ -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'")
        
        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 .bitcoin import TYPE_ADDRESS
        from .transaction import TxOutput
       +from .network import Network
       +
        
        REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', '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_keyID = None
        
       t@@ -64,25 +67,31 @@ PR_UNKNOWN = 2     # sent but not propagated
        PR_PAID    = 3     # send and propagated
        
        
       -
       -def get_payment_request(url):
       +async def get_payment_request(url: str) -> 'PaymentRequest':
            u = urllib.parse.urlparse(url)
            error = None
       -    if u.scheme in ['http', 'https']:
       +    if u.scheme in ('http', 'https'):
       +        resp_content = None
                try:
       -            response = requests.request('GET', url, headers=REQUEST_HEADERS)
       -            response.raise_for_status()
       -            # Guard against `bitcoin:`-URIs with invalid payment request URLs
       -            if "Content-Type" not in response.headers \
       -            or response.headers["Content-Type"] != "application/bitcoin-paymentrequest":
       -                data = None
       -                error = "payment URL not pointing to a payment request handling server"
       -            else:
       -                data = response.content
       -            print_error('fetched payment request', url, len(response.content))
       -        except requests.exceptions.RequestException:
       +            proxy = Network.get_instance().proxy
       +            async with make_aiohttp_session(proxy, headers=REQUEST_HEADERS) as session:
       +                async with session.get(url) as response:
       +                    resp_content = await response.read()
       +                    response.raise_for_status()
       +                    # Guard against `bitcoin:`-URIs with invalid payment request URLs
       +                    if "Content-Type" not in response.headers \
       +                    or response.headers["Content-Type"] != "application/bitcoin-paymentrequest":
       +                        data = None
       +                        error = "payment URL not pointing to a payment request handling server"
       +                    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
       -            error = "payment URL not pointing to a valid server"
            elif u.scheme == 'file':
                try:
                    with open(u.path, 'r', encoding='utf-8') as f:
       t@@ -92,7 +101,7 @@ def get_payment_request(url):
                    error = "payment URL not pointing to a valid file"
            else:
                data = None
       -        error = "Unknown scheme for payment request. URL: {}".format(url)
       +        error = f"Unknown scheme for payment request. URL: {url}"
            pr = PaymentRequest(data, error)
            return pr
        
       t@@ -255,7 +264,7 @@ class PaymentRequest:
            def get_outputs(self):
                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
                if not self.details.payment_url:
                    return False, "no url"
       t@@ -267,24 +276,25 @@ class PaymentRequest:
                paymnt.memo = "Paid using Electrum"
                pm = paymnt.SerializeToString()
                payurl = urllib.parse.urlparse(pay_det.payment_url)
       +        resp_content = None
                try:
       -            r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=ca_path)
       -        except requests.exceptions.SSLError:
       -            print("Payment Message/PaymentACK verify Failed")
       -            try:
       -                r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=False)
       -            except Exception as e:
       -                print(e)
       -                return False, "Payment Message/PaymentACK Failed"
       -        if r.status_code >= 500:
       -            return False, r.reason
       -        try:
       -            paymntack = pb2.PaymentACK()
       -            paymntack.ParseFromString(r.content)
       -        except Exception:
       -            return False, "PaymentACK could not be processed. Payment was sent; please manually verify that payment was received."
       -        print("PaymentACK message received: %s" % paymntack.memo)
       -        return True, paymntack.memo
       +            proxy = Network.get_instance().proxy
       +            async with make_aiohttp_session(proxy, headers=ACK_HEADERS) as session:
       +                async with session.post(payurl.geturl(), data=pm) as response:
       +                    resp_content = await response.read()
       +                    response.raise_for_status()
       +                    try:
       +                        paymntack = pb2.PaymentACK()
       +                        paymntack.ParseFromString(resp_content)
       +                    except Exception:
       +                        return False, "PaymentACK could not be processed. Payment was sent; please manually verify that payment was received."
       +                    print(f"PaymentACK message received: {paymntack.memo}")
       +                    return True, paymntack.memo
       +        except aiohttp.ClientError as e:
       +            error = f"Payment Message/PaymentACK Failed:\n{repr(e)}"
       +            if isinstance(e, aiohttp.ClientResponseError) and e.status == 400 and resp_content:
       +                error += "\n" + resp_content.decode("utf8")
       +            return False, error
        
        
        def make_unsigned_request(req):
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -788,7 +788,8 @@ class Transaction:
                return self
        
            @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:
                    return addr
                elif output_type == TYPE_ADDRESS:
   DIR diff --git a/electrum/util.py b/electrum/util.py
       t@@ -23,7 +23,7 @@
        import binascii
        import os, sys, re, json
        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
        import decimal
        from decimal import Decimal
       t@@ -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)
        #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 .bitcoin import COIN
        
       t@@ -746,18 +746,17 @@ def parse_URI(uri, on_pr=None):
            sig = out.get('sig')
            name = out.get('name')
            if on_pr and (r or (name and sig)):
       -        def get_payment_request_thread():
       +        async def get_payment_request():
                    from . import paymentrequest as pr
                    if name and sig:
                        s = pr.serialize_request(out).SerializeToString()
                        request = pr.PaymentRequest(s)
                    else:
       -                request = pr.get_payment_request(r)
       +                request = await pr.get_payment_request(r)
                    if on_pr:
                        on_pr(request)
       -        t = threading.Thread(target=get_payment_request_thread)
       -        t.setDaemon(True)
       -        t.start()
       +        loop = asyncio.get_event_loop()
       +        asyncio.run_coroutine_threadsafe(get_payment_request(), loop)
        
            return out