and an address is present in the bip21 uri path, register the on-chain address in the
Invoice.outputs field to allow paying onchain from a saved Invoice later (when the PI
is unavailable). Fixes#8654
- @amount_msat.validator prevents the creation of invoices with e.g. too large amounts
- however the qml gui is mutating invoices by directly setting the `amount_msat` field,
and it looks like attrs validators only run during init.
We can use `on_setattr` (introduced in attrs==20.1.0).
- a wallet db upgrade is added to rm existing insane invoices
- btw the qml gui was already doing its own input validation on the textedit
(see qeconfig.btcAmountRegex). however that only limits the input to not have more
chars than what is needed to represent 21M BTC (e.g. you can still enter 99M BTC,
which the invoice logic does not tolerate later on - but is normally caught).
fixes https://github.com/spesmilo/electrum/issues/8582
decorators (instead of overloading JsonDB._convert_dict and
_convert_value)
- stored_in for elements of a StoreDict
- stored_as for singletons
- extra register methods are defined for key conversions
This commit was adapted from the jsonpatch branch
This fixes an inconsistency where the 'expiration' field was
relative for invoices, and absolute timestamp for requests.
This in turn fixes QML the timer refreshing the request list.
In order to prevent any API using that field from being silently
broken, the 'expiration' field is renamed as 'expiry'.
During wallet-open, we load all invoices/payreqs. This involved decoding the lnaddrs twice.
Now we only decode once.
For a wallet with ~1000 payreqs, this noticeably sped up wallet-open:
(before:)
8.83 | D | util.profiler | Daemon._load_wallet 6.4317 sec
(after:)
5.69 | D | util.profiler | Daemon._load_wallet 3.4450 sec
It is very expensive to parse all the lnaddrs...
Without this change, when configuring electrum with bolt11_fallback disabled, and calling
invoice.get_address() on a lightning enabled invoice, it will return None, thereby disabling
retrieving BIP21 uri or address from the invoice/request.
Given a wallet with LN disabled,
and a bolt11 invoice (or a bip21 uri that only contains bolt11 but lacks a top-level address),
if the bolt11 invoice includes a fallback address,
we would previously just error "Lightning is disabled".
Now we offer the user to pay on-chain using the fallback address.
closes https://github.com/spesmilo/electrum/issues/8047
Replace get_key_for_outgoing_invoice, get_key_for_incoming_request
with Invoice.get_id()
When a new request is created, reuse addresses of expired requests (fixes#7927)
The API is changed for the following commands:
get_request, get_invoice,
list_requests, list_invoices,
delete_request, delete_invoice
I believe lightning requests created before https://github.com/spesmilo/electrum/pull/7730
can have an amount of None - ones created after have amount 0 instead.
We could do a wallet db upgrade potentially.
Regardless, the type hint is `get_amount_sat(self) -> Union[int, str, None]`,
so None should be handled. (well, arguably "!" should be handled too...)
```
E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter
Traceback (most recent call last):
File "...\electrum\electrum\gui\qt\request_list.py", line 101, in item_changed
self.parent.show_receive_request(req)
File "...\electrum\electrum\gui\qt\main_window.py", line 1279, in show_receive_request
URI = req.get_bip21_URI(lightning=bip21_lightning)
File "...\electrum\electrum\invoices.py", line 164, in get_bip21_URI
amount = int(self.get_amount_sat())
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
```
```
E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter
Traceback (most recent call last):
File "...\electrum\electrum\gui\qt\request_list.py", line 101, in item_changed
self.parent.show_receive_request(req)
File "...\electrum\electrum\gui\qt\main_window.py", line 1281, in show_receive_request
can_receive_lightning = self.wallet.lnworker and req.get_amount_sat() <= self.wallet.lnworker.num_sats_can_receive()
TypeError: '<=' not supported between instances of 'NoneType' and 'decimal.Decimal'
```
E | gui.qt.ElectrumGui |
Traceback (most recent call last):
File "...\electrum\electrum\gui\qt\__init__.py", line 361, in start_new_window
window = self._create_window_for_wallet(wallet)
File "...\electrum\electrum\gui\qt\__init__.py", line 304, in _create_window_for_wallet
w = ElectrumWindow(self, wallet)
File "...\electrum\electrum\gui\qt\main_window.py", line 223, in __init__
self.send_tab = self.create_send_tab()
File "...\electrum\electrum\gui\qt\main_window.py", line 1537, in create_send_tab
self.invoice_list = InvoiceList(self)
File "...\electrum\electrum\gui\qt\invoice_list.py", line 76, in __init__
self.update()
File "...\electrum\electrum\gui\qt\invoice_list.py", line 109, in update
amount = item.get_amount_sat()
File "...\electrum\electrum\invoices.py", line 158, in get_amount_sat
return int(amount_msat / 1000)
TypeError: unsupported operand type(s) for /: 'str' and 'int'
The 'height' field was added in cdfaaa2609
At the time we thought we could just add it with a default value without a db upgrade;
however the issue is that if old code tries to open a new db, it will fail (due to unexpected new field).
Hence it is better to do an explicit conversion where old code *knows* it cannot open the new db.
E | gui.qt.ElectrumGui |
Traceback (most recent call last):
File "...\electrum\electrum\gui\qt\__init__.py", line 257, in start_new_window
wallet = self.daemon.load_wallet(path, None)
File "...\electrum\electrum\daemon.py", line 488, in load_wallet
db = WalletDB(storage.read(), manual_upgrades=manual_upgrades)
File "...\electrum\electrum\wallet_db.py", line 72, in __init__
self.load_data(raw)
File "...\electrum\electrum\wallet_db.py", line 103, in load_data
self._after_upgrade_tasks()
File "...\electrum\electrum\wallet_db.py", line 189, in _after_upgrade_tasks
self._load_transactions()
File "...\electrum\electrum\util.py", line 408, in <lambda>
return lambda *args, **kw_args: do_profile(args, kw_args)
File "...\electrum\electrum\util.py", line 404, in do_profile
o = func(*args, **kw_args)
File "...\electrum\electrum\wallet_db.py", line 1139, in _load_transactions
self.data = StoredDict(self.data, self, [])
File "...\electrum\electrum\json_db.py", line 79, in __init__
self.__setitem__(k, v)
File "...\electrum\electrum\json_db.py", line 44, in wrapper
return func(self, *args, **kwargs)
File "...\electrum\electrum\json_db.py", line 105, in __setitem__
v = self.db._convert_dict(self.path, key, v)
File "...\electrum\electrum\wallet_db.py", line 1182, in _convert_dict
v = dict((k, Invoice.from_json(x)) for k, x in v.items())
File "...\electrum\electrum\wallet_db.py", line 1182, in <genexpr>
v = dict((k, Invoice.from_json(x)) for k, x in v.items())
File "...\electrum\electrum\invoices.py", line 108, in from_json
return OnchainInvoice(**x)
TypeError: __init__() got an unexpected keyword argument 'height'