From 37914d5af0d23d010ad66eabbb5fb5ad3ea89966 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 4 Jun 2025 14:23:25 +0200 Subject: [PATCH 1/3] cmdline: use 'wallet_path' argument to pass wallet_path --- electrum/commands.py | 9 +++++---- electrum/daemon.py | 5 +---- tests/test_daemon.py | 10 ++-------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 00e18d8bb..59a559e26 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -166,11 +166,12 @@ def command(s): if 'wallet_path' in cmd.options and kwargs.get('wallet_path') is None: kwargs['wallet_path'] = daemon.config.get_wallet_path() if cmd.requires_wallet and kwargs.get('wallet') is None: - kwargs['wallet'] = daemon.config.get_wallet_path() + kwargs['wallet_path'] = daemon.config.get_wallet_path() if 'wallet' in cmd.options: - wallet = kwargs.get('wallet', None) - if isinstance(wallet, str): - wallet = daemon.get_wallet(wallet) + wallet_path = kwargs.pop('wallet_path', None) # unit tests may set wallet and not wallet_path + wallet = kwargs.get('wallet', None) # run_offline_command sets both + if wallet is None: + wallet = daemon.get_wallet(wallet_path) if wallet is None: raise UserFacingException('wallet not loaded') kwargs['wallet'] = wallet diff --git a/electrum/daemon.py b/electrum/daemon.py index f053e0a50..28e062bbb 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -367,10 +367,7 @@ class CommandsServer(AuthenticatedServer): wallet_path = config_options.get('wallet_path') if len(self.daemon._wallets) > 1 and wallet_path is None: raise UserFacingException("error: wallet not specified") - if 'wallet_path' in cmd.options: - kwargs['wallet_path'] = wallet_path - else: - kwargs['wallet'] = wallet_path + kwargs['wallet_path'] = wallet_path func = getattr(self.cmd_runner, cmd.name) # execute requested command now. note: cmd can raise, the caller (self.handle) will wrap it. result = await func(*args, **kwargs) diff --git a/tests/test_daemon.py b/tests/test_daemon.py index 26d697897..31e06a76d 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -215,11 +215,8 @@ class TestCommandsWithDaemon(DaemonTestCase): await cmds.load_wallet(wallet_path=wpath, password="123456") wallet = self.daemon.get_wallet(wpath) self.assertIsInstance(wallet, Abstract_Wallet) - - # when using the CLI/RPC to run commands, the "wallet" param is a path: self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet=wpath, password="123456")) - # in unit tests or custom code, the "wallet" param is often an Abstract_Wallet: + await cmds.getseed(wallet_path=wpath, password="123456")) self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", await cmds.getseed(wallet=wallet, password="123456")) @@ -229,10 +226,7 @@ class TestCommandsWithDaemon(DaemonTestCase): await cmds.load_wallet(wallet_path=wpath, password=None) wallet = self.daemon.get_wallet(wpath) self.assertIsInstance(wallet, Abstract_Wallet) - - # when using the CLI/RPC to run commands, the "wallet" param is a path: self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet=wpath)) - # in unit tests or custom code, the "wallet" param is often an Abstract_Wallet: + await cmds.getseed(wallet_path=wpath)) self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", await cmds.getseed(wallet=wallet)) From 85c3c7709686c701d3e108dd79a6abba96b63c9d Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 4 Jun 2025 12:44:07 +0200 Subject: [PATCH 2/3] CLI: make 'wallet_path' relative to wallets dir If the wallet_path passed to the RPC is a simple filename, interpret it as relative to the user wallets directory, rather than to the current working directory. This is a breaking change, it might affect existing scripts --- RELEASE-NOTES | 3 +++ electrum/commands.py | 6 ++---- electrum/simple_config.py | 13 +++++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index b12882371..ed8f73dec 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -97,6 +97,9 @@ * CLI: - The command line help has been improved; parameters are documented in the same docstring as the command they belong to. + - If the --wallet parameter passed to a command is a simple filename, + it is now interpreted as relative to the users wallets directory, + rather than to the current working directory - Plugins may add extra commands to the CLI. Plugin commands must be prefixed with the plugin's internal name - support for hold invoices diff --git a/electrum/commands.py b/electrum/commands.py index 59a559e26..04e1bfc23 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -163,10 +163,8 @@ def command(s): password = kwargs.get('password') daemon = cmd_runner.daemon if daemon: - if 'wallet_path' in cmd.options and kwargs.get('wallet_path') is None: - kwargs['wallet_path'] = daemon.config.get_wallet_path() - if cmd.requires_wallet and kwargs.get('wallet') is None: - kwargs['wallet_path'] = daemon.config.get_wallet_path() + if 'wallet_path' in cmd.options or cmd.requires_wallet: + kwargs['wallet_path'] = daemon.config.maybe_complete_wallet_path(kwargs.get('wallet_path')) if 'wallet' in cmd.options: wallet_path = kwargs.pop('wallet_path', None) # unit tests may set wallet and not wallet_path wallet = kwargs.get('wallet', None) # run_offline_command sets both diff --git a/electrum/simple_config.py b/electrum/simple_config.py index a48e29521..5daf25e87 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -458,11 +458,20 @@ class SimpleConfig(Logger): else: return self.WALLET_BACKUP_DIRECTORY + def maybe_complete_wallet_path(self, path: Optional[str]) -> str: + return self._complete_wallet_path(path) if path is not None else self.get_wallet_path() + + def _complete_wallet_path(self, path: str) -> str: + """ add user wallets directory if needed """ + if os.path.split(path)[0] == '': + path = os.path.join(self.get_datadir_wallet_path(), path) + return path + def get_wallet_path(self) -> str: """Returns the wallet path.""" # command line -w option - if self.get('wallet_path'): - return os.path.join(self.get('cwd', ''), self.get('wallet_path')) + if path:= self.get('wallet_path'): + return self._complete_wallet_path(path) # current wallet path = self.CURRENT_WALLET if path and os.path.exists(path): From 8019ceb5684d0ea1d326eae800db11fe21d79d93 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 5 Jun 2025 09:06:02 +0200 Subject: [PATCH 3/3] test_daemon: add test the wallet can be loaded by basename --- tests/test_daemon.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/test_daemon.py b/tests/test_daemon.py index 31e06a76d..ed60e421a 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -189,44 +189,43 @@ class TestUnifiedPassword(DaemonTestCase): class TestCommandsWithDaemon(DaemonTestCase): TESTNET = True + SEED = "bitter grass shiver impose acquire brush forget axis eager alone wine silver" async def test_wp_command_with_inmemory_wallet_has_password(self): cmds = Commands(config=self.config, daemon=self.daemon) - wallet = restore_wallet_from_text('bitter grass shiver impose acquire brush forget axis eager alone wine silver', + wallet = restore_wallet_from_text(self.SEED, gap_limit=2, path=None, password="123456", config=self.config)['wallet'] - self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet=wallet, password="123456")) + self.assertEqual(self.SEED, await cmds.getseed(wallet=wallet, password="123456")) async def test_wp_command_with_inmemory_wallet_no_password(self): cmds = Commands(config=self.config, daemon=self.daemon) - wallet = restore_wallet_from_text('bitter grass shiver impose acquire brush forget axis eager alone wine silver', + wallet = restore_wallet_from_text(self.SEED, gap_limit=2, path=None, config=self.config)['wallet'] - self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet=wallet)) + self.assertEqual(self.SEED, await cmds.getseed(wallet=wallet)) async def test_wp_command_with_diskfile_wallet_has_password(self): cmds = Commands(config=self.config, daemon=self.daemon) - wpath = self._restore_wallet_from_text("bitter grass shiver impose acquire brush forget axis eager alone wine silver", password="123456", encrypt_file=True) + wpath = self._restore_wallet_from_text(self.SEED, password="123456", encrypt_file=True) + basename = os.path.basename(wpath) await cmds.load_wallet(wallet_path=wpath, password="123456") wallet = self.daemon.get_wallet(wpath) self.assertIsInstance(wallet, Abstract_Wallet) - self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet_path=wpath, password="123456")) - self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet=wallet, password="123456")) + self.assertEqual(self.SEED, await cmds.getseed(wallet_path=wpath, password="123456")) + self.assertEqual(self.SEED, await cmds.getseed(wallet_path=basename, password='123456')) + self.assertEqual(self.SEED, await cmds.getseed(wallet=wallet, password="123456")) async def test_wp_command_with_diskfile_wallet_no_password(self): cmds = Commands(config=self.config, daemon=self.daemon) - wpath = self._restore_wallet_from_text("bitter grass shiver impose acquire brush forget axis eager alone wine silver", password=None) + wpath = self._restore_wallet_from_text(self.SEED, password=None) + basename = os.path.basename(wpath) await cmds.load_wallet(wallet_path=wpath, password=None) wallet = self.daemon.get_wallet(wpath) self.assertIsInstance(wallet, Abstract_Wallet) - self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet_path=wpath)) - self.assertEqual("bitter grass shiver impose acquire brush forget axis eager alone wine silver", - await cmds.getseed(wallet=wallet)) + self.assertEqual(self.SEED, await cmds.getseed(wallet_path=wpath)) + self.assertEqual(self.SEED, await cmds.getseed(wallet_path=basename)) + self.assertEqual(self.SEED, await cmds.getseed(wallet=wallet))