qt send tab: handle invalid ln invoice; and ln invoice with ln disabled
fixes #5639 fixes #5662
This commit is contained in:
@@ -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':
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user