Some regtest tests depend on manual fee injection to simulate certain
mempool conditions (e.g. lnwatcher_waits_until_fees_go_down). This is
done by manually injecting fee estimates into the `Network` object using
the `test_inject_fee_etas` cli command. However it can still happen that
the Network automatically updates its fee estimates from the connected
electrum server in the time between injecting the fee and the actual
tested logic making decisions based on the fee. This causes the test to
fail sometimes.
By setting the `test_disable_automatic_fee_eta_update` true the Network
will stop automatically updating the fee estimates and the test will
behave as expected.
The ServerWidget was not working properly, when switching from "Manual
Mode" to "Auto Connect" the change wouldn't get saved as it depended on
having a correct server string entered (which isn't neccessary for Auto
Connect).
Also makes the widget behave more sane by cleaning the server input if
Auto Connect is enabled and switching to Manual Mode if the user
manually selects a server.
Update the ServerWidget every time it is shown (on initialization and
also when the user opens it again or switches between network dialog
tabs).
This will clean it up if the user has entered some invalid server and
closes it, otherwise this server would stay in the input field until the
application is restarted.
The list of servers in the ServerWidget allows the user to right click
and 'Use as server' on the servers in the list, however internally it
was handled differently than what the user would expect when clicking on
'Use as server'. E.g. if the user selects a server in autoconnect mode
it would still stay in autoconnect mode so the server could switch again
to another server any time? Now it will also change the mode to manual
(or stay in single server mode if that was selected before), making it
clear that this server will stay selected.
If the user clicks on "Follow this branch" the connect mode will get changed to
autoconnect as internally we connect to a random interface on this
branch.
exc triggered when switching from same server to same server:
```
9.43 | D | gui.qml.qenetwork | server_status updated: Connecting
9.43 | E | network | Exception in _run_new_interface: Exception('diagnostic name not yet available?')
Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/logging.py", line 241, in __get_logger_for_obj
diag_name = self.diagnostic_name()
File "/home/user/wspace/electrum/electrum/interface.py", line 555, in diagnostic_name
return self.server.net_addr_str()
AttributeError: 'str' object has no attribute 'net_addr_str'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/util.py", line 1218, in wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/network.py", line 986, in _run_new_interface
interface = Interface(network=self, server=server)
File "/home/user/wspace/electrum/electrum/interface.py", line 502, in __init__
Logger.__init__(self)
File "/home/user/wspace/electrum/electrum/logging.py", line 232, in __init__
self.logger = self.__get_logger_for_obj()
File "/home/user/wspace/electrum/electrum/logging.py", line 243, in __get_logger_for_obj
raise Exception("diagnostic name not yet available?") from e
Exception: diagnostic name not yet available?
```
- 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
```