1
0

qt send tab: handle invalid ln invoice; and ln invoice with ln disabled

fixes #5639
fixes #5662
This commit is contained in:
SomberNight
2019-10-01 19:07:27 +02:00
parent 1773bd6cd6
commit 8dabdf8bfb
3 changed files with 72 additions and 35 deletions

View File

@@ -1629,30 +1629,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
outputs = self.payto_e.get_outputs(self.max_button.isChecked()) outputs = self.payto_e.get_outputs(self.max_button.isChecked())
return outputs return outputs
def check_send_tab_outputs_and_show_errors(self, outputs) -> bool: def check_send_tab_onchain_outputs_and_show_errors(self, outputs) -> bool:
"""Returns whether there are errors with outputs. """Returns whether there are errors with outputs.
Also shows error dialog to user if so. Also shows error dialog to user if so.
""" """
pr = self.payment_request
if pr:
if pr.has_expired():
self.show_error(_('Payment request has expired'))
return True
if not pr:
errors = self.payto_e.get_errors()
if errors:
self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
return True
if self.payto_e.is_alias and self.payto_e.validated is False:
alias = self.payto_e.toPlainText()
msg = _('WARNING: the alias "{}" could not be validated via an additional '
'security check, DNSSEC, and thus may not be correct.').format(alias) + '\n'
msg += _('Do you wish to continue?')
if not self.question(msg):
return True
if not outputs: if not outputs:
self.show_error(_('No outputs')) self.show_error(_('No outputs'))
return True return True
@@ -1670,6 +1650,34 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
return False # no errors return False # no errors
def check_send_tab_payto_line_and_show_errors(self) -> bool:
"""Returns whether there are errors.
Also shows error dialog to user if so.
"""
pr = self.payment_request
if pr:
if pr.has_expired():
self.show_error(_('Payment request has expired'))
return True
if not pr:
errors = self.payto_e.get_errors()
if errors:
self.show_warning(_("Invalid Lines found:") + "\n\n" +
'\n'.join([_("Line #") + f"{err.idx+1}: {err.line_content[:40]}... ({repr(err.exc)})"
for err in errors]))
return True
if self.payto_e.is_alias and self.payto_e.validated is False:
alias = self.payto_e.toPlainText()
msg = _('WARNING: the alias "{}" could not be validated via an additional '
'security check, DNSSEC, and thus may not be correct.').format(alias) + '\n'
msg += _('Do you wish to continue?')
if not self.question(msg):
return True
return False # no errors
def pay_lightning_invoice(self, invoice): def pay_lightning_invoice(self, invoice):
amount_sat = self.amount_e.get_amount() amount_sat = self.amount_e.get_amount()
attempts = LN_NUM_PAYMENT_ATTEMPTS attempts = LN_NUM_PAYMENT_ATTEMPTS
@@ -1694,14 +1702,21 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.show_error(_('Error') + '\n' + str(e)) self.show_error(_('Error') + '\n' + str(e))
def read_invoice(self): def read_invoice(self):
message = self.message_e.text() if self.check_send_tab_payto_line_and_show_errors():
amount = self.amount_e.get_amount() return
if not self.is_onchain: if not self.is_onchain:
return self.wallet.lnworker.parse_bech32_invoice(self.payto_e.lightning_invoice) invoice = self.payto_e.lightning_invoice
if not invoice:
return
if not self.wallet.lnworker:
self.show_error(_('Lightning is disabled'))
return
return self.wallet.lnworker.parse_bech32_invoice(invoice)
else: else:
outputs = self.read_outputs() outputs = self.read_outputs()
if self.check_send_tab_outputs_and_show_errors(outputs): if self.check_send_tab_onchain_outputs_and_show_errors(outputs):
return return
message = self.message_e.text()
return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI) return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI)
def do_save_invoice(self): def do_save_invoice(self):
@@ -1968,8 +1983,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.payment_request_error_signal.emit() self.payment_request_error_signal.emit()
def parse_lightning_invoice(self, invoice): def parse_lightning_invoice(self, invoice):
from electrum.lnaddr import lndecode """Parse ln invoice, and prepare the send tab for it."""
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) from electrum.lnaddr import lndecode, LnDecodeException
try:
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
except Exception as e:
raise LnDecodeException(e) from e
pubkey = bh2u(lnaddr.pubkey.serialize()) pubkey = bh2u(lnaddr.pubkey.serialize())
for k,v in lnaddr.tags: for k,v in lnaddr.tags:
if k == 'd': if k == 'd':

View File

@@ -25,6 +25,7 @@
import re import re
from decimal import Decimal from decimal import Decimal
from typing import NamedTuple, Sequence
from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QFontMetrics
@@ -33,6 +34,7 @@ from electrum.util import bfh
from electrum.transaction import TxOutput, push_script from electrum.transaction import TxOutput, push_script
from electrum.bitcoin import opcodes from electrum.bitcoin import opcodes
from electrum.logging import Logger from electrum.logging import Logger
from electrum.lnaddr import LnDecodeException
from .qrtextedit import ScanQRTextEdit from .qrtextedit import ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
@@ -44,6 +46,12 @@ frozen_style = "QWidget {border:none;}"
normal_style = "QPlainTextEdit { }" normal_style = "QPlainTextEdit { }"
class PayToLineError(NamedTuple):
idx: int # index of line
line_content: str
exc: Exception
class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def __init__(self, win): def __init__(self, win):
@@ -58,11 +66,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.c = None self.c = None
self.textChanged.connect(self.check_text) self.textChanged.connect(self.check_text)
self.outputs = [] self.outputs = []
self.errors = [] self.errors = [] # type: Sequence[PayToLineError]
self.is_pr = False self.is_pr = False
self.is_alias = False self.is_alias = False
self.update_size() self.update_size()
self.payto_address = None self.payto_address = None
self.lightning_invoice = None
self.previous_payto = '' self.previous_payto = ''
def setFrozen(self, b): def setFrozen(self, b):
@@ -125,6 +134,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
outputs = [] outputs = []
total = 0 total = 0
self.payto_address = None self.payto_address = None
self.lightning_invoice = None
if len(lines) == 1: if len(lines) == 1:
data = lines[0] data = lines[0]
if data.startswith("bitcoin:"): if data.startswith("bitcoin:"):
@@ -134,8 +144,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
if lower.startswith("lightning:ln"): if lower.startswith("lightning:ln"):
lower = lower[10:] lower = lower[10:]
if lower.startswith("ln"): if lower.startswith("ln"):
self.win.parse_lightning_invoice(lower) try:
self.lightning_invoice = lower self.win.parse_lightning_invoice(lower)
except LnDecodeException as e:
self.errors.append(PayToLineError(idx=0, line_content=data, exc=e))
else:
self.lightning_invoice = lower
return return
try: try:
self.payto_address = self.parse_output(data) self.payto_address = self.parse_output(data)
@@ -150,8 +164,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
for i, line in enumerate(lines): for i, line in enumerate(lines):
try: try:
output = self.parse_address_and_amount(line) output = self.parse_address_and_amount(line)
except: except Exception as e:
self.errors.append((i, line.strip())) self.errors.append(PayToLineError(idx=i, line_content=line.strip(), exc=e))
continue continue
outputs.append(output) outputs.append(output)
if output.value == '!': if output.value == '!':
@@ -171,7 +185,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.amount_edit.setAmount(total if outputs else None) self.amount_edit.setAmount(total if outputs else None)
self.win.lock_amount(total or len(lines)>1) self.win.lock_amount(total or len(lines)>1)
def get_errors(self): def get_errors(self) -> Sequence[PayToLineError]:
return self.errors return self.errors
def get_recipient(self): def get_recipient(self):

View File

@@ -276,10 +276,14 @@ class LnAddr(object):
now = time.time() now = time.time()
return now > self.get_expiry() + self.date return now > self.get_expiry() + self.date
def lndecode(a, verbose=False, expected_hrp=None):
class LnDecodeException(Exception): pass
def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
if expected_hrp is None: if expected_hrp is None:
expected_hrp = constants.net.SEGWIT_HRP expected_hrp = constants.net.SEGWIT_HRP
hrp, data = bech32_decode(a, ignore_long_length=True) hrp, data = bech32_decode(invoice, ignore_long_length=True)
if not hrp: if not hrp:
raise ValueError("Bad bech32 checksum") raise ValueError("Bad bech32 checksum")