Browse Source

invoices: deal with expiration of "0" mess

Internally, we've been using an expiration of 0 to mean "never expires".
For LN invoices, BOLT-11 does not specify what an expiration of 0 means.
Other clients seem to treat it as "0 seconds" (i.e. already expired).
This means there is no way to create a BOLT-11 invoice that "never" expires.

For LN invoices,
- we now treat an expiration of 0, , as "0 seconds",
- when creating an invoice, if the user selected never, we will put 100 years as expiration
hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
7962e17df6
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 4
      electrum/gui/kivy/uix/screens.py
  2. 6
      electrum/gui/qt/main_window.py
  3. 25
      electrum/lnaddr.py
  4. 8
      electrum/lnworker.py
  5. 7
      electrum/util.py

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

@ -24,7 +24,7 @@ from kivy.utils import platform
from kivy.logger import Logger
from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
from electrum.util import PR_TYPE_ONCHAIN, PR_TYPE_LN
from electrum.util import PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum import bitcoin, constants
from electrum.transaction import Transaction, tx_from_any, PartialTransaction, PartialTxOutput
from electrum.util import (parse_URI, InvalidBitcoinURI, PR_PAID, PR_UNKNOWN, PR_EXPIRED,
@ -419,7 +419,7 @@ class ReceiveScreen(CScreen):
Clock.schedule_interval(lambda dt: self.update(), 5)
def expiry(self):
return self.app.electrum_config.get('request_expiry', 3600) # 1 hour
return self.app.electrum_config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
def clear(self):
self.screen.address = ''

6
electrum/gui/qt/main_window.py

@ -62,7 +62,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
get_new_wallet_name, send_exception_to_crash_reporter,
InvalidBitcoinURI, maybe_extract_bolt11_invoice, NotEnoughFunds,
NoDynamicFeeEstimates, MultipleSpendMaxTxOutputs)
from electrum.util import PR_TYPE_ONCHAIN, PR_TYPE_LN
from electrum.util import PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum.transaction import (Transaction, PartialTxInput,
PartialTransaction, PartialTxOutput)
from electrum.address_synchronizer import AddTransactionException
@ -1007,7 +1007,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
evl = sorted(pr_expiration_values.items())
evl_keys = [i[0] for i in evl]
evl_values = [i[1] for i in evl]
default_expiry = self.config.get('request_expiry', 3600)
default_expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
try:
i = evl_keys.index(default_expiry)
except ValueError:
@ -1139,7 +1139,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
def create_invoice(self, is_lightning):
amount = self.receive_amount_e.get_amount()
message = self.receive_message_e.text()
expiry = self.config.get('request_expiry', 3600)
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
if is_lightning:
key = self.wallet.lnworker.add_request(amount, message, expiry)
else:

25
electrum/lnaddr.py

@ -199,6 +199,8 @@ def lnencode(addr, privkey):
# Get minimal length by trimming leading 5 bits at a time.
expirybits = bitstring.pack('intbe:64', v)[4:64]
while expirybits.startswith('0b00000'):
if len(expirybits) == 5:
break # v == 0
expirybits = expirybits[5:]
data += tagged('x', expirybits)
elif k == 'h':
@ -259,21 +261,24 @@ class LnAddr(object):
return self._min_final_cltv_expiry
def get_tag(self, tag):
description = ''
for k,v in self.tags:
for k, v in self.tags:
if k == tag:
description = v
break
return description
return v
return None
def get_description(self):
return self.get_tag('d')
def get_description(self) -> str:
return self.get_tag('d') or ''
def get_expiry(self):
return int(self.get_tag('x') or '3600')
def get_expiry(self) -> int:
exp = self.get_tag('x')
if exp is None:
exp = 3600
return int(exp)
def is_expired(self):
def is_expired(self) -> bool:
now = time.time()
# BOLT-11 does not specify what expiration of '0' means.
# we treat it as 0 seconds here (instead of never)
return now > self.get_expiry() + self.date

8
electrum/lnworker.py

@ -1112,7 +1112,7 @@ class LNWallet(LNWorker):
raise Exception(_("add invoice timed out"))
@log_exceptions
async def _add_request_coro(self, amount_sat, message, expiry):
async def _add_request_coro(self, amount_sat, message, expiry: int):
timestamp = int(time.time())
routing_hints = await self._calc_routing_hints_for_invoice(amount_sat)
if not routing_hints:
@ -1122,6 +1122,12 @@ class LNWallet(LNWorker):
payment_hash = sha256(payment_preimage)
info = PaymentInfo(payment_hash, amount_sat, RECEIVED, PR_UNPAID)
amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
if expiry == 0:
# hack: BOLT-11 is not really clear on what an expiry of 0 means.
# It probably interprets it as 0 seconds, so already expired...
# Our higher level invoices code however uses 0 for "never".
# Hence set some high expiration here
expiry = 100 * 365 * 24 * 60 * 60 # 100 years
lnaddr = LnAddr(payment_hash, amount_btc,
tags=[('d', message),
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),

7
electrum/util.py

@ -104,17 +104,22 @@ pr_tooltips = {
PR_FAILED:_('Failed'),
}
PR_DEFAULT_EXPIRATION_WHEN_CREATING = 24*60*60 # 1 day
pr_expiration_values = {
0: _('Never'),
10*60: _('10 minutes'),
60*60: _('1 hour'),
24*60*60: _('1 day'),
7*24*60*60: _('1 week')
7*24*60*60: _('1 week'),
}
assert PR_DEFAULT_EXPIRATION_WHEN_CREATING in pr_expiration_values
def get_request_status(req):
status = req['status']
exp = req.get('exp', 0) or 0
if req.get('type') == PR_TYPE_LN and exp == 0:
status = PR_EXPIRED # for BOLT-11 invoices, exp==0 means 0 seconds
if req['status'] == PR_UNPAID and exp > 0 and req['time'] + req['exp'] < time.time():
status = PR_EXPIRED
status_str = pr_tooltips[status]

Loading…
Cancel
Save