If checked, we download prev (parent) txs from the network, asynchronously.
This allows calculating the fee and showing "input addresses".
We could also SPV-verify the tx, to fill in missing tx_mined_status
(block height, blockhash, timestamp, short ids), but this is not done currently.
Note that there is no clean way to do this with electrum protocol 1.4:
`blockchain.transaction.get_merkle(tx_hash, height)` requires knowledge of the block height.
Loosely based on 6112fe0e51
- wallet.add_input_info() previously had a fallback to download parent
prev txs from the network (after a lookup in wallet.db failed).
wallet.add_input_info() is not async, so the network request cannot
be done cleanly there and was really just a hack.
- tx.add_info_from_wallet() calls wallet.add_input_info() on each txin,
in which case these network requests were done sequentially, not concurrently
- the network part of wallet.add_input_info() is now split out into new method:
txin.add_info_from_network()
- in addition to tx.add_info_from_wallet(), there is now also tx.add_info_from_network()
- callers of old tx.add_info_from_wallet() should now called either
- tx.add_info_from_wallet(), then tx.add_info_from_network(), preferably in that order
- tx.add_info_from_wallet() alone is sufficient if the tx is complete,
or typically when not in a signing context
- callers of wallet.bump_fee and wallet.dscancel are now expected to have already
called tx.add_info_from_network(), as it cannot be done in a non-async context
(but for the common case of all-inputs-are-ismine, bump_fee/dscancel should work regardless)
- PartialTxInput.utxo was moved to the baseclass, TxInput.utxo
Required a much higher mental load to parse the name "convert_bip32_path_to_list_of_uint32"
than to parse "convert_bip32_strpath_to_intpath".
And we already have the ~inverse: "convert_bip32_intpath_to_strpath".
fixes https://github.com/spesmilo/electrum/issues/8240#8240 was triggering an AssertionError in wallet.get_invoice_status,
as code there was assuming conf >= 0. To trigger, force-close
a LN channel, and while the sweep is waiting on the CSV, try to
make a payment in the Send tab to the ismine change address used
for the sweep in the future_tx. (order of events can also be reversed)
That way, users can see the effects settings directly on their transaction.
This changes the API of make_tx:
- get_coins is called inside make_tx, so that inputs can be changed dynamically
- make_tx takes an optional parameter: unconfirmed_only, passed to get_coins
- ConfirmTxDialog detects if we can pay by disabling confirmed_only or lowering fee
follow-up 719b468eee
Traceback (most recent call last):
File "...\electrum\electrum\gui\qt\request_list.py", line 111, in item_changed
self.receive_tab.update_current_request()
File "...\electrum\electrum\gui\qt\receive_tab.py", line 227, in update_current_request
lnaddr = self.wallet.get_bolt11_invoice(req) if not help_texts.ln_is_error else ''
File "...\electrum\electrum\wallet.py", line 2515, in get_bolt11_invoice
amount_msat = req.amount_msat if req.amount_msat > 0 else None
TypeError: '>' not supported between instances of 'NoneType' and 'int'
This fixes a bug where if one runs `wallet.clear_history()` they would see exceptions later:
```
Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 866, in timer_actions
self.update_wallet()
File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 1021, in update_wallet
self.update_tabs()
File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 1033, in update_tabs
self.utxo_list.update()
File "/home/user/wspace/electrum/electrum/gui/qt/utxo_list.py", line 103, in update
self.refresh_row(name, idx)
File "/home/user/wspace/electrum/electrum/gui/qt/utxo_list.py", line 124, in refresh_row
parents = self.wallet.get_tx_parents(txid)
File "/home/user/wspace/electrum/electrum/wallet.py", line 885, in get_tx_parents
result.update(self.get_tx_parents(_txid))
File "/home/user/wspace/electrum/electrum/wallet.py", line 881, in get_tx_parents
for i, txin in enumerate(tx.inputs()):
AttributeError: 'NoneType' object has no attribute 'inputs'
```
This is related to the privacy analysis, which assumes that for each tx item in the history list
we should have the raw tx in the db. This is no longer true after wallet.clear_history(), if
the wallet has certain LN channels. E.g. an already redeemed channel that was local-force-closed,
as that closing tx is not related to the wallet directly.
In commit 3541ecb576, we decided not to watch already redeemed channels.
This is potentially good for e.g. privacy, as the server would otherwise see us subscribe to that chan.
However it means that after running wallet.clear_history() txs related to the channel but not to the
wallet won't be re-downloaded.
Instead, now if there are missing txs for a redeemed channel, we start watching it, hence the
synchronizer will re-downloaded the txs.
- add a new event, 'adb_removed_tx'
- new wallet method: get_tx_parents
- number of parents is shown in coins tab
- detailed list of parents is shown in dialog
related https://github.com/spesmilo/electrum/issues/8202
For a HD wallet, instead of checking the first 10 addrs + 10 additional random ones,
we now check the first 10 addrs + 10 random used addrs + 10 random unused addrs.
Checking unused addresses is useful to prevent getting money sent there,
and checking used addresses is useful to inform people of already lost money.
ShortIDs were originally designed for lightning channels, and are now
understood by some block explorers.
This allows to remove one column in the UTXO tab (height is redundant).
In the transaction dialog, the space saving ensures that all inputs fit
into one line (it was not the case previously with p2wsh addresses).
For clarity and consistency, the ShortID is displayed for both inputs
and outputs in the transaction dialog.
wallet.lock -> wallet.transaction_lock order must be respected.
deadlock example:
```
# ThreadID: 19100
File: ...\Python\Python310\lib\threading.py", line 966, in _bootstrap
self._bootstrap_inner()
File: ...\Python\Python310\lib\threading.py", line 1009, in _bootstrap_inner
self.run()
File: ...\Python\Python310\lib\threading.py", line 946, in run
self._target(*self._args, **self._kwargs)
File: "...\electrum\electrum\util.py", line 1552, in run_event_loop
loop.run_until_complete(stopping_fut)
File: ...\Python\Python310\lib\asyncio\base_events.py", line 633, in run_until_complete
self.run_forever()
File: ...\Python\Python310\lib\asyncio\windows_events.py", line 321, in run_forever
super().run_forever()
File: ...\Python\Python310\lib\asyncio\base_events.py", line 600, in run_forever
self._run_once()
File: ...\Python\Python310\lib\asyncio\base_events.py", line 1896, in _run_once
handle._run()
File: ...\Python\Python310\lib\asyncio\events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File: "...\electrum\electrum\wallet.py", line 494, in on_event_adb_added_verified_tx
self._update_invoices_and_reqs_touched_by_tx(tx_hash)
File: "...\electrum\electrum\wallet.py", line 2454, in _update_invoices_and_reqs_touched_by_tx
status = self.get_invoice_status(request)
File: "...\electrum\electrum\wallet.py", line 2333, in get_invoice_status
paid, conf = self.is_onchain_invoice_paid(invoice)
File: "...\electrum\electrum\wallet.py", line 1120, in is_onchain_invoice_paid
is_paid, conf_needed, relevant_txs = self._is_onchain_invoice_paid(invoice)
File: "...\electrum\electrum\wallet.py", line 1095, in _is_onchain_invoice_paid
with self.lock, self.transaction_lock:
File: "...\electrum\electrum\address_synchronizer.py", line 70, in acquire
return self._lock.acquire(*args, **kwargs)
# ThreadID: 20040
File: "C:\Program Files\JetBrains\PyCharm Community Edition 2021.3.3\plugins\python-ce\helpers\pycharm\_jb_pytest_runner.py", line 51, in <module>
sys.exit(pytest.main(args, plugins_to_load + [Plugin]))
File: "...\Python\Python310\site-packages\_pytest\config\__init__.py", line 164, in main
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
File: ...\Python\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File: "...\Python\Python310\site-packages\_pytest\main.py", line 315, in pytest_cmdline_main
return wrap_session(config, _main)
File: "...\Python\Python310\site-packages\_pytest\main.py", line 268, in wrap_session
session.exitstatus = doit(config, session) or 0
File: "...\Python\Python310\site-packages\_pytest\main.py", line 322, in _main
config.hook.pytest_runtestloop(session=session)
File: ...\Python\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File: "...\Python\Python310\site-packages\_pytest\main.py", line 347, in pytest_runtestloop
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
File: ...\Python\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 111, in pytest_runtest_protocol
runtestprotocol(item, nextitem=nextitem)
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 130, in runtestprotocol
reports.append(call_and_report(item, "call", log))
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 219, in call_and_report
call = call_runtest_hook(item, when, **kwds)
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 258, in call_runtest_hook
return CallInfo.from_call(
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 338, in from_call
result: Optional[TResult] = func()
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 259, in <lambda>
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
File: ...\Python\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File: ...\Python\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File: "...\Python\Python310\site-packages\_pytest\runner.py", line 166, in pytest_runtest_call
item.runtest()
File: "...\Python\Python310\site-packages\_pytest\unittest.py", line 327, in runtest
self._testcase(result=self) # type: ignore[arg-type]
File: ...\Python\Python310\lib\unittest\case.py", line 650, in __call__
return self.run(*args, **kwds)
File: ...\Python\Python310\lib\unittest\case.py", line 591, in run
self._callTestMethod(testMethod)
File: ...\Python\Python310\lib\unittest\case.py", line 549, in _callTestMethod
method()
File: "...\electrum\electrum\tests\test_invoices.py", line 120, in test_wallet_without_ln_creates_payreq_and_gets_paid_onchain
self.assertEqual(PR_PAID, wallet1.get_invoice_status(pr))
File: "...\electrum\electrum\wallet.py", line 2333, in get_invoice_status
paid, conf = self.is_onchain_invoice_paid(invoice)
File: "...\electrum\electrum\wallet.py", line 1120, in is_onchain_invoice_paid
is_paid, conf_needed, relevant_txs = self._is_onchain_invoice_paid(invoice)
File: "...\electrum\electrum\wallet.py", line 1095, in _is_onchain_invoice_paid
with self.lock, self.transaction_lock:
File: "...\electrum\electrum\address_synchronizer.py", line 70, in acquire
return self._lock.acquire(*args, **kwargs)
```
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
Especially during initial history sync, there are a lot of False->False up_to_date transitions
(e.g. adb.add_address generates one), and the GUI does some work for each, which adds up to a lot
of CPU usage for the full sync.
- replace complex strategies with a simpler choice,
between preserving or decreasing the payment.
- Always expose that choice to the user.
- Show the resulting fees to the user before they click OK
preference from the GUI, because the mempoolfullrbf option in
Bitcoin 0.24 makes RBF signaling pretty meaningless. Fixes#8088.
Note: RBF remains disabled for channel funding transactions.
In that case, the flag is actually only used as a semaphore
between different instances of the same wallet.
I find this easier to reason about than occasionally overwriting the items.
get_request_by_addr still only returns a single invoice for simplicity,
but now all logic regarding how to handle collisions is inside that method.