Commit Graph

133 Commits

Author SHA1 Message Date
SomberNight 7afec53828 follow-up prev 2026-03-27 13:15:07 +00:00
SomberNight a508519017 plugin.py: fix some type hints 2026-03-26 18:56:02 +00:00
ThomasV 032dfcf107 plugins: use decorator to early return if plugin not authorized 2026-03-26 17:40:25 +01:00
f321x caff7db493 plugin: make DeviceMgr.run non-blocking, fix lock
Prevents `DeviceMgr.run()` from blocking the `Plugins` `DaemonThread` by
scheduling the hww timeout check instead of awaiting its result on the
`Plugins` thread.

If something in the `_hwd_comms_executor` thread is waiting for user
input, e.g. when setting up a hww in the wizard the user needs to
unlock the hww for `HardwareClientBase.get_xpub()` to return, the
`_hwd_comms_executor` is blocked. If then `DeviceMgr.run()` gets called by
the `Plugins` `DaemonThread` concurrently and tries to check the hww
timeout on the `_hwd_comms_executor` as well the `DaemonThread` is
blocked too until the `_hwd_comms_executor` gets unblocked (and the
`DaemonThread.job_lock` is taken.

Now if something tries to take the `DaemonThread.job_lock` it blocks as
well, so if a user e.g. tries to load a new plugin from the plugins
dialog the whole gui thread will freeze until the hww gets unlocked.
2026-01-21 13:57:04 +01:00
copronista 6be8e3ec88 Update plugin.py
https://github.com/spesmilo/electrum/issues/10220
2025-09-13 23:12:44 -04:00
Sander van Grieken 2ca9e1b686 plugin: fix translatable string, and organize whitespace/imports/type hints while weŕe at it. 2025-08-14 11:37:16 +02:00
ThomasV d7f6820fd6 hardware_wallets: register keystore if if external plugin was authorized
Also remove 'gui_good' from hw_wallets dict (was always True)
2025-06-19 11:23:57 +02:00
ThomasV 630124136e plugins dialog: fix is_available, do not show plugins that are not available 2025-06-10 11:02:18 +02:00
f321x 18e241edf0 fix: use make_aiohttp_session for plugin dl 2025-06-06 17:24:41 +02:00
ThomasV 853b793bef rm verbosity_shortcuts option (unused, redundant) 2025-05-29 16:20:41 +02:00
SomberNight 37350edeae plugins: use util.make_dir for creating external plugin dir
This sets more restrictive unix permissions on the folder.
Also, util.make_dir is "multiprocess-safe".
2025-05-23 15:49:14 +00:00
SomberNight 9b24316915 plugin: _execute_commands_in_subprocess: make sure pipes get closed 2025-05-22 21:48:27 +00:00
ThomasV 79941529d2 simplify plugin logic: remove install/uninstall buttons
external plugins are enabled iff authorized
2025-05-18 12:24:38 +02:00
f321x abc50a97e1 plugins: add functionality to allow setting plugin pubkey from gui
Adds functionality that allows the user to store the plugin authorization pubkey without having to edit files/registry manually.
On Linux systems it spawns the commands in a subprocess with pkexec which will trigger a OS prompt to execute the commands as root.
The user sees the executed commands and can either authorize with the root password or decline.
On windows it uses the windows `ShellExecuteExW` api to edit the registry, this also triggers a OS dialog to accept or decline (UAC dialog).
There is also functionality to reset the key again, which works in the same way.
2025-05-09 13:34:09 +02:00
SomberNight ba3783f998 refactor qt.util.ChoiceWidget: introduce ChoiceItem 2025-05-06 18:12:37 +00:00
SomberNight ef49bb2109 hw plugins: fix DeviceMgr.select_device
regression from ChoiceWidget refactor

follow-up https://github.com/spesmilo/electrum/commit/f7749d62aa9538545645563a5e8d59513b2cab69
2025-05-06 18:11:10 +00:00
f321x e80551192b plugins: structure plugin storage in wallet
store all plugin data by plugin name in a root dictionary `plugin_data`
inside the wallet db so that plugin data can get deleted again.
Prunes the data of plugins from the wallet db on wallet stop if the
plugin is not installed anymore.
2025-05-06 13:16:49 +02:00
ThomasV 9ce21173ec Merge pull request #9770 from f321x/plugin_update
plugins: handle updating plugins from plugin manager
2025-04-30 14:17:01 +02:00
f321x 3bfe1d2d28 handle loading plugins with same name as installed plugin (update) 2025-04-29 15:30:26 +02:00
f321x 1cce216c1f fix: prevent PluginsDialog from getting in bad state
If the plugin file got already deleted while being in the installation
dialog, trying to delete it again will raise an exception.
This is fixed by catching the exception.

If the user tries to install an external plugin that is already
installed, and then closes the PluginDialog, the PluginsDialog will
get into a bad state, throwing an exeption when opening it. This
happens because the add_plugin_dialog deletes the zipfile if the
user closes or cancels the installation dialog.
This is fixed by checking if the plugin is already existing,
instead of trying to install an already existing plugin.
2025-04-29 13:09:19 +02:00
ThomasV 8c028f7528 Add/remove plugins from GUI
- both internal and external plugins require GUI install
   (except internal HW plugins, which are 'auto-loaded' and hidden)
 - remove init_qt hook
 - in Qt, reload wallet windows if plugin enabled/disabled
 - add 'uninstall' button to PluginDialog
 - add 'add plugins' button to wizard hw screen
 - add icons to the plugin list
2025-04-15 08:35:10 +02:00
ThomasV 8f3490c87e recursive config file
move plugin variables into sub dictionaries of user config
2025-04-11 19:06:48 +02:00
ThomasV e084789577 minor fix (follow-up 737417fb80) 2025-04-11 10:05:58 +02:00
ThomasV de047195a9 Allow zip plugins to register keystore
This makes it possible to create external plugins that add support
for hardware wallets.
2025-04-11 09:20:57 +02:00
ThomasV 737417fb80 Userspace plugins:
- Allow plugins saved as zipfiles in user data dir
 - plugins are authorized with a user chosen password
 - pubkey derived from password is saved with admin permissions
2025-04-11 08:45:28 +02:00
ThomasV 6e087950cf move hw_wallet.py from plugins to electrum library 2025-04-10 10:19:15 +02:00
SomberNight 5c233ac325 ci: enable more flake8 stuff
```
$ export ELECTRUM_LINTERS=E9,E101,E129,E273,E274,E703,E71,E722,F5,F6,F7,F8,W191,W29,B
$ export ELECTRUM_LINTERS_IGNORE=B007,B009,B010,B019,B036,F541,F841
$ flake8 . --count --select="$ELECTRUM_LINTERS" --ignore="$ELECTRUM_LINTERS_IGNORE" --show-source --statistics --exclude "*_pb2.py,electrum/_vendor/"
./electrum/commands.py:98:1: F811 redefinition of unused 'format_satoshis' from line 48
def format_satoshis(x):
^
./electrum/commands.py:437:9: F811 redefinition of unused 'Mnemonic' from line 62
        from .mnemonic import Mnemonic
        ^
./electrum/gui/qt/wizard/wallet.py:37:5: F811 redefinition of unused 'Daemon' from line 14
    from electrum.daemon import Daemon
    ^
./electrum/lntransport.py:14:1: F811 redefinition of unused 'Optional' from line 12
from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence
^
./electrum/lntransport.py:14:1: F811 redefinition of unused 'TYPE_CHECKING' from line 12
from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence
^
./electrum/plugin.py:966:13: F811 redefinition of unused 'hid' from line 593
            import hid
            ^
./electrum/plugin.py:1040:13: F811 redefinition of unused 'hid' from line 593
            import hid
            ^
./electrum/util.py:44:1: F811 redefinition of unused 'json' from line 26
import json
^
./electrum/util.py:46:1: F811 redefinition of unused 'NamedTuple' from line 29
from typing import NamedTuple, Optional
^
./electrum/util.py:46:1: F811 redefinition of unused 'Optional' from line 29
from typing import NamedTuple, Optional
^
./electrum/util.py:1456:56: F811 redefinition of unused 'traceback' from line 34
        async def __aexit__(self, exc_type, exc_value, traceback):
                                                       ^
./electrum/wallet_db.py:536:9: F811 redefinition of unused 'LOCAL' from line 46
        LOCAL = 1
        ^
./electrum/wallet_db.py:537:9: F811 redefinition of unused 'REMOTE' from line 46
        REMOTE = -1
        ^
./tests/test_bitcoin.py:28:1: F811 redefinition of unused 'bitcoin' from line 9
from electrum import crypto, constants, bitcoin
^
./tests/test_txbatcher.py:11:1: F811 redefinition of unused 'Transaction' from line 7
from electrum.transaction import Transaction, PartialTxInput, PartialTxOutput, TxOutpoint
^
./tests/test_wallet_vertical.py:20:1: F811 redefinition of unused 'Transaction' from line 10
from electrum.transaction import Transaction, PartialTxOutput, tx_from_any, Sighash
^
16    F811 redefinition of unused 'format_satoshis' from line 48
16

```
2025-04-02 16:21:59 +00:00
SomberNight 55281295b7 ci: bump flake8 to new version
```
./electrum/commands.py:144:9: F824 `global known_commands` is unused: name is never assigned in scope
        global known_commands
        ^
./electrum/commands.py:1916:9: F824 `global known_commands` is unused: name is never assigned in scope
        global known_commands
        ^
./electrum/gui/qt/main_window.py:2405:13: F824 `nonlocal done` is unused: name is never assigned in scope
            nonlocal done
            ^
./electrum/i18n.py:52:5: F824 `global language` is unused: name is never assigned in scope
    global language
    ^
./electrum/plugin.py:189:9: F824 `global _root_permission_cache` is unused: name is never assigned in scope
        global _root_permission_cache
        ^
5     F824 `global known_commands` is unused: name is never assigned in scope
5

```
2025-04-02 14:56:53 +00:00
ThomasV 4c14711dc7 Merge pull request #9656 from SomberNight/202503_configvar_plugins
plugin ConfigVars: define vars less dynamically
2025-03-20 08:45:40 +01:00
SomberNight b132e357a3 plugin ConfigVars: define vars less dynamically
and restore ability to have different internal ConfigVar name and user-visible "key"
(Keys are hard to change as that breaks compat, but it is nice to be able to change
the internal var name, to reorganise stuff sometimes. After new ConfigVars are added,
sometimes we get better insight into how the older ones should have been named.)

follow-up https://github.com/spesmilo/electrum/pull/9648
2025-03-19 16:42:51 +00:00
SomberNight 83bf760c4a plugins: better handle exceptions in __init__
When importing a plugin, if it raised an exception in its `__init__` file, we
ignored it, and still loaded the plugin, in a potentially half-broken state.
This is because maybe_load_plugin_init_method only calls exec_module_from_spec
if the plugin is not already in sys.modules, but exec_module_from_spec
will put the plugin into sys.modules even if it errors.

Consider this patch to test with, enable the "labels" plugin:
```patch
diff --git a/electrum/plugins/labels/__init__.py b/electrum/plugins/labels/__init__.py
index b68127df8e..0d6d95abce 100644
--- a/electrum/plugins/labels/__init__.py
+++ b/electrum/plugins/labels/__init__.py
@@ -21,3 +21,5 @@ async def pull(self: 'Commands', plugin: 'LabelsPlugin' = None, wallet=None, for
     arg:bool:force:pull all labels
     """
     return await plugin.pull_thread(wallet, force=force)
+
+raise Exception("heyheyhey")

```

I would expect we don't load the labels plugin due to the error, but we do:
```
>>> plugins.get_plugin("labels")
<electrum.plugins.labels.qt.Plugin object at 0x7801df30fb50>
```

Log:
```
$ ./run_electrum -v --testnet -o
  0.75 | I | simple_config.SimpleConfig | electrum directory /home/user/.electrum/testnet
  0.75 | E | p/plugin.Plugins | cannot initialize plugin labels: Error pre-loading electrum.plugins.labels: Exception('heyheyhey')
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/plugin.py", line 148, in exec_module_from_spec
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/user/wspace/electrum/electrum/plugins/labels/__init__.py", line 25, in <module>
    raise Exception("heyheyhey")
Exception: heyheyhey

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/plugin.py", line 167, in load_plugins
    self.maybe_load_plugin_init_method(name)
  File "/home/user/wspace/electrum/electrum/plugin.py", line 293, in maybe_load_plugin_init_method
    module = self.exec_module_from_spec(init_spec, base_name)
  File "/home/user/wspace/electrum/electrum/plugin.py", line 150, in exec_module_from_spec
    raise Exception(f"Error pre-loading {path}: {repr(e)}") from e
Exception: Error pre-loading electrum.plugins.labels: Exception('heyheyhey')
  0.75 | D | util.profiler | Plugins.__init__ 0.0030 sec
  0.84 | I | simple_config.SimpleConfig | electrum directory /home/user/.electrum/testnet
  0.89 | I | __main__ | get_default_language: detected default as lang='en_UK'
  0.89 | I | i18n | setting language to 'en_UK'
  0.89 | I | logging | Electrum version: 4.5.8 - https://electrum.org - https://github.com/spesmilo/electrum
  0.89 | I | logging | Python version: 3.10.12 (main, Feb  4 2025, 14:57:36) [GCC 11.4.0]. On platform: Linux-6.8.0-52-generic-x86_64-with-glibc2.35
  0.89 | I | logging | Logging to file: /home/user/.electrum/testnet/logs/electrum_log_20250319T161247Z_6605.log
  0.89 | I | logging | Log filters: verbosity '*', verbosity_shortcuts ''
  0.89 | I | exchange_rate.FxThread | using exchange CoinGecko
  0.90 | D | util.profiler | Daemon.__init__ 0.0047 sec
  0.90 | I | daemon.Daemon | starting taskgroup.
  0.90 | I | daemon.CommandsServer | now running and listening. socktype=unix, addr=/home/user/.electrum/testnet/daemon_rpc_socket
  0.90 | I | p/plugin.Plugins | registering hardware bitbox02: ['hardware', 'bitbox02', 'BitBox02']
  0.90 | I | p/plugin.Plugins | registering hardware coldcard: ['hardware', 'coldcard', 'Coldcard Wallet']
  0.90 | I | p/plugin.Plugins | registering hardware digitalbitbox: ['hardware', 'digitalbitbox', 'Digital Bitbox wallet']
  0.90 | I | p/plugin.Plugins | could not find manifest.json of plugin hw_wallet, skipping...
  0.90 | I | p/plugin.Plugins | registering hardware jade: ['hardware', 'jade', 'Jade wallet']
  0.90 | I | p/plugin.Plugins | registering hardware keepkey: ['hardware', 'keepkey', 'KeepKey wallet']
  0.90 | I | p/plugin.Plugins | registering hardware ledger: ['hardware', 'ledger', 'Ledger wallet']
  0.90 | I | p/plugin.Plugins | registering hardware safe_t: ['hardware', 'safe_t', 'Safe-T mini wallet']
  0.90 | I | p/plugin.Plugins | registering hardware trezor: ['hardware', 'trezor', 'Trezor wallet']
  0.90 | I | p/plugin.Plugins | registering wallet type ('2fa', 'trustedcoin')
  1.01 | I | p/plugin.Plugins | loaded plugin 'labels'. (from thread: 'GUI')
  1.01 | D | util.profiler | Plugins.__init__ 0.1183 sec
```
2025-03-19 16:27:23 +00:00
ThomasV d8964a00e7 config vars for plugins 2025-03-19 11:59:05 +01:00
f321x a9f8048251 use manifest.json instead of loading init file for plugin registration 2025-03-19 10:38:20 +01:00
ThomasV 38f9cac48c Merge pull request #9649 from f321x/move_commands_to_init
Move plugin commands to init file of plugin
2025-03-18 11:14:30 +01:00
f321x e74029c880 move plugin commands to init file of plugin 2025-03-18 09:37:07 +01:00
SomberNight 457979ce63 plugin.py: fix plugin.read_file
follow-up https://github.com/spesmilo/electrum/commit/246f03fe20b1cd65728f68b6937f73e3ef184b25
2025-03-17 17:52:31 +00:00
f321x 246f03fe20 allow all plugins to be either zip or directory based 2025-03-17 16:27:33 +01:00
ThomasV cb39737a39 Plugins call with cmd_only:
- pass temporary config to Plugins
 - load only enabled plugins
 - parse the command line again after plugins are loaded
2025-03-15 13:31:00 +01:00
f321x ae64583ebc add handling of plugin commands 2025-03-15 13:22:28 +01:00
SomberNight be2cd02e54 some clean-ups now that we require python 3.10 2025-01-10 18:52:53 +00:00
f321x ea10c7cfc1 add filehash of external plugins to PluginDialog
remove hashlib import

add filehash of external plugins to PluginDialog

add emptyline

add filehash of external plugins to PluginDialog
2025-01-09 18:15:12 +01:00
ThomasV eccc5900e0 move plugin icons to plugins 2024-10-17 13:32:30 +02:00
ThomasV d70996082e load_external_plugin: allow 'requires_wallet_type' 2024-10-17 10:47:11 +02:00
ThomasV 4ec3b7f344 find_external_plugins: fix for python versions < 3.10 2024-10-11 11:26:33 +02:00
ThomasV 333b3db8ea Unix: Import external plugins from /opt/electrum_plugins 2024-10-08 10:32:37 +02:00
ThomasV 7c6ff6757c plugins dialog: show description and enable buttons in the same dialog 2024-10-02 11:16:59 +02:00
ThomasV b7a9e4cf7e load_external_plugins: fix config variable name 2024-09-27 14:14:03 +02:00
Sander van Grieken 83e14794a1 plugin: clean up imports, style 2024-06-19 11:24:13 +02:00
SomberNight 85af0b8030 win/mac build: bump pyinstaller (5.11.0->5.13.2)
- needed for bumping python version, as 3.11+ is borked without https://github.com/pyinstaller/pyinstaller/issues/7692
- plugin.py: adapt to pyinstaller 5.12+
    loader was renamed in https://github.com/pyinstaller/pyinstaller/commit/b9111db8a869dd19dd7e8b3c952abea3238d02a6
2024-04-18 18:16:10 +00:00
SomberNight 8d07672345 plugin: load_plugin: better exception msg if not found 2024-04-18 16:53:48 +00:00