Instead of storing its own path, each StoredDict element stores
its own key and a pointer to its parent. If a dict is removed
from the db, its parent pointer is set to None. This makes
self.path return None for all branches that have been pruned.
This passes tests/tests_json_db.py and fixes issue #10000
StoredDict to json_db.
convert_key, convert_value are used to convert json objects
to python classes, do not call them in StoredDict.__setitem__
This makes StoredDict agnostic about the type of database we use.
The toolbar button is very small and some users probably don't even
notice it. As we have lots of space in the toolbar anyways i think it
makes sense to add some text to it to make it more visible and easier to
click. Maybe this is also useful for screen readers.
Launch `WatchtowerPlugin.watchtower.start_watching()` through
asyncio.run_coroutine_threadsafe instead of ensure_future to prevent the
following exception from happening when running python with
`PYTHONASYNCIODEBUG=1`.
```
20251103T165007.087746Z | ERROR | plugin.Plugins | cannot initialize plugin watchtower: Error loading watchtower plugin: RuntimeError('Non-thread-safe operation invoked on an event loop other than the current one')
Traceback (most recent call last):
File "/home/user/code/electrum-fork/electrum/plugin.py", line 629, in load_plugin_by_name
plugin = module.Plugin(self, self.config, name)
File "/home/user/code/electrum-fork/electrum/plugins/watchtower/watchtower.py", line 59, in __init__
asyncio.ensure_future(self.watchtower.start_watching())
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.14/asyncio/tasks.py", line 732, in ensure_future
return loop.create_task(coro_or_future)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.14/asyncio/base_events.py", line 468, in create_task
return self._task_factory(self, coro, **kwargs)
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/code/electrum-fork/electrum/util.py", line 1773, in factory
task = asyncio.Task(coro, loop=loop_, **kwargs)
File "/usr/lib64/python3.14/asyncio/base_events.py", line 829, in call_soon
self._check_thread()
~~~~~~~~~~~~~~~~~~^^
File "/usr/lib64/python3.14/asyncio/base_events.py", line 866, in _check_thread
raise RuntimeError(
"Non-thread-safe operation invoked on an event loop other "
"than the current one")
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/user/code/electrum-fork/electrum/plugin.py", line 184, in load_plugins
self.load_plugin_by_name(name)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/home/user/code/electrum-fork/electrum/plugin.py", line 631, in load_plugin_by_name
raise Exception(f"Error loading {name} plugin: {repr(e)}") from e
Exception: Error loading watchtower plugin: RuntimeError('Non-thread-safe operation invoked on an event loop other than the current one')
```
This is an optimisation and possible hardening against traffic analysis.
After a new block is mined, we sometimes receive "blockchain.scripthash.subscribe" notifications. If so, this is often due to the just mined block including mempool txs we already knew about.
Normally we would call the "blockchain.scripthash.get_history" RPC, to get full history of the affected scripthash.
Instead now we first optimistically guess that all mempool txs touching this scripthash just got mined and see if by assuming that we can reproduce the announced sh status.
- if yes, we saved a network RTT by not having to call "blockchain.scripthash.get_history"
- if no, we request the history from the server using the RPC
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.
Stops the running regtest if one test fails (using the --failfast
option) and makes the wallet data directories of alice, bob and carol
available for debugging. This seems helpful to fix issues only happening
on the CI.
The amount edit box is not turning red anymore if the amount is higher
than the wallet balance, so this string can be removed.
Alternatively it could be made red again but seems like nobody missed
it.
Allow to open the BalanceDialog, by clicking on the Pie Diagram in the
bottom left corner of the main window, if there is an active warning,
even if the wallets balance is 0. Right now the user can see the
warning icon for the lighting channel reserve, but cannot click on the
icon to read the actual warning if there is no balance in the wallet.
peer initialization was never awaited in the `add_peer` method.
This awaits the initialization of the peer so that the caller
actually knows if connection succeeded or timed out.
Because `LNPeer.initialized` was awaited in
`LNPeer._query_gossip()` instead of the main loop the other tasks got
spawned concurrently and each task on its own has to wait for the
initialization. In `LNPeer._send_own_gossip()` this was missing, instead
there is a fixed 10 sec sleep. If the connection was not initialized but
the 10 sec are exceeded `_send_own_gossip()` tries to send gossip and
causes this exception as the `LNTransport` is not ready:
```
2.13 | E | lnpeer.Peer.[LNWallet, 0288fa27c0-bc1900c8] | Exception in main_loop: AttributeError("'LNTransport' object has no attribute 'sk'")
Traceback (most recent call last):
File "/home/user/code/electrum-fork/electrum/util.py", line 1232, in wrapper
return await func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 511, in wrapper_func
return await func(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 525, in main_loop
async with self.taskgroup as group:
^^^^^^^^^^^^^^
File "/home/user/code/electrum-fork/env/lib/python3.14/site-packages/aiorpcx/curio.py", line 304, in __aexit__
await self.join()
File "/home/user/code/electrum-fork/electrum/util.py", line 1420, in join
task.result()
~~~~~~~~~~~^^
File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 573, in _send_own_gossip
self.send_node_announcement(alias, color)
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File "/home/user/code/electrum-fork/electrum/lnpeer.py", line 1830, in send_node_announcement
self.transport.send_bytes(raw_msg)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/home/user/code/electrum-fork/electrum/lntransport.py", line 225, in send_bytes
lc = aead_encrypt(self.sk, self.sn(), b'', l)
^^^^^^^
AttributeError: 'LNTransport' object has no attribute 'sk'. Did you mean: 'sn'?
```
By awaiting the initialization directly in the `main_loop` it is more
clear that the task getting spawned subsequently depend on the transport
being available and separates the initialization more clearly these
other functions.
The string is not translated and might not be accessible for non-english
speakers, even though its relatively self-explanatory i think adding
this emoji makes it more accessible.