electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 8dabdf8bfb3abea550ed0e15c7695ad992d936fa
parent 1773bd6cd6b9aaa693c6c1cb865f88fc3597e09f
Author: SomberNight <somber.night@protonmail.com>
Date:   Tue,  1 Oct 2019 19:07:27 +0200

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

fixes #5639
fixes #5662

Diffstat:
Melectrum/gui/qt/main_window.py | 65++++++++++++++++++++++++++++++++++++++++++-----------------------
Melectrum/gui/qt/paytoedit.py | 26++++++++++++++++++++------
Melectrum/lnaddr.py | 8++++++--
3 files changed, 68 insertions(+), 31 deletions(-)

diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py @@ -1629,10 +1629,31 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): outputs = self.payto_e.get_outputs(self.max_button.isChecked()) 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. Also shows error dialog to user if so. """ + if not outputs: + self.show_error(_('No outputs')) + return True + + for o in outputs: + if o.address is None: + self.show_error(_('Bitcoin Address is None')) + return True + if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address): + self.show_error(_('Invalid Bitcoin Address')) + return True + if o.value is None: + self.show_error(_('Invalid Amount')) + return True + + 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(): @@ -1642,7 +1663,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): 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])) + 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: @@ -1653,21 +1676,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): if not self.question(msg): return True - if not outputs: - self.show_error(_('No outputs')) - return True - - for o in outputs: - if o.address is None: - self.show_error(_('Bitcoin Address is None')) - return True - if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address): - self.show_error(_('Invalid Bitcoin Address')) - return True - if o.value is None: - self.show_error(_('Invalid Amount')) - return True - return False # no errors def pay_lightning_invoice(self, invoice): @@ -1694,14 +1702,21 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.show_error(_('Error') + '\n' + str(e)) def read_invoice(self): - message = self.message_e.text() - amount = self.amount_e.get_amount() + if self.check_send_tab_payto_line_and_show_errors(): + return 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: 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 + message = self.message_e.text() return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI) def do_save_invoice(self): @@ -1968,8 +1983,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.payment_request_error_signal.emit() def parse_lightning_invoice(self, invoice): - from electrum.lnaddr import lndecode - lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) + """Parse ln invoice, and prepare the send tab for it.""" + 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()) for k,v in lnaddr.tags: if k == 'd': diff --git a/electrum/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py @@ -25,6 +25,7 @@ import re from decimal import Decimal +from typing import NamedTuple, Sequence from PyQt5.QtGui import QFontMetrics @@ -33,6 +34,7 @@ from electrum.util import bfh from electrum.transaction import TxOutput, push_script from electrum.bitcoin import opcodes from electrum.logging import Logger +from electrum.lnaddr import LnDecodeException from .qrtextedit import ScanQRTextEdit from .completion_text_edit import CompletionTextEdit @@ -44,6 +46,12 @@ frozen_style = "QWidget {border:none;}" normal_style = "QPlainTextEdit { }" +class PayToLineError(NamedTuple): + idx: int # index of line + line_content: str + exc: Exception + + class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): def __init__(self, win): @@ -58,11 +66,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): self.c = None self.textChanged.connect(self.check_text) self.outputs = [] - self.errors = [] + self.errors = [] # type: Sequence[PayToLineError] self.is_pr = False self.is_alias = False self.update_size() self.payto_address = None + self.lightning_invoice = None self.previous_payto = '' def setFrozen(self, b): @@ -125,6 +134,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): outputs = [] total = 0 self.payto_address = None + self.lightning_invoice = None if len(lines) == 1: data = lines[0] if data.startswith("bitcoin:"): @@ -134,8 +144,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): if lower.startswith("lightning:ln"): lower = lower[10:] if lower.startswith("ln"): - self.win.parse_lightning_invoice(lower) - self.lightning_invoice = lower + try: + 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 try: self.payto_address = self.parse_output(data) @@ -150,8 +164,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): for i, line in enumerate(lines): try: output = self.parse_address_and_amount(line) - except: - self.errors.append((i, line.strip())) + except Exception as e: + self.errors.append(PayToLineError(idx=i, line_content=line.strip(), exc=e)) continue outputs.append(output) if output.value == '!': @@ -171,7 +185,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): self.amount_edit.setAmount(total if outputs else None) self.win.lock_amount(total or len(lines)>1) - def get_errors(self): + def get_errors(self) -> Sequence[PayToLineError]: return self.errors def get_recipient(self): diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py @@ -276,10 +276,14 @@ class LnAddr(object): now = time.time() 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: 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: raise ValueError("Bad bech32 checksum")