Commit Graph

124 Commits

Author SHA1 Message Date
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 f7749d62aa
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 246f03fe20
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 b9111db8a8
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
ThomasV
c9820aeca0 Qt: add download_plugin_dialog 2024-04-13 11:35:49 +02:00
ThomasV
ad774a49be external plugins: add methods to read image files 2024-04-13 11:35:49 +02:00
ThomasV
858d999d31 Allow external plugins
- borrows code brom ElectronCash
 - external plugins are imported as zip files
 - check hash from plugins.json file
2024-04-13 11:35:49 +02:00
ThomasV
3e7d4749cf turn classmethod 'find_all_plugins' into an instance method.
change 'use_' prefix to 'enable_plugin_'
2024-04-13 11:35:49 +02:00
SomberNight
b5d8d31723 plugins: follow-up prev, log thread name when loading plugins
Correction to comment in prev commit (and removing it here):
spec.loader.exec_module does not spawn new threads, it simply
executes the module in the current thread.
I got confused but turns out "load_plugin" itself is sometimes
not called from the main thread. Specifically (e.g.), since the recent
wizard rewrite, in the qt gui, the wizard loads the hww plugins
from a new thread.

This now better explains the macos hww crashes: they had started
appearing because we upgraded hidapi (which made it more sensitive
to having to import from main thread) AND scanning(->importing) from
the wizard no longer happened on the main thread after the rewrite.

Plugins should be thread-safe in terms of where they are imported from.
Let's log the importer thread's name (added here), to help recognise
related threading issues.
2023-12-17 06:07:37 +00:00
SomberNight
740016e0d5 hw wallets: fix crashes on macOS
related https://github.com/trezor/cython-hidapi/pull/150#issuecomment-1542391087
2023-12-17 05:22:40 +00:00
SomberNight
0912e5615d pyinstaller build: follow-up: dirty hack for duplicate plugins
Ah ok, I give up for now... the prev does not really work.

The prior commit works on Windows but not on macOS.
On Windows, it would package all plugins as code and only as code.
On MacOS however, it would not package any plugins at all. And with this commit,
where I mark the plugins folder to be packaged as *data*, it packages all plugins as *both* code and data.
Not sure why.

Let's just package all plugins as both code and data, and ignore the code instances explicitly...
2023-11-30 13:48:53 +00:00
SomberNight
b5f1783a62 plugins: better error and logging on duplicate plugins
(=plugin name collision)

This assert is currently failing in pyinstaller builds
(probably have been since it was added in 552bfb589a).

Looks like the pyinstaller binaries include two copies of the hw_wallet and the jade plugins.
This likely has been the case for years, we just never noticed. -- but it started triggering the new assert.
```
  2.78 | I | plugin | iter_modules=[
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='audio_modem', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='bitbox02', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='coldcard', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='cosigner_pool', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='digitalbitbox', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='hw_wallet', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='jade', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='keepkey', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='labels', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='ledger', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='payserver', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='revealer', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='safe_t', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='swapserver', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='trezor', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='trustedcoin', ispkg=True),
ModuleInfo(module_finder=FileFinder('C:\\Program Files (x86)\\Electrum\\electrum\\plugins'), name='virtualkeyboard', ispkg=True),
ModuleInfo(module_finder=<pyimod02_importers.FrozenImporter object at 0x015FDFB8>, name='hw_wallet', ispkg=True),
ModuleInfo(module_finder=<pyimod02_importers.FrozenImporter object at 0x015FDFB8>, name='jade', ispkg=True)]
```
2023-11-30 13:44:00 +00:00
SomberNight
8c9fec4ab8 commands: getconfig to use default values, add existence checks
- getconfig and setconfig now both check configvars for existence
- getconfig returns default values when applicable
- setconfig does not side-step type-checks for values

fixes https://github.com/spesmilo/electrum/issues/8607
closes https://github.com/spesmilo/electrum/pull/8608
2023-09-08 15:11:55 +00:00