- on my PC, with Tor Browser running (socks proxy on port 9150), detect_tor_socks_proxy took ~4.01 seconds
- this was because we probed port 9050, 2 sec timeout, 9051, 2 sec timeout, 9150, ~few ms to succeed
- instead we now probe all ports concurrently
- Wallet.make_unsigned_transaction takes a FeePolicy parameter
- fee sliders act on a FeePolicy instead of config
- different fee policies may be used for different purposes
- do not detect dust outputs in lnsweep, delegate that to lnwatcher
allow enable/disable proxy without nuking proxy mode, host and port (explicit enable_proxy config setting),
move tor probe from frontend to backend code, add probe buttons for Qt and QML
This avoids some false negatives for is_proxy_tor.
(previously we only set is_proxy_tor when the proxy settings were changed)
In particular, consider scenario:
- Tor browser not running
- user sets "localhost:9150" as proxy
- detection sets network.is_proxy_tor to False
- user starts Tor browser
- network, due to retries, finds proxy working and connects to some servers
- network.is_proxy_tor remains False
```
32.40 | E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter
Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/gui/qt/wizard/wallet.py", line 709, in <lambda>
button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select))
File "/home/user/wspace/electrum/electrum/gui/qt/bip39_recovery_dialog.py", line 40, in __init__
fut = asyncio.run_coroutine_threadsafe(coro, network.asyncio_loop)
AttributeError: 'NoneType' object has no attribute 'asyncio_loop'
```
Somewhat a follow-up to 649ce979ab.
This adds some safety belts so we don't accidentally sign a tx that
contains a dummy address.
Specifically we check that tx does not contain output for dummy addr:
- in wallet.sign_transaction
- in network.broadcast_transaction
The second one is perhaps redundant, but I think it does not hurt.
We should not show the untrusted text in the GUI...
With this change, we still log the text, but otherwise it should avoid
unintentionally showing it anywhere, as the original exception is
masked away.
related: https://github.com/spesmilo/electrum/issues/8599#issuecomment-1706775508
Example traceback (and the exc is then shown in main_window.on_error):
```
10.77 | D | n/network | got error from server for Network.listunspent_for_scripthash: <UntrustedServerReturnedError [DO NOT TRUST THIS MESSAGE] original_exception: "RPCError(0, 'heyheyhey')">
10.78 | E | gui.qt.main_window.[test_segwit_2] | on_error
Traceback (most recent call last):
File "...\electrum\electrum\network.py", line 898, in wrapper
return await func(self, *args, **kwargs)
File "...\electrum\electrum\network.py", line 1149, in listunspent_for_scripthash
return await self.interface.listunspent_for_scripthash(sh)
File "...\electrum\electrum\interface.py", line 1027, in listunspent_for_scripthash
raise aiorpcx.jsonrpc.RPCError(0, "heyheyhey")
aiorpcx.jsonrpc.RPCError: (0, 'heyheyhey')
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "...\electrum\electrum\gui\qt\util.py", line 925, in run
result = task.task()
File "...\electrum\electrum\gui\qt\main_window.py", line 2505, in <lambda>
task = lambda: self.network.run_from_another_thread(
File "...\electrum\electrum\network.py", line 383, in run_from_another_thread
return fut.result(timeout)
File "...\Python310\lib\concurrent\futures\_base.py", line 458, in result
return self.__get_result()
File "...\Python310\lib\concurrent\futures\_base.py", line 403, in __get_result
raise self._exception
File "...\electrum\electrum\wallet.py", line 151, in sweep_preparations
async with OldTaskGroup() as group:
File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
await self.join()
File "...\electrum\electrum\util.py", line 1316, in join
task.result()
File "...\electrum\electrum\wallet.py", line 142, in find_utxos_for_privkey
await _append_utxos_to_inputs(
File "...\electrum\electrum\wallet.py", line 129, in _append_utxos_to_inputs
u = await network.listunspent_for_scripthash(scripthash)
File "...\electrum\electrum\network.py", line 872, in make_reliable_wrapper
async with OldTaskGroup(wait=any) as group:
File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
await self.join()
File "...\electrum\electrum\util.py", line 1327, in join
self.completed.result()
File "...\electrum\electrum\network.py", line 903, in wrapper
raise wrapped_exc from e
electrum.network.UntrustedServerReturnedError: The server returned an error.
```
A new config API is introduced, and ~all of the codebase is adapted to it.
The old API is kept but mainly only for dynamic usage where its extra flexibility is needed.
Using examples, the old config API looked this:
```
>>> config.get("request_expiry", 86400)
604800
>>> config.set_key("request_expiry", 86400)
>>>
```
The new config API instead:
```
>>> config.WALLET_PAYREQ_EXPIRY_SECONDS
604800
>>> config.WALLET_PAYREQ_EXPIRY_SECONDS = 86400
>>>
```
The old API operated on arbitrary string keys, the new one uses
a static ~enum-like list of variables.
With the new API:
- there is a single centralised list of config variables, as opposed to
these being scattered all over
- no more duplication of default values (in the getters)
- there is now some (minimal for now) type-validation/conversion for
the config values
closes https://github.com/spesmilo/electrum/pull/5640
closes https://github.com/spesmilo/electrum/pull/5649
Note: there is yet a third API added here, for certain niche/abstract use-cases,
where we need a reference to the config variable itself.
It should only be used when needed:
```
>>> var = config.cv.WALLET_PAYREQ_EXPIRY_SECONDS
>>> var
<ConfigVarWithConfig key='request_expiry'>
>>> var.get()
604800
>>> var.set(3600)
>>> var.get_default_value()
86400
>>> var.is_set()
True
>>> var.is_modifiable()
True
```
local_watchtower.adb.start_network was getting called twice.
follow-up 6ac3f84095
```
20230418T014725.636141Z | ERROR | __main__ |
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 435, in main
handle_cmd(
File "/home/user/wspace/electrum/./run_electrum", line 469, in handle_cmd
d = daemon.Daemon(config, fd)
File "/home/user/wspace/electrum/electrum/util.py", line 462, in <lambda>
return lambda *args, **kw_args: do_profile(args, kw_args)
File "/home/user/wspace/electrum/electrum/util.py", line 458, in do_profile
o = func(*args, **kw_args)
File "/home/user/wspace/electrum/electrum/daemon.py", line 404, in __init__
self.network = Network(config, daemon=self)
File "/home/user/wspace/electrum/electrum/network.py", line 348, in __init__
self.local_watchtower.adb.start_network(self)
File "/home/user/wspace/electrum/electrum/address_synchronizer.py", line 185, in start_network
assert self.network is None, "already started"
AssertionError: already started
```
The qt, qml, and kivy GUIs have a first-start network-setup screen
that allows the user customising the network settings before creating a wallet.
Previously the daemon used to create the network and start it, before this screen,
before the GUI even starts. If the user changed network settings, those would
be set on the already running network, potentially including restarting the network.
Now it becomes the responsibility of the GUI to start the network, allowing this
first-start customisation to take place before starting the network at all.
The qt and the qml GUIs are adapted to make use of this. Kivy, and the other
prototype GUIs are not adapted and just start the network right away, as before.
self.interface might get set to None after decorator checks it but before func gets scheduled:
125.04 | E | asyncio | Task exception was never retrieved
future: <Task finished name='Task-408' coro=<Transaction.add_info_from_network.<locals>.add_info_to_txin() done, defined at ...\electrum\electrum\transaction.py:976> exception=AttributeError("'NoneType' object has no attribute 'get_transaction'")>
Traceback (most recent call last):
File "...\electrum\electrum\transaction.py", line 980, in add_info_to_txin
await txin.add_info_from_network(network=network, ignore_network_issues=ignore_network_issues)
File "...\electrum\electrum\transaction.py", line 375, in add_info_from_network
self.utxo = await fetch_from_network(txid=self.prevout.txid.hex())
File "...\electrum\electrum\transaction.py", line 362, in fetch_from_network
raw_tx = await network.get_transaction(txid, timeout=10)
File "...\electrum\electrum\network.py", line 866, in make_reliable_wrapper
async with OldTaskGroup(wait=any) as group:
File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
await self.join()
File "...\electrum\electrum\util.py", line 1410, in join
self.completed.result()
File "...\electrum\electrum\network.py", line 889, in wrapper
return await func(self, *args, **kwargs)
File "...\electrum\electrum\network.py", line 1114, in get_transaction
return await self.interface.get_transaction(tx_hash=tx_hash, timeout=timeout)
AttributeError: 'NoneType' object has no attribute 'get_transaction'
- in lnurl.py, make request methods async
- in Qt GUI, lnurl network requests no longer block the GUI thread
- but they still do in the kivy GUI
- "lightning address" (LUD-16) support is removed for now as the
email addresses are indistinguishable from openalias email addresses
(both protocols should have added and enforced a prefix, or similar,
to remove this kind of ambiguity -- now we would need to make a
network request just to identify what kind of ID we were given)