1
0

daemon: load_wallet: add force_check_password arg, and use it in qml

- fix: qml gui errors when trying to open a wallets with only keystore-encryption
  - fixes https://github.com/spesmilo/electrum/issues/10171
- qml gui to prompt for password on wallet open even if wallet is not storage-encrypted
This commit is contained in:
SomberNight
2025-08-23 16:54:48 +00:00
parent 23a82f328f
commit 92bdc4d4ca
3 changed files with 105 additions and 11 deletions

View File

@@ -476,14 +476,28 @@ class Daemon(Logger):
return func_wrapper
@with_wallet_lock
def load_wallet(self, path, password, *, upgrade=False) -> Optional[Abstract_Wallet]:
def load_wallet(
self,
path,
password: Optional[str],
*,
upgrade: bool = False,
force_check_password: bool = False,
) -> Optional[Abstract_Wallet]:
"""
force_check_password: if False, the password arg is only used if it needed to decrypt the storage.
if True, the password arg is always validated.
"""
assert password != ''
path = standardize_path(path)
wallet_key = self._wallet_key_from_path(path)
# wizard will be launched if we return
if wallet := self._wallets.get(wallet_key):
if force_check_password:
wallet.check_password(password)
return wallet
wallet = self._load_wallet(path, password, upgrade=upgrade, config=self.config)
wallet = self._load_wallet(
path, password, upgrade=upgrade, config=self.config, force_check_password=force_check_password)
if self.network:
wallet.start_network(self.network)
elif wallet.lnworker:
@@ -501,10 +515,11 @@ class Daemon(Logger):
@profiler
def _load_wallet(
path,
password,
password: Optional[str],
*,
upgrade: bool = False,
config: SimpleConfig,
force_check_password: bool = False, # if set, always validate password
) -> Optional[Abstract_Wallet]:
path = standardize_path(path)
storage = WalletStorage(path, allow_partial_writes=config.WALLET_PARTIAL_WRITES)
@@ -519,6 +534,8 @@ class Daemon(Logger):
if db.get_action():
raise WalletUnfinished(db)
wallet = Wallet(db, config=config)
if force_check_password:
wallet.check_password(password)
return wallet
@with_wallet_lock
@@ -546,7 +563,7 @@ class Daemon(Logger):
def stop_wallet(self, path: str) -> bool:
"""Returns True iff a wallet was found."""
# note: this must not be called from the event loop. # TODO raise if so
assert util.get_running_loop() != util.get_asyncio_loop(), 'must not be called from asyncio thread'
fut = asyncio.run_coroutine_threadsafe(self._stop_wallet(path), self.asyncio_loop)
return fut.result()

View File

@@ -201,7 +201,7 @@ class QEDaemon(AuthMixin, QObject):
wallet_already_open = self.daemon.get_wallet(self._path)
if wallet_already_open is not None:
wallet_already_open_password = QEWallet.getInstanceFor(wallet_already_open).password
password = QEWallet.getInstanceFor(wallet_already_open).password
def load_wallet_task():
success = False
@@ -209,7 +209,13 @@ class QEDaemon(AuthMixin, QObject):
local_password = password # need this in local scope
wallet = None
try:
wallet = self.daemon.load_wallet(self._path, local_password, upgrade=True)
wallet = self.daemon.load_wallet(
self._path,
password=local_password,
upgrade=True,
# might have a keystore password, but unencrypted storage. we want to prompt for pw even then:
force_check_password=True,
)
except InvalidPassword:
self.walletRequiresPassword.emit(self._name, self._path)
except FileNotFoundError:
@@ -224,11 +230,6 @@ class QEDaemon(AuthMixin, QObject):
if wallet is None:
return
if wallet_already_open is not None:
# wallet already open. daemon.load_wallet doesn't mind, but
# we need the correct current wallet password below
local_password = wallet_already_open_password
if self.daemon.config.WALLET_USE_SINGLE_PASSWORD:
self._use_single_password = self.daemon.update_password_for_directory(old_password=local_password, new_password=local_password)
self._password = local_password

View File

@@ -228,3 +228,79 @@ class TestCommandsWithDaemon(DaemonTestCase):
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))
class TestLoadWallet(DaemonTestCase):
async def test_simple_load(self):
path1 = self._restore_wallet_from_text("9dk", password=None)
wallet1 = self.daemon.load_wallet(path1, password=None)
await self.daemon._stop_wallet(path1)
async def test_password_checks_for_no_password(self):
real_password = None
path1 = self._restore_wallet_from_text("9dk", password=real_password)
# load_wallet will not validate the password arg unless needed for storage.decrypt():
wallet1 = self.daemon.load_wallet(path1, password="garbage")
await self.daemon._stop_wallet(path1)
# unless force_check_password is set:
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage", force_check_password=True)
wallet1 = self.daemon.load_wallet(path1, password=real_password)
await self.daemon._stop_wallet(path1)
wallet1 = self.daemon.load_wallet(path1, password=real_password, force_check_password=True)
await self.daemon._stop_wallet(path1)
# load_wallet will not validate the password arg if wallet is already loaded, unless force_check_password
wallet1 = self.daemon.load_wallet(path1, password=real_password)
wallet1 = self.daemon.load_wallet(path1, password="garbage")
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage", force_check_password=True)
async def test_password_checks_for_ks_enc(self):
real_password = "1234"
path1 = self._restore_wallet_from_text("9dk", password=real_password, encrypt_file=False)
# load_wallet will not validate the password arg unless needed for storage.decrypt():
wallet1 = self.daemon.load_wallet(path1, password="garbage")
await self.daemon._stop_wallet(path1)
# unless force_check_password is set:
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage", force_check_password=True)
wallet1 = self.daemon.load_wallet(path1, password=real_password)
await self.daemon._stop_wallet(path1)
wallet1 = self.daemon.load_wallet(path1, password=real_password, force_check_password=True)
await self.daemon._stop_wallet(path1)
# load_wallet will not validate the password arg if wallet is already loaded, unless force_check_password
wallet1 = self.daemon.load_wallet(path1, password=real_password)
wallet1 = self.daemon.load_wallet(path1, password="garbage")
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage", force_check_password=True)
async def test_password_checks_for_sto_enc(self):
real_password = "1234"
path1 = self._restore_wallet_from_text("9dk", password=real_password, encrypt_file=True)
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage")
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage", force_check_password=True)
wallet1 = self.daemon.load_wallet(path1, password=real_password)
await self.daemon._stop_wallet(path1)
wallet1 = self.daemon.load_wallet(path1, password=real_password, force_check_password=True)
await self.daemon._stop_wallet(path1)
# load_wallet will not validate the password arg if wallet is already loaded, unless force_check_password
wallet1 = self.daemon.load_wallet(path1, password=real_password)
wallet1 = self.daemon.load_wallet(path1, password="garbage")
with self.assertRaises(util.InvalidPassword):
wallet1 = self.daemon.load_wallet(path1, password="garbage", force_check_password=True)