mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-03 20:35:13 +00:00
bip70 PRs: use aiohttp instead of requests. use proxy. small fixes.
This commit is contained in:
parent
1b46866e34
commit
1686a97ece
5 changed files with 62 additions and 50 deletions
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue