Commit Graph

9166 Commits

Author SHA1 Message Date
f321x f0fc8a9691 qt: add changelog action to help menu in toolbar
Adds a "Changelog" action to the "Help" menu of the toolbar. This allows
users to open the RELEASE-NOTES file in the browser (from github).
2026-01-23 10:20:54 +01:00
ThomasV d2882371a4 bump version number to 4.7.0 2026-01-22 16:22:42 +01:00
ghost43 e286a49340 Merge pull request #10428 from SomberNight/202601_wallet_objs_not_gced
fix util.CallbackManager memory leak
2026-01-22 15:05:22 +00:00
SomberNight 08fa871f39 update block header checkpoints 2026-01-22 14:55:02 +00:00
ThomasV d9091784e7 update locale 2026-01-22 14:02:19 +01:00
ThomasV 16c9bc25b7 Merge pull request #10399 from f321x/qt_sort_cosigners
qt: WalletInfoDialog: sort keystores by root fingerprint
2026-01-22 10:35:04 +01:00
ThomasV 1ae74088e9 Merge pull request #10247 from f321x/improve_send_change_to_ln
qt: improve send_change_to_lightning feedback
2026-01-22 10:31:05 +01:00
f321x 2d97717e99 qt: TxEditor: restructure submarine payment messages
Show:
error
amount details
\n
proposed action

instead of:
error
proposed action
amount details
2026-01-22 10:10:10 +01:00
f321x 1b28e6bf73 TxEditor: move swap request to TxEditor
Moves the logic requesting the forward swap into the TxEditor so it can
use the open transport and doesn't have to reconnect to the relays
again.

Also disables the "Preview" button in the TxEditor when the transaction will
send change to lightning.
This should prevent the user from saving the transaction to history and
broadcasting it later or exporting it and broadcasting it through some
external way.
Broadcasting needs to happen directly after the TxEditor so we can send
the second rpc call to the swapserver and await the incoming htlcs
before broadcasting the (funding-) transaction.
2026-01-22 10:09:29 +01:00
f321x a1841600a1 TxEditor: update dynamically based on swap transport
Update the TxEditor (onchain tab) if Send change to lightning is enabled
and the swap transport changes. Connect to swap transport if send change
to lightning gets enabled or if it is enabled and the TxEditor gets
opened.
This allows to nicely show the swap fees without blocking the UI to wait
until the swap manager gets initialized.
2026-01-22 10:09:28 +01:00
f321x 7b828a8317 wallet: check swap provider liquidity for send change to ln
Check the swap providers liquidity as well if we try to send change to
lightning in `make_unsigned_transaction`. It is now expected that the
swap_manager is already initialized when calling
`make_unsigned_transaction`, otherwise no dummy output will get added.
2026-01-22 10:09:26 +01:00
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
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
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 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
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
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
    https://github.com/spesmilo/electrum/commit/7113cec4c724c2a443031c71cc8d08fee5e2f385,
    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
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
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
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
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 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