197 Commits

Author SHA1 Message Date
ThomasV b19820dc9d adb.get_spender: subscribe to outputs explicitly, instead of as a side effect
This allows to subscribe only if we need it (for channel closing tx and htlcs)
2026-03-19 14:41:31 +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
f321x 663fcddc0c AddressSynchronizer: invalidate balance cache on spv
There was a race incorrectly counting transactions with one
confirmations to the unconfirmed balance instead of the confirmed
balance.
This happened because the balance cache of AddressSynchronizer got
invalidated after `on_event_blockchain_updated` and then again after
`receive_history_callback`->`add_transaction`, however when calling
`AddressSynchronizer.get_balance()` before the tx got spv verified the
height would still be counted as 0 (unconfirmed), populating the balance
cache again with the unconfirmed balance.
I noticed this only on QML due to timing differences to Qt.
Invalidating the cache in `AddressSynchronizer.add_verified_tx()` after
the tx got verified causes the balance to get recalculated and shown
correctly.
2026-01-07 18:15:08 +01:00
f321x 72f083d2d0 AddressSynchronizer: remove unneccessary loop
This loop seems like a leftover that is not useful anymore, clearing the
cache once has the same effect.
2026-01-07 16:36:07 +01:00
SomberNight a4293c483e transaction: SPV-verify TxInput.block_height and .spent_height 2025-09-24 13:46:35 +00:00
SomberNight b944371ffd adb: change API of util.TxMinedInfo: height() is now always SPV-ed 2025-09-24 13:46:24 +00:00
SomberNight b036eaf3eb lnwatcher: add some type hints 2025-09-08 15:01:50 +00:00
SomberNight f337b4782d lnwatcher: keep watching sweep TXOs that are dust due to high fees
- if fee estimates are high atm, some outputs are not worth to sweep
- however, fee estimates might be only-temporarily very high
  - previously in such a case lnwatcher would just discard outputs as dust,
    and mark the channel REDEEMED (and hence never watch it or try again)
  - now, instead, if the outputs would not be dust if fee estimates were lower,
    lnwatcher will keep watching the channel
    - and if estimates go down, lnwatcher will sweep them then
- relatedly, previously txbatcher.is_dust() used allow_fallback_to_static_rates=True,
    and it erroneously almost always fell back to the static rates (150 s/b) during
	startup (race: lnwatcher was faster than the network managed to get estimates)
	- now, instead, txbatcher.is_dust() does not fallback to static rates,
	  and the callers are supposed to handle NoDynamicFeeEstimates.
	  - I think this makes much more sense. The previous meaning of "is_dust"
	    with the fallback was weird. Now it means: "is dust at current feerates".

fixes https://github.com/spesmilo/electrum/issues/9980
2025-07-08 14:02:52 +00:00
наб e28836eb1d address_synchronizer: add a cache in front of get_utxos()
get_utxos() is called pretty often, both spuriously,
and on focus change, on tab switch, &c.

It blocks as it iterates, functionally, /every/ address the wallet knows of.

On large wallets (like testnet
  vpub5VfkVzoT7qgd5gUKjxgGE2oMJU4zKSktusfLx2NaQCTfSeeSY3S723qXKUZZaJzaF6YaF8nwQgbMTWx54Ugkf4NZvSxdzicENHoLJh96EKg
from #6625 with 11k TXes and 10.5k addresses),
this takes 1.3s of 100%ed CPU usage,
basically in a loop from the UI thread.

get_utxos() is 50-70% of the flame-graph when sampling
a synced wallet process.

This data is a function of the block-chain state,
and we have hooks that notify us of when the block-chain state changes:
we can just cache the result and only re-compute it then.

For example, here's a trace log where get_utxos() has
  print(end - start, len(domain), block_height)
and a transaction is clearing:
  1.3775344607420266 10540 4507192
  0.0010390589013695717 10540 4507192 cached!
  0.001393263228237629 10540 4507192 cached!
  0.0009001069702208042 10540 4507192 cached!
  0.0010241391137242317 10540 4507192 cached!
  ...
  0.00207632128149271 10540 4507192 cached!
  0.001397700048983097 10540 4507192 cached!
  invalidate_cache
  1.4686454269103706 10540 4507192
  0.0012429207563400269 10540 4507192 cached!
  0.0015075239352881908 10540 4507192 cached!
  0.0010459059849381447 10540 4507192 cached!
  0.0009669591672718525 10540 4507192 cached!
  ...
  on_event_blockchain_updated
  invalidate_cache
  1.3897203942760825 10540 4507193
  0.0010689008049666882 10540 4507193 cached!
  0.0010420521721243858 10540 4507193 cached!
  ...
  invalidate_cache
  1.408584670163691 10540 4507193
  0.001336586195975542 10540 4507193 cached!
  0.0009196233004331589 10540 4507193 cached!
  0.0009176661260426044 10540 4507193 cached!
  ...
about 30s of low activity.

Without this patch, the UI is prone to freezing,
running behind, and I wouldn't be surprised if UI thread blocking
on Windows ends up crashing to some extent as the issue notes.
In the log, this manifests as a much slower but consistent
stream of full 1.3-1.4s updates during use,
and every time the window is focused.
2025-06-15 22:08:30 +02:00
наб fdaafd5abf address_synchronizer: apply @with_lock where applicable 2025-06-15 21:22:27 +02:00
ThomasV 420cd1e5ed make wallet stop and restart-able 2025-06-05 19:12:47 +02:00
SomberNight db759765d6 adb.get_tx_height: allow future txs to be partially signed
If the full tx is missing, we should force mempool/confirmed txs to be LOCAL height,
however future txs should not be forced to LOCAL, they should remain FUTURE.

follow-up https://github.com/spesmilo/electrum/commit/197933debf1a31734da86edde2678c2b76987ed3
2025-05-27 18:19:01 +00:00
SomberNight 197933debf adb.get_tx_height: return "LOCAL" height if missing full signed tx
get_tx_height previously did not consider whether the walletDB has the full tx for
the corresponding txid, and could consider a tx even "mined" and spv-ed, even if
we were missing the raw tx.
Now if the full tx is missing or the tx in the db is partial,
get_tx_height considers it to be LOCAL.

In particular the txbatcher, in `run_iteration`,
- first saves a tx as local *unsigned* (to reserve UTXOs),
- then signs it and tries to broadcast it.

The history tx will later transition to local and signed,
after we get back the broadcasted tx via the Synchronizer dance.
In the meantime there is a race where we have an unsigned tx in the history,
but the txid could already transition to mempool or even to mined,
before we download the full raw tx from the server.
During that time window, it makes the adb state more consistent
to just consider the history tx to be local, as done here.
2025-05-26 16:57:51 +00:00
SomberNight 4543192e1a adb: take lock in more places
for example, adb.get_utxos() could previously return an inconsistent result
2025-05-21 18:43:36 +00:00
SomberNight 3b37a920d6 adb/wallet: merge transaction_lock and lock
The distinction was no longer clear.
2025-05-21 18:43:33 +00:00
SomberNight b1f0c6e353 synchronizer: get_transaction should discard tx_height as it can change
tx_height comes from the `get_history` RPC, we then call the `get_transaction` RPC.
By the time the `get_transaction` RPC returns, we might have received another
scripthash status update, called `get_history` again, and updated height for the txid.
Synchronizer._get_transaction() should not call adb.receive_tx_callback() with
the old tx_height (but it was doing exactly that).

Patch to trigger, e.g. regtest failures:
(e.g. for tests.regtest.TestLightningAB.test_extract_preimage)
```
diff --git a/electrum/interface.py b/electrum/interface.py
index 8649652b9c..fce7a1c6de 100644
--- a/electrum/interface.py
+++ b/electrum/interface.py
@@ -991,6 +991,7 @@ class Interface(Logger):
         return res

     async def get_transaction(self, tx_hash: str, *, timeout=None) -> str:
+        await asyncio.sleep(3)
         if not is_hash256_str(tx_hash):
             raise Exception(f"{repr(tx_hash)} is not a txid")
         raw = await self.session.send_request('blockchain.transaction.get', [tx_hash], timeout=timeout)

```
2025-05-15 19:35:15 +00:00
SomberNight 61283fe18b adb: (trivial) receive_tx_callback: make tx_height param kw-only 2025-05-15 19:09:37 +00:00
ThomasV 39224f003d Merge pull request #9636 from SomberNight/202503_avoid_reuse
wallet: add new config option "FREEZE_REUSED_ADDRESS_UTXOS"
2025-03-14 12:56:29 +01:00
ThomasV 8e6be0a36a Remove inheritance between LNWatcher and Watchtower
As LNWatcher is no longer async, there is not enough overlap
between these classes to deserve inheritance
2025-03-14 11:59:56 +01:00
SomberNight d52762a2e8 wallet: add new config option "FREEZE_REUSED_ADDRESS_UTXOS"
Adds a new config option: `WALLET_FREEZE_REUSED_ADDRESS_UTXOS`.
This is based on Bitcoin Core's "avoid_reuse" wallet flag. [0]

This opt-in feature, if enabled:
> Automatically freeze coins received to already used addresses.
> This can eliminate a serious privacy issue where a malicious user can track your spends by sending small payments
> to a previously-paid address of yours that would then be included with unrelated inputs in your future payments.

Note that currently we only have a single coinchooser policy, `CoinChooserPrivacy`,
which interacts well with this option, as it spends all coins from any selected address.
However, if we later add a different coinchooser policy, which allowed "partial spends",
care should be taken re e.g. disallowing using that when this option is set.

Also note that this PR adds this as a config option, but arguably it could be wallet-specific instead,
such as `use_change`.

[0]: https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.19.0.1.md#wallet

closes https://github.com/spesmilo/electrum/issues/7497
2025-03-14 00:47:42 +00:00
SomberNight 838490fea4 adb.add_transaction: try to ser-deser tx early
Previously calling add_transaction with a malformed Transaction obj could
result in an exception late in the flow, after the walletdb was already side-effected.
Rollback of such side-effects is not implemented :/
  but this small patch should at least cover and prevent some common cases.

```
File "/opt/electrum/electrum/address_synchronizer.py", line 358, in add_transaction
  self.db.add_transaction(tx_hash, tx)
File "/opt/electrum/electrum/json_db.py", line 42, in wrapper
  return func(self, *args, **kwargs)
File "/opt/electrum/electrum/wallet_db.py", line 1434, in add_transaction
  tx = tx_from_any(str(tx))
File "/opt/electrum/electrum/transaction.py", line 1339, in tx_from_any
  raise SerializationError(f"Failed to recognise tx encoding, or to parse transaction. "
```
2025-01-10 12:24:26 +00:00
SomberNight 2f1095510c bitcoin.py/transaction.py: API changes: rm most hex usage
Instead of some functions operating with hex strings,
and others using bytes, this consolidates most things to use bytes.

This mainly focuses on bitcoin.py and transaction.py,
and then adapts the API usages in other files.

Notably,
- scripts,
- pubkeys,
- signatures
should be bytes in almost all places now.
2024-04-29 17:10:26 +00:00
SomberNight 7247130aaf adb: get_balance: add comment about #8835
> These kind of checks look incorrect and confused/confusing for me:
> https://github.com/spesmilo/electrum/blob/43a5d034268e4bb6e3dfa3db3fa632642c154d68/electrum/gui/qml/qeinvoice.py#L388
> https://github.com/spesmilo/electrum/blob/43a5d034268e4bb6e3dfa3db3fa632642c154d68/electrum/gui/qt/main_window.py#L1801

> For example, consider creating a local tx that spends all UTXOs in the wallet, and consolidates them to another ismine address.
> Such a tx basically does not change what wallet.get_balance() returns.
> but to the checks listed above, the confirmed balance of the wallet I guess should be 0?
2024-01-19 15:22:28 +00:00
SomberNight de2007e90c adb: simplify get_conflicting_transactions 2024-01-17 18:58:18 +00:00
SomberNight eee61a98cb gui: cpfp: calc parent fee for cpfp using wallet.get_tx_info
I am only making this change as it makes it simpler to do manual testing of CPFP-ing *local* txs.
That is, by changing a few easy-to-find lines of code, I can make the GUI allow CPFP-ing a local tx, but
without this change it errors out with "unknown fee for parent transaction".

If one has an incoming local tx saved in the wallet, adb.get_tx_fee(txid) returns None
(if the tx gets into the mempool, it will use the server-reported value instead).
In contrast, wallet.get_tx_info(tx).fee calls both wallet.get_wallet_delta(tx) and adb.get_tx_fee(txid),
making it more expensive but more complete.
2023-12-29 02:55:11 +00:00
SomberNight e1d0247ce4 wallet: clarify difference between wallet.is_mine and adb.is_mine
related https://github.com/spesmilo/electrum/pull/8699
2023-12-12 14:17:38 +00:00
SomberNight bb76b836a3 addr_sync.receive_tx_callback: rm redundant tx_hash arg 2023-10-24 16:07:30 +00:00
SomberNight 227e257444 (follow-up) wallet: add option to merge duplicate tx outputs 2023-10-24 14:41:39 +00:00
ThomasV 5e09a416a7 address_synchronizer: save stored_height on each block 2023-08-27 11:57:50 +02:00
SomberNight 703ec09355 addr_sync: expand docstring for get_tx_fee 2023-06-13 00:24:56 +00:00
SomberNight 24980feab7 config: introduce ConfigVars
A new config API is introduced, and ~all of the codebase is adapted to it.
The old API is kept but mainly only for dynamic usage where its extra flexibility is needed.

Using examples, the old config API looked this:
```
>>> config.get("request_expiry", 86400)
604800
>>> config.set_key("request_expiry", 86400)
>>>
```

The new config API instead:
```
>>> config.WALLET_PAYREQ_EXPIRY_SECONDS
604800
>>> config.WALLET_PAYREQ_EXPIRY_SECONDS = 86400
>>>
```

The old API operated on arbitrary string keys, the new one uses
a static ~enum-like list of variables.

With the new API:
- there is a single centralised list of config variables, as opposed to
  these being scattered all over
- no more duplication of default values (in the getters)
- there is now some (minimal for now) type-validation/conversion for
  the config values

closes https://github.com/spesmilo/electrum/pull/5640
closes https://github.com/spesmilo/electrum/pull/5649

Note: there is yet a third API added here, for certain niche/abstract use-cases,
where we need a reference to the config variable itself.
It should only be used when needed:
```
>>> var = config.cv.WALLET_PAYREQ_EXPIRY_SECONDS
>>> var
<ConfigVarWithConfig key='request_expiry'>
>>> var.get()
604800
>>> var.set(3600)
>>> var.get_default_value()
86400
>>> var.is_set()
True
>>> var.is_modifiable()
True
```
2023-05-25 17:39:48 +00:00
SomberNight 56fa832563 wallet.get_tx_parents: explicitly handle missing "from address"
(happened to work even without this)
2023-05-03 15:08:56 +00:00
SomberNight 6ac3f84095 wallet/lnworker/etc: add sanity checks to start_network methods
related: https://github.com/spesmilo/electrum/issues/8301
2023-04-17 16:56:57 +00:00
SomberNight b81508cfc0 qml: fix refresh bug in history, for local->unconfirmed tx transition
Previously if a local tx got broadcast, it was still displayed as local
in the history until it got mined (or some other action forced a full refresh).
2023-04-05 13:09:51 +00:00
SomberNight e748345be0 addr_sync: change return type of get_address_history to dict from list 2023-04-05 13:09:47 +00:00
SomberNight db4943ff86 wallet.get_full_history: more consistent sort order
before:
```
>>> [print(wallet.get_tx_status(txid, wallet.adb.get_tx_height(txid))) for txid in list(wallet.get_full_history())[-10:]]
(7, '2023-04-04 16:13')
(7, '2023-04-04 16:13')
(7, '2023-04-04 16:13')
(7, '2023-04-04 16:13')
(0, 'Unconfirmed [20. sat/b, 0.00 MB]')
(2, 'in 2 blocks')
(3, 'Local [180.4 sat/b]')
(3, 'Local [180.2 sat/b]')
(2, 'in 2016 blocks')
(0, 'Unconfirmed [180. sat/b, 0.00 MB]')
```

after:
```
>>> [print(wallet.get_tx_status(txid, wallet.adb.get_tx_height(txid))) for txid in list(wallet.get_full_history())[-10:]]
(7, '2023-04-04 16:13')
(7, '2023-04-04 16:13')
(7, '2023-04-04 16:13')
(7, '2023-04-04 16:13')
(0, 'Unconfirmed [20. sat/b, 0.00 MB]')
(0, 'Unconfirmed [180. sat/b, 0.00 MB]')
(2, 'in 2016 blocks')
(2, 'in 2 blocks')
(3, 'Local [180.4 sat/b]')
(3, 'Local [180.2 sat/b]')
```
2023-04-04 18:32:53 +00:00
SomberNight f0e89b3ef6 addr_sync: migrate usages of get_txpos to get_tx_height
the return value of get_txpos is fine-tuned for sorting... other uses are highly questionable.
2023-04-04 17:49:46 +00:00
SomberNight f8f0af4a2f qml: history: add some support for future txs
- they are now distinguished from local by the status text "in %d blocks"
- this status text needs updating occasionally: on new blocks,
  and some lnwatcher interactions, hence new event: "adb_set_future_tx"
2023-04-04 17:39:58 +00:00
SomberNight 9097d5e43d addr_sync.set_future_tx: clarify wanted_height off-by-one semantics 2023-04-04 13:55:12 +00:00
SomberNight 446879ade0 lnwatcher.maybe_redeem: wanted_height should always be absolute
previously, if prev_height.height was <= 0, lnwatcher was calling adb.set_future_tx()
with weird wanted_height values (with ~sweep_info.csv_delay)
2023-04-04 13:37:10 +00:00
ThomasV 54bb42f82c adb: take locks in get_balance. fixes #8200 2023-04-01 10:37:38 +02:00
SomberNight 81772faf6c wallet: add_input_info to no longer do network requests
- wallet.add_input_info() previously had a fallback to download parent
  prev txs from the network (after a lookup in wallet.db failed).
  wallet.add_input_info() is not async, so the network request cannot
  be done cleanly there and was really just a hack.
- tx.add_info_from_wallet() calls wallet.add_input_info() on each txin,
  in which case these network requests were done sequentially, not concurrently
- the network part of wallet.add_input_info() is now split out into new method:
  txin.add_info_from_network()
- in addition to tx.add_info_from_wallet(), there is now also tx.add_info_from_network()
  - callers of old tx.add_info_from_wallet() should now called either
    - tx.add_info_from_wallet(), then tx.add_info_from_network(), preferably in that order
    - tx.add_info_from_wallet() alone is sufficient if the tx is complete,
      or typically when not in a signing context
- callers of wallet.bump_fee and wallet.dscancel are now expected to have already
  called tx.add_info_from_network(), as it cannot be done in a non-async context
  (but for the common case of all-inputs-are-ismine, bump_fee/dscancel should work regardless)
- PartialTxInput.utxo was moved to the baseclass, TxInput.utxo
2023-03-12 00:21:57 +00:00
SomberNight 7584ba00ce wallet: kill negative conf numbers for TxMinedInfo
fixes https://github.com/spesmilo/electrum/issues/8240

#8240 was triggering an AssertionError in wallet.get_invoice_status,
as code there was assuming conf >= 0. To trigger, force-close
a LN channel, and while the sweep is waiting on the CSV, try to
make a payment in the Send tab to the ismine change address used
for the sweep in the future_tx. (order of events can also be reversed)
2023-03-09 14:59:08 +00:00
ThomasV 27ce9d88c3 follow-up 2ed71579c3: remove wrong assert 2023-03-04 09:04:50 +01:00
ThomasV 2ed71579c3 privacy analysis: detect address reuse
add tx position to get_addr_io
2023-03-04 08:53:49 +01:00
ThomasV e4273e5ab9 utxo privacy analysis:
- add a new event, 'adb_removed_tx'
 - new wallet method: get_tx_parents
 - number of parents is shown in coins tab
 - detailed list of parents is shown in dialog
2023-02-25 11:46:47 +01:00
SomberNight 90f1279d9a addr_sync: (trivial) don't use private utxo._is_coinbase_output 2023-02-08 23:32:28 +00:00
SomberNight 7d42676785 qt tx dialog: make scid and addr texts clickable in IO fields
based on:
https://github.com/Electron-Cash/Electron-Cash/commit/7eea0b6dae3d8ebc3823ceb7abf6093119b915c0
https://github.com/Electron-Cash/Electron-Cash/commit/52d845017c99d5c9a02df6b9af7825d2111b8def
2023-02-03 14:49:01 +00:00
ThomasV 7625b4e63b follow-up d6febb5c12 2023-01-26 11:13:35 +01:00
ThomasV d6febb5c12 Display mined tx outputs as ShortIDs instead of full transaction outpoints.
ShortIDs were originally designed for lightning channels, and are now
understood by some block explorers.

This allows to remove one column in the UTXO tab (height is redundant).

In the transaction dialog, the space saving ensures that all inputs fit
into one line (it was not the case previously with p2wsh addresses).
For clarity and consistency, the ShortID is displayed for both inputs
and outputs in the transaction dialog.
2023-01-26 10:48:28 +01:00