Browse Source

invoice: fail gracefully with large amount

patch-4
bitromortac 4 years ago
parent
commit
853e912885
No known key found for this signature in database GPG Key ID: 1965063FC13BEBE2
  1. 7
      electrum/gui/kivy/uix/screens.py
  2. 17
      electrum/gui/qt/main_window.py
  3. 13
      electrum/invoices.py
  4. 11
      electrum/lnaddr.py

7
electrum/gui/kivy/uix/screens.py

@ -318,6 +318,7 @@ class SendScreen(CScreen, Logger):
self.app.show_error(_('Invalid amount') + ':\n' + self.amount) self.app.show_error(_('Invalid amount') + ':\n' + self.amount)
return return
message = self.message message = self.message
try:
if self.is_lightning: if self.is_lightning:
return LNInvoice.from_bech32(address) return LNInvoice.from_bech32(address)
else: # on-chain else: # on-chain
@ -333,6 +334,8 @@ class SendScreen(CScreen, Logger):
message=message, message=message,
pr=self.payment_request, pr=self.payment_request,
URI=self.parsed_URI) URI=self.parsed_URI)
except InvoiceError as e:
self.app.show_error(_('Error creating payment') + ':\n' + str(e))
def do_save(self): def do_save(self):
invoice = self.read_invoice() invoice = self.read_invoice()
@ -447,6 +450,7 @@ class ReceiveScreen(CScreen):
amount = self.amount amount = self.amount
amount = self.app.get_amount(amount) if amount else 0 amount = self.app.get_amount(amount) if amount else 0
message = self.message message = self.message
try:
if lightning: if lightning:
key = self.app.wallet.lnworker.add_request(amount, message, self.expiry()) key = self.app.wallet.lnworker.add_request(amount, message, self.expiry())
else: else:
@ -461,6 +465,9 @@ class ReceiveScreen(CScreen):
req = self.app.wallet.make_payment_request(addr, amount, message, self.expiry()) req = self.app.wallet.make_payment_request(addr, amount, message, self.expiry())
self.app.wallet.add_payment_request(req) self.app.wallet.add_payment_request(req)
key = addr key = addr
except InvoiceError as e:
self.app.show_error(_('Error creating payment request') + ':\n' + str(e))
return
self.clear() self.clear()
self.update() self.update()
self.app.show_request(lightning, key) self.app.show_request(lightning, key)

17
electrum/gui/qt/main_window.py

@ -63,7 +63,8 @@ from electrum.util import (format_time,
get_new_wallet_name, send_exception_to_crash_reporter, get_new_wallet_name, send_exception_to_crash_reporter,
InvalidBitcoinURI, maybe_extract_bolt11_invoice, NotEnoughFunds, InvalidBitcoinURI, maybe_extract_bolt11_invoice, NotEnoughFunds,
NoDynamicFeeEstimates, MultipleSpendMaxTxOutputs, NoDynamicFeeEstimates, MultipleSpendMaxTxOutputs,
AddTransactionException, BITCOIN_BIP21_URI_SCHEME) AddTransactionException, BITCOIN_BIP21_URI_SCHEME,
InvoiceError)
from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING, Invoice from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING, Invoice
from electrum.invoices import PR_PAID, PR_FAILED, pr_expiration_values, LNInvoice, OnchainInvoice from electrum.invoices import PR_PAID, PR_FAILED, pr_expiration_values, LNInvoice, OnchainInvoice
from electrum.transaction import (Transaction, PartialTxInput, from electrum.transaction import (Transaction, PartialTxInput,
@ -78,7 +79,7 @@ from electrum.exchange_rate import FxThread
from electrum.simple_config import SimpleConfig from electrum.simple_config import SimpleConfig
from electrum.logging import Logger from electrum.logging import Logger
from electrum.lnutil import ln_dummy_address, extract_nodeid, ConnStringFormatError from electrum.lnutil import ln_dummy_address, extract_nodeid, ConnStringFormatError
from electrum.lnaddr import lndecode, LnDecodeException from electrum.lnaddr import lndecode, LnDecodeException, LnAddressError
from .exception_window import Exception_Hook from .exception_window import Exception_Hook
from .amountedit import AmountEdit, BTCAmountEdit, FreezableLineEdit, FeerateEdit from .amountedit import AmountEdit, BTCAmountEdit, FreezableLineEdit, FeerateEdit
@ -1223,10 +1224,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
else: else:
return return
def create_invoice(self, is_lightning): def create_invoice(self, is_lightning: bool):
amount = self.receive_amount_e.get_amount() amount = self.receive_amount_e.get_amount()
message = self.receive_message_e.text() message = self.receive_message_e.text()
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING) expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
try:
if is_lightning: if is_lightning:
if not self.wallet.lnworker.channels: if not self.wallet.lnworker.channels:
self.show_error(_("You need to open a Lightning channel first.")) self.show_error(_("You need to open a Lightning channel first."))
@ -1238,6 +1240,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if not key: if not key:
return return
self.address_list.update() self.address_list.update()
except (InvoiceError, LnAddressError) as e:
self.show_error(_('Error creating payment request') + ':\n' + str(e))
return
assert key is not None assert key is not None
self.request_list.update() self.request_list.update()
self.request_list.select_key(key) self.request_list.select_key(key)
@ -1250,7 +1256,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
title = _('Invoice') if is_lightning else _('Address') title = _('Invoice') if is_lightning else _('Address')
self.do_copy(content, title=title) self.do_copy(content, title=title)
def create_bitcoin_request(self, amount, message, expiration) -> Optional[str]: def create_bitcoin_request(self, amount: int, message: str, expiration: int) -> Optional[str]:
addr = self.wallet.get_unused_address() addr = self.wallet.get_unused_address()
if addr is None: if addr is None:
if not self.wallet.is_deterministic(): # imported wallet if not self.wallet.is_deterministic(): # imported wallet
@ -1595,6 +1601,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
def read_invoice(self): def read_invoice(self):
if self.check_send_tab_payto_line_and_show_errors(): if self.check_send_tab_payto_line_and_show_errors():
return return
try:
if not self._is_onchain: if not self._is_onchain:
invoice_str = self.payto_e.lightning_invoice invoice_str = self.payto_e.lightning_invoice
if not invoice_str: if not invoice_str:
@ -1621,6 +1628,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
message=message, message=message,
pr=self.payment_request, pr=self.payment_request,
URI=self.payto_URI) URI=self.payto_URI)
except InvoiceError as e:
self.show_error(_('Error creating payment') + ':\n' + str(e))
def do_save_invoice(self): def do_save_invoice(self):
self.pending_invoice = self.read_invoice() self.pending_invoice = self.read_invoice()

13
electrum/invoices.py

@ -6,7 +6,7 @@ import attr
from .json_db import StoredObject from .json_db import StoredObject
from .i18n import _ from .i18n import _
from .util import age from .util import age, InvoiceError
from .lnaddr import lndecode, LnAddr from .lnaddr import lndecode, LnAddr
from . import constants from . import constants
from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
@ -134,12 +134,12 @@ class OnchainInvoice(Invoice):
def _validate_amount(self, attribute, value): def _validate_amount(self, attribute, value):
if isinstance(value, int): if isinstance(value, int):
if not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN): if not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN):
raise ValueError(f"amount is out-of-bounds: {value!r} sat") raise InvoiceError(f"amount is out-of-bounds: {value!r} sat")
elif isinstance(value, str): elif isinstance(value, str):
if value != "!": if value != "!":
raise ValueError(f"unexpected amount: {value!r}") raise InvoiceError(f"unexpected amount: {value!r}")
else: else:
raise ValueError(f"unexpected amount: {value!r}") raise InvoiceError(f"unexpected amount: {value!r}")
@classmethod @classmethod
def from_bip70_payreq(cls, pr: 'PaymentRequest', height:int) -> 'OnchainInvoice': def from_bip70_payreq(cls, pr: 'PaymentRequest', height:int) -> 'OnchainInvoice':
@ -173,9 +173,9 @@ class LNInvoice(Invoice):
return return
if isinstance(value, int): if isinstance(value, int):
if not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN * 1000): if not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN * 1000):
raise ValueError(f"amount is out-of-bounds: {value!r} msat") raise InvoiceError(f"amount is out-of-bounds: {value!r} msat")
else: else:
raise ValueError(f"unexpected amount: {value!r}") raise InvoiceError(f"unexpected amount: {value!r}")
@property @property
def _lnaddr(self) -> LnAddr: def _lnaddr(self) -> LnAddr:
@ -231,4 +231,3 @@ class LNInvoice(Invoice):
# 'tags': str(lnaddr.tags), # 'tags': str(lnaddr.tags),
}) })
return d return d

11
electrum/lnaddr.py

@ -22,6 +22,10 @@ if TYPE_CHECKING:
from .lnutil import LnFeatures from .lnutil import LnFeatures
class LnAddressError(Exception):
pass
# BOLT #11: # BOLT #11:
# #
# A writer MUST encode `amount` as a positive decimal integer with no # A writer MUST encode `amount` as a positive decimal integer with no
@ -265,6 +269,7 @@ def lnencode(addr: 'LnAddr', privkey) -> str:
return bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(data)) return bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(data))
class LnAddr(object): class LnAddr(object):
def __init__(self, *, paymenthash: bytes = None, amount=None, currency=None, tags=None, date=None, def __init__(self, *, paymenthash: bytes = None, amount=None, currency=None, tags=None, date=None,
payment_secret: bytes = None): payment_secret: bytes = None):
@ -286,16 +291,16 @@ class LnAddr(object):
@amount.setter @amount.setter
def amount(self, value): def amount(self, value):
if not (isinstance(value, Decimal) or value is None): if not (isinstance(value, Decimal) or value is None):
raise ValueError(f"amount must be Decimal or None, not {value!r}") raise LnAddressError(f"amount must be Decimal or None, not {value!r}")
if value is None: if value is None:
self._amount = None self._amount = None
return return
assert isinstance(value, Decimal) assert isinstance(value, Decimal)
if value.is_nan() or not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC): if value.is_nan() or not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC):
raise ValueError(f"amount is out-of-bounds: {value!r} BTC") raise LnAddressError(f"amount is out-of-bounds: {value!r} BTC")
if value * 10**12 % 10: if value * 10**12 % 10:
# max resolution is millisatoshi # max resolution is millisatoshi
raise ValueError(f"Cannot encode {value!r}: too many decimal places") raise LnAddressError(f"Cannot encode {value!r}: too many decimal places")
self._amount = value self._amount = value
def get_amount_sat(self) -> Optional[Decimal]: def get_amount_sat(self) -> Optional[Decimal]:

Loading…
Cancel
Save