bip70 PRs: use aiohttp instead of requests. use proxy. small fixes.

This commit is contained in:
SomberNight 2018-11-05 19:31:17 +01:00
parent 1b46866e34
commit 1686a97ece
No known key found for this signature in database
GPG key ID: B33B5F232C6271E9
5 changed files with 62 additions and 50 deletions

View file

@ -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

View file

@ -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 *
@ -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

View file

@ -27,9 +27,10 @@ import sys
import time
import traceback
import json
import requests
import requests
import urllib.parse
import aiohttp
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'")
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
@ -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:
@ -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
@ -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"
@ -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):

View file

@ -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:

View file

@ -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
@ -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
@ -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