invoice: fail gracefully with large amount
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
Reference in New Issue
Block a user