1
0

19723 Commits

Author SHA1 Message Date
f321x
40811abe45 qt: TxEditor: separate swap transport from tab
Separates the swap transport initialization logic from the submarine
payment tab logic so it can be used for the send change to lightning
functionality too.

Also makes the gui updates on transport establishment more thread safe
by using pyqtSignals instead of calling gui methods from the asyncio
thread.
2026-01-22 10:09:24 +01:00
f321x
844312f5f6 TxEditor: move submarine payment help text to messages
Moves the help text for submarine payments to messages.py
2026-01-22 10:09:22 +01:00
f321x
4f3274e4c6 qt: TxEditor: extend messages for send change to ln
Extend TxEditor.get_messages() with errors for the send change to
lightning function so users see why it wasn't used even though it is
enabled.
2026-01-22 10:09:15 +01:00
SomberNight
cc4611f43d util.CallbackManager: follow-up prev: fix deadlock 2026-01-21 17:51:03 +00:00
SomberNight
87540dbe3e util.EventListener: store WeakMethods in CallbackManager to avoid leaks
This patch changes the CallbackManager to use WeakMethods (weakrefs) to
break the ref cycle and allow the GC to clean up the wallet objects.
unregister_callbacks() will also get called automatically, from
EventListener.__del__, to clean up the CallbackManager.

I also added a few unit tests for this.

fixes https://github.com/spesmilo/electrum/issues/10427

-----

original problem:

In many subclasses of `EventListener`, such as `Abstract_Wallet`, `LNWatcher`,
`LNPeerManager`, we call `register_callbacks()` in `__init__`.
`unregister_callbacks()` is usually called in the `stop()` method.

Example - consider the wallet object:
- `Abstract_Wallet.__init__()` calls `register_callbacks()`
- there is a `start_network()` method
- there is a `stop()` method, which calls `unregister_callbacks()`
- typically the wallet API user only calls `stop()` if they also called
  `start_network()`.

This means the callbacks are often left registered, leading to the wallet
objects not getting GC-ed. The GC won't clean them up as
`util.callback_mgr.callbacks` stores strong refs to instance methods
of `Abstract_Wallet`, hence strong refs to the `Abstract_Wallet` objects.

An annoying example is `daemon.check_password_for_directory`, which
potentially creates wallet objects for all wallet files in the datadir.
It simply constructs the wallets, does not call `start_network()` and
neither does it call `stop()`.
2026-01-21 17:49:41 +00:00
ghost43
86070618cd Merge pull request #10429 from f321x/fix_deadlock
plugin: make DeviceMgr.run non-blocking, fix lock
2026-01-21 15:28:20 +00: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
ThomasV
64289d7e99 Merge pull request #10426 from f321x/qml_preferences_section
qml: restructure preferences, security improvements
2026-01-21 11:04:27 +01:00
f321x
d6b6fb8a6b qml: shorten max brightnes preference string
Shorten the max brightness preference string so it fits in a single line
and disable it if the system is not on Android.
2026-01-21 10:37:45 +01:00
f321x
5d31401211 qml: require authentication for message signing
Requests authentication when trying to sign a message if "Payment
Authentication" is enabled.
2026-01-21 10:21:18 +01:00
f321x
8e5ea8e12d qml: protect Address Private Keys from screenshots
Address private keys weren't protected from screenshots.
2026-01-21 10:21:17 +01:00
f321x
aaa314b36e qml: Preferences: disable screenshot protection if !Android
Disables the "Always allow Screenshots" switch in the preferences if the
App isn't running on Android. QML doesn't have screenshot protection
outside of Android so this toggle is misleading.
2026-01-21 10:21:06 +01:00
f321x
f8c44886f4 qml: Preferences: add "Security" section
Adds a separate "Security" section to the qml preferences which contains
security related toggles so the Preferences are less mixed up.
2026-01-21 10:19:29 +01:00
SomberNight
15067be527 wallet: stop() to clean-up lnworker even in offline mode 2026-01-20 22:33:24 +00:00
SomberNight
50b10284ac util.CallbackManager: use sets instead of lists
- to gracefully take duplicate calls of register_callbacks(): should be idempotent now
- as a side-effect, the order of the callbacks is changed and not guaranteed
  - not like anyone should have been relying on it before though
2026-01-20 22:33:21 +00:00
SomberNight
57b26ba473 tests: add basic tests for util.CallbackManager and EventListener 2026-01-20 22:33:18 +00:00
SomberNight
5d41891554 utils/memory_leak: factor out count_objects_in_memory
and use weakrefs, to properly allow using this to test garbage collection
2026-01-20 22:33:07 +00:00
SomberNight
3a8fbabcae utils/memory_leak: fix debug_memusage_dump_random_backref_chain 0 case
handle case where no objects of desired type are found
2026-01-20 22:31:08 +00:00
ThomasV
ac99463e4f Merge pull request #10425 from f321x/qml_password_dialog_wrong_password
qml: PasswordDialog: show error on invalid password
2026-01-20 14:04:30 +01:00
f321x
07f61ebd5a qml: PasswordDialog: show error on invalid password
Currently the PasswordDialog on QML would just close if the user enters
an incorrect password. This is confusing as the user doesn't know why
the dialog closed and if it initiated any action or not.

With the change the PasswordDialog will get the ability to show an error
message and will show "Invalid Password" if an incorrect password is
entered.
I also used it for the password unification warning ("Need to enter
similar password ...") instead of showing a separate popup.
2026-01-20 12:30:31 +01:00
ThomasV
bf1e0103e4 Merge pull request #10423 from SomberNight/202601_qml_open_passwordless_wallets
qml: fix: allow opening passwordless wallets
2026-01-20 09:29:58 +01:00
ghost43
40b54f5c8b Merge pull request #10170 from SomberNight/202508_debug_mem
add/organise memory_leak helpers
2026-01-19 17:56:27 +00:00
ghost43
4394231d89 Merge pull request #10422 from SomberNight/202601_lnpeer_diag_name
lnpeer: log name of wallet file in each line
2026-01-19 17:11:34 +00:00
ghost43
64f6ae829d Merge pull request #10419 from SomberNight/202601_qml_wallet_unlocked
qml: QEDaemon.setPassword to restore invariant wallets are unlocked
2026-01-19 17:10:21 +00:00
SomberNight
6d9ec72853 qml: QEDaemon.setPassword to restore invariant wallets are unlocked
fixes https://github.com/spesmilo/electrum/issues/10415
2026-01-19 17:09:02 +00:00
SomberNight
91f286b422 wallet: minor clean-up and sanity checks for unlock/lock
- unlock() did not handle password=="" well
    instead of the caller converting the arg, as in
    7113cec4c7,
    it is more robust for the function itself to do it
- get_unlocked_password() should never return an invalid password
- add is_unlocked()
2026-01-19 17:07:35 +00:00
SomberNight
5b915fbff8 qml: fix: allow opening passwordless wallets
This must be an old regression.
The GUI was not allowing to open a wallet that did not have a password set:
it prompted for a password and did not accept any string (should at least accept empty "").

Without this, it was only possible to open a passwordless wallet if that was the first wallet the user opened
(as otherwise we would overwrite the empty pw with the pw of the current wallet).
2026-01-19 16:41:57 +00:00
SomberNight
0ae10b2a7b lnpeer: log name of wallet file in each line
- if multiple LN-enabled wallets are open, need to know which peer is for which wallet
- note: LNGossip is a singleton
  - if a wallet is named LNGossip, can't distinguish. I think that's ok.

compare log lines:
before:
```
84.82 | I | lnpeer.Peer.[LNWallet, 034cc6216f-f8dcaa6e] | Disconnecting: GracefulDisconnect('Failed to initialize: TimeoutError()')
17.97 | D | lnpeer.Peer.[LNGossip, 0259d4116d-1618547b] | Sending INIT
```
after:
```
 5.80 | D | lnpeer.Peer.[test_segwit_2, 038863cf8a-fd53ef9c] | Sending CHANNEL_READY
 5.92 | D | lnpeer.Peer.[LNGossip, 038863cf8a-6286ffd4] | Received INIT
```
2026-01-19 16:03:42 +00:00
SomberNight
d5ca2e89d5 contrib/android/Readme.md: add note re adb install --user option 2026-01-19 15:33:38 +00:00
ghost43
ece52b0a4e Merge pull request #10340 from f321x/fingerprint
android: implement biometric authentication (fingerprint)
2026-01-19 15:22:17 +00:00
user
47efb8b108 qml: remove pin code authentication
Completely removes the pin code authentication from qml. The config
option in the wallet preferences has been renamed to "Payment
authentication" and now either asks for the Android system
authentication (Biometric or system pin/password) if enabled or will ask
for the wallet password as fallback.
2026-01-19 14:07:25 +01:00
SomberNight
21248817c9 config: allow setting "forgetconfig" option in file, not just on CLI
- by specifying "default=<VALUE>" in commands.py, <VALUE> would always overwrite what is in the config file
- note `$ ./run_electrum -o setconfig forget_config true` still does not work,
  as that first sets forget_config in memory, and then - by virtue of the setting -
  it refuses to write changes to the config file
  - hence this option would have to be set manually by editing the json
    (or as a CLI flag, as before)

ref https://github.com/spesmilo/electrum/pull/10421#issuecomment-3765862081
2026-01-19 11:30:53 +00:00
ThomasV
99e173e504 Merge pull request #10417 from f321x/save_htlcs
lnpeer: followup #10413
2026-01-16 15:18:01 +01:00
user
e81ac4b7cd lnpeer: followup #10413
Save the updated htlc set in `Peer._fulfill_htlc_set` and
`Peer._fail_htlc_set()` only after the loop iterated through all htlcs.
This potentially improves performance, especially considering that
writing the db can take >100 ms for larger wallets without partial
writes.
2026-01-16 12:16:23 +01:00
ThomasV
43b0098f6c Merge pull request #10414 from SomberNight/202601_wallet_stored_height_fix
addr_sync: update "stored_height" db field immediately on wallet-open
2026-01-16 09:47:39 +01:00
SomberNight
a27e2cc6b5 addr_sync: update "stored_height" db field immediately on wallet-open
Repro steps:
- in qt gui, with network enabled, open wallet1
- open wizard, create wallet2 (restore from seed something that has mined history)
- close both wallets, stop electrum
- start electrum with "-o" offline flag, open wallet2
- observe all txs in history tab show up as "unconfirmed"

The cause is that "stored_height" only gets updated ~on new blocks.
So if you created a wallet and closed it soon, its db would not contain "stored_height."
2026-01-15 16:43:22 +00:00
ghost43
9857534e18 Merge pull request #10413 from SomberNight/202601_mpp_status_htlcs_frozenset
lnutil: change ReceivedMPPStatus.htlcs to frozenset, i.e. immutable
2026-01-15 16:40:26 +00:00
SomberNight
c37b844f66 lnutil: change ReceivedMPPStatus.htlcs to frozenset, i.e. immutable
As ThomasV says:

> ReceivedMPPStatus is a Namedtuple, which is immutable, but it contains
> a mutable field. Since ReceivedMPPStatus is not a StoredObject,
> no patch will be created when the htlcs list is modified, and we may
> end up not saving the change to disk if partial writes are enabled.

patch taken from https://github.com/spesmilo/electrum/pull/10395#pullrequestreview-3634244541
closes https://github.com/spesmilo/electrum/pull/10395

Co-authored-by: f321x <f@f321x.com>
2026-01-15 15:59:14 +00:00
ghost43
5266b3d61e Merge pull request #10371 from accumulator/qml_manual_fee_feerate_edit
qml: allow manual editing of fee/feerate
2026-01-15 14:43:08 +00:00
SomberNight
8a3d9fd758 qml: FeePicker: restrict abs/rate editing to mimic wallet.bump_fee/cpfp 2026-01-14 16:53:30 +00:00
SomberNight
ca597942fd qml: (trivial) qeinvoice: add type hint 2026-01-14 16:13:00 +00:00
ghost43
5db4a16ace Merge pull request #10410 from f321x/fix_10406
qml: fix invalid QEInvoiceParser state
2026-01-14 15:55:18 +00:00
f321x
b599ae7d4a qml: fix invalid QEInvoiceParser state
Fixes the issue described in #10406.
When scanning a lightning invoice we would pass it to
`QEInvoiceParser.fromResolvedPaymentIdentifier()`, however
`fromResolvedPaymentIdentifier()` doesn't reset the state of
`QEInvoiceParser._lnurlData` which is used in QML to evaluate
`payImmediately: invoiceParser.isLnurlPay` in the `onValidationSuccess`
connection.

This change calls `clear()` in `fromResolvedPaymentIdentifier()` to
ensure that `QEInvoiceParser` state gets reset when loading a new invoice.
However when retrieving a bolt11 from a lnurl-pay callback we don't
wan't to reset `QEInvoiceParser._lnurlData` so that `payImmediately` is
true when confirming the lnurl pay dialog, for that I skip calling
`fromResolvedPaymentIdentifier()` and instead call `validateRecipient()`
directly so the `QEInvoiceParser` state doesn't get reset in this case.
2026-01-14 11:42:55 +01:00
f321x
6450187902 qeqrscanner: check requestCode on activity result 2026-01-13 18:10:07 +01:00
user
5dd3dda238 android: implement biometric authentication
Allows to unlock the android app with the android biometric api (e.g.
fingerprint). Can be enabled in the settings.
2026-01-13 18:10:00 +01:00
ThomasV
fa575ab1fc fix test_onion_message (follow-up previous commit) 2026-01-13 17:36:06 +01:00
ThomasV
1845143786 lnpeer: wait_for_received_pending_htlcs_to_get_removed: wait only if peer has been initialized 2026-01-13 17:16:10 +01:00
ThomasV
69af2f77ed Merge pull request #10404 from f321x/fix_10403
ExchangeRate: return NaN if rate is 0
2026-01-13 14:55:20 +01:00
f321x
5199c6c742 ExchangeRate: return NaN if rate is 0
Prevent DivisionByZero exceptions by returning `Decimal('NaN')
instead of `Decimal(0)` if the exchange rate is 0.

Fixes https://github.com/spesmilo/electrum/issues/10403

```
>>> bool(Decimal(0))
False
```
2026-01-13 13:57:14 +01:00
ThomasV
0c29c5006e Merge pull request #10394 from f321x/cosigner_wallet_event_listener
bugfixes: set psbt_nostr event on aio loop and use correct cb in TxEditor
2026-01-13 13:45:06 +01:00