Commit Graph

203 Commits

Author SHA1 Message Date
SomberNight
af11ebb3c3 interface: _search_headers_backwards: add an assert 2025-06-09 13:12:35 +00:00
SomberNight
b01e69cf45 interface: split request_chunk method 2025-06-09 13:12:31 +00:00
SomberNight
57bb98dc7f blockchain: parameterise magic number 2016 as CHUNK_SIZE 2025-06-09 13:11:59 +00:00
SomberNight
27599ac537 interface: small clean-up. intro ChainResolutionMode.
- type hints
- minor API changes
- no functional changes
2025-06-06 16:42:15 +00:00
SomberNight
57704600d9 interface: make sure interface.taskgroup gets cleaned up
follow-up 0ce89b6d54
2025-06-02 15:24:00 +00:00
ThomasV
0ce89b6d54 PaddedRSTransport: fix busy loop in poll_sbuffer (follow-up 43ca469774)
According to the asyncio documentation:

> When a task is cancelled, asyncio.CancelledError will be raised in the task at the next opportunity.

I guess the 'next opportunity' means the next await statement.
Here the issue is that the task was not awaiting ever.

Note: ElectrumX needs a similar patch
2025-06-02 13:38:54 +02:00
SomberNight
c9ed8779fc interface: address feedback for PaddedRSTransport 2025-05-29 17:29:33 +00:00
SomberNight
447052b4ff interface: add padding and some noise to protocol messages
basic countermeasures against traffic analysis
2025-05-29 17:29:30 +00:00
ThomasV
853b793bef rm verbosity_shortcuts option (unused, redundant) 2025-05-29 16:20:41 +02:00
SomberNight
bf168ce6f8 interface: log block height of main interface on new block
follow-up 7a7c0f1606
- before ref commit, we logged new headers for every interface
- after ref commit, we logged new header only for fastest interface
- now, we log new header for fastest interface and for main interface
  - useful to see if main interface is very slow
2025-05-28 17:05:13 +00:00
SomberNight
351cc6abd9 Revert "interface: add padding and some noise to protocol messages"
Unforeseen issues. Needs more work..

This reverts commit 097eabed1f.
2025-05-08 18:34:07 +00:00
SomberNight
097eabed1f interface: add padding and some noise to protocol messages
basic countermeasures against traffic analysis
2025-05-08 14:35:44 +00:00
ThomasV
840243e029 separate fee policy from config
- Wallet.make_unsigned_transaction takes a FeePolicy parameter
 - fee sliders act on a FeePolicy instead of config
 - different fee policies may be used for different purposes
 - do not detect dust outputs in lnsweep, delegate that to lnwatcher
2025-03-05 10:29:26 +01:00
ThomasV
7a7c0f1606 remove 'skipping header' lines from log, to make it less verbose 2024-11-13 11:43:11 +01:00
Sander van Grieken
f4520b9e0d network: use TOR stream isolation
also refactor, for proxy instantiation, use Network instance, not a proxy dict.
2024-10-25 01:10:58 +02:00
SomberNight
92d6792b38 interface: disable ssl.VERIFY_X509_STRICT for self-signed certs
The "ssl.VERIFY_X509_STRICT" flag for openssl verification has been enabled by default in python 3.13+ (it was disabled before that).
see https://github.com/python/cpython/issues/107361
and https://discuss.python.org/t/ssl-changing-the-default-sslcontext-verify-flags/30230/16

We explicitly disable it for self-signed certs, thereby restoring the pre-3.13 defaults,
as it seems to break lots of servers.

For example, using python 3.13 (or setting `sslc.verify_flags |= ssl.VERIFY_X509_STRICT`),
- I can connect to `btc.electroncash.dk:60002:s`
- but not to `electrum.emzy.de:50002:s`
despite both using self-signed certs.

We should investigate more why exactly "strict" verification fails for some self-signed certs and not for others,
and make sure that at least newly generated certs adhere to the stricter requirements (e.g. update guide in e-x?).
2024-10-18 16:22:47 +00:00
SomberNight
02a9ab80be interface: nicer error for CA-signed "Hostname mismatch" certs
Previously when encountering a CA-signed cert that failed verification with "Hostname mismatch",
we would
1. erroneously mark it as self-signed
2. save its cert to pin it
3. when connecting to it later, and being served a CA-signed cert, we would reject the connection
  - I think this is because we use the saved cert (the peer cert, just the last cert in the chain) as if it was a root CA,
    and then during the connection we try to verify against that root. This fails as we are served a different root then.
Error logged in step(3):
```
  3.85 | W | i/interface.[wirg2tsto7rme7n26lkd3ivbvxmjyy2pktlozwjuep22jcsfsghfqbqd.onion:50002] | Cannot connect to main server due to SSL error (maybe cert changed compared to "/home/user/.electrum/testnet/certs/wirg2tsto7rme7n26lkd3ivbvxmjyy2pktlozwjuep22jcsfsghfqbqd.onion"). Exc: ConnectError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)'))
```

This commit fixes step(1), we won't mark the cert as self-signed, instead the error is propagated out and the connection closed.
```
 35.05 | I | i/interface.[wirg2tsto7rme7n26lkd3ivbvxmjyy2pktlozwjuep22jcsfsghfqbqd.onion:50002] | disconnecting due to: ErrorGettingSSLCertFromServer(ConnectError(SSLCertVerificationError(1, "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'wirg2tsto7rme7n26lkd3ivbvxmjyy2pktlozwjuep22jcsfsghfqbqd.onion'. (_ssl.c:1007)")))
```

Compare:
- SSLCertVerificationError(1, "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'wirg2tsto7rme7n26lkd3ivbvxmjyy2pktlozwjuep22jcsfsghfqbqd.onion'. (_ssl.c:1007)")
  - verify_code=62
- SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1007)')
  - verify_code=18

Note: the verify_code constants look stable, though they might be openssl-specific. I guess that's ok(?)
140540189c/include/openssl/x509_vfy.h.in (L224)
2024-06-07 14:56:03 +00:00
SomberNight
27f09c1f9f interface: also trigger 'blockchain_updated' during initial sync
In fact, semantically it might be more correct to only trigger 'blockchain_updated' and not 'network_updated' here...
Anyway, 'blockchain_updated' should be triggered whenever the Blockchain object gets longer (or changes otherwise).

In particular, in qml, the NetworkOverview only updates the displayed height on 'blockchain_updated'.
2024-05-22 14:34:37 +00:00
SomberNight
9b08eec491 interface: (trivial) clean-up timeouts 2024-03-14 15:20:06 +00:00
SomberNight
fef895295f interface: also log cancellations in send_request 2024-03-14 12:52:11 +00:00
SomberNight
8931420938 interface: log: silence some tracebacks
```
191.73 | D | i/interface.[btc.electroncash.dk:60002] | (disconnect) trace for RPCError(-32603, 'internal error: bitcoind request timed out')
Traceback (most recent call last):
  File "...\electrum\electrum\interface.py", line 514, in wrapper_func
    return await func(self, *args, **kwargs)
  File "...\electrum\electrum\interface.py", line 537, in run
    await self.open_session(ssl_context)
  File "...\electrum\electrum\interface.py", line 687, in open_session
    async with self.taskgroup as group:
  File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
    await self.join()
  File "...\electrum\electrum\util.py", line 1309, in join
    task.result()
  File "...\electrum\electrum\interface.py", line 724, in request_fee_estimates
    async with OldTaskGroup() as group:
  File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
    await self.join()
  File "...\electrum\electrum\util.py", line 1309, in join
    task.result()
  File "...\electrum\electrum\interface.py", line 1128, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "...\electrum\electrum\interface.py", line 169, in send_request
    response = await util.wait_for2(
  File "...\electrum\electrum\util.py", line 1383, in wait_for2
    return await asyncio.ensure_future(fut, loop=get_running_loop())
  File "...\aiorpcX\aiorpcx\session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "...\aiorpcX\aiorpcx\session.py", line 512, in _send_concurrent
    return await future
aiorpcx.jsonrpc.RPCError: (-32603, 'internal error: bitcoind request timed out')
```

```
 93.60 | E | asyncio | Task exception was never retrieved
future: <Task finished name='Task-7385' coro=<Interface.get_estimatefee() done, defined at ...\electrum\electrum\interface.py:1123> exception=RPCError(-32603, 'internal error: bitcoind request timed out')>
Traceback (most recent call last):
  File "...\electrum\electrum\interface.py", line 1132, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "...\electrum\electrum\interface.py", line 169, in send_request
    response = await util.wait_for2(
  File "...\electrum\electrum\util.py", line 1383, in wait_for2
    return await asyncio.ensure_future(fut, loop=get_running_loop())
  File "...\aiorpcX\aiorpcx\session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "...\aiorpcX\aiorpcx\session.py", line 512, in _send_concurrent
    return await future
aiorpcx.jsonrpc.RPCError: (-32603, 'internal error: bitcoind request timed out')
```
2023-08-10 14:22:43 +00:00
SomberNight
d51f00e2a3 asyncio.wait_for() is too buggy. use util.wait_for2() instead
wasted some time because asyncio.wait_for() was suppressing cancellations. [0][1][2]
deja vu... [3]

Looks like this is finally getting fixed in cpython 3.12 [4]
So far away...
In attempt to avoid encountering this again, let's try using
asyncio.timeout in 3.11, which is how upstream reimplemented wait_for in 3.12 [4], and
aiorpcx.timeout_after in 3.8-3.10.

[0] https://github.com/python/cpython/issues/86296
[1] https://bugs.python.org/issue42130
[2] https://bugs.python.org/issue45098
[3] https://github.com/kyuupichan/aiorpcX/issues/44
[4] https://github.com/python/cpython/pull/98518
2023-08-04 18:18:21 +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
312f2641e7 don't use bare except
use "except Exception", or if really needed explicitly "except BaseException"
2023-04-24 12:58:01 +00:00
SomberNight
8291018c0e interface: workaround electrs erroring on 'blockchain.estimatefee'
15.00 | E | i/interface.[electrum.blockstream.info:50002] | Exception in run: ProtocolError(-32600, 'ill-formed response error object: cannot estimate fee for 25 blocks')
Traceback (most recent call last):
  File "...\electrum\electrum\util.py", line 1261, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\interface.py", line 516, in wrapper_func
    return await func(self, *args, **kwargs)
  File "...\electrum\electrum\interface.py", line 539, in run
    await self.open_session(ssl_context)
  File "...\electrum\electrum\interface.py", line 689, in open_session
    async with self.taskgroup as group:
  File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
    await self.join()
  File "...\electrum\electrum\util.py", line 1423, in join
    task.result()
  File "...\electrum\electrum\interface.py", line 726, in request_fee_estimates
    async with OldTaskGroup() as group:
  File "...\aiorpcX\aiorpcx\curio.py", line 304, in __aexit__
    await self.join()
  File "...\electrum\electrum\util.py", line 1423, in join
    task.result()
  File "...\electrum\electrum\interface.py", line 1128, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "...\electrum\electrum\interface.py", line 171, in send_request
    response = await asyncio.wait_for(
  File "...\Python310\lib\asyncio\tasks.py", line 408, in wait_for
    return await fut
  File "...\aiorpcX\aiorpcx\session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "...\aiorpcX\aiorpcx\session.py", line 512, in _send_concurrent
    return await future
  File "...\aiorpcX\aiorpcx\jsonrpc.py", line 721, in receive_message
    item, request_id = self._protocol.message_to_item(message)
  File "...\aiorpcX\aiorpcx\jsonrpc.py", line 273, in message_to_item
    return cls._process_response(payload)
  File "...\aiorpcX\aiorpcx\jsonrpc.py", line 220, in _process_response
    raise cls._error(code, message, False, request_id)
aiorpcx.jsonrpc.ProtocolError: (-32600, 'ill-formed response error object: cannot estimate fee for 25 blocks')
2023-03-30 15:34:27 +00:00
SomberNight
04df286519 interface: fix ServerAddr.from_str_with_inference() for raw IPv6 addr
and add tests
2023-03-30 00:59:13 +00:00
SomberNight
4915db52b9 interface: fix DeprecationWarnings re SSLContext
```
...\electrum\electrum\interface.py:585: DeprecationWarning: ssl.SSLContext() without protocol argument is deprecated.
  sslc = ssl.SSLContext()
...\electrum\electrum\interface.py:585: DeprecationWarning: ssl.PROTOCOL_TLS is deprecated
  sslc = ssl.SSLContext()
```
2022-05-21 18:43:32 +02:00
SomberNight
d7678e14b5 interface: bypass proxy for servers on localhost
closes https://github.com/spesmilo/electrum/issues/3126
closes https://github.com/spesmilo/electrum/issues/7256

I am unsure if this would be safe to do for the more general "server running on private ip" case,
so this is restricted to localhost only atm.
2022-05-16 22:24:07 +02:00
SomberNight
b3d8b4603f interface: trigger fewer 'blockchain_updated' notifications
The Qt GUI refreshes all tabs on 'blockchain_updated', which is expensive for very large wallets.
Previously, on every new block, the GUI would get one notification for each connected interface,
now only the fastest interface triggers it.

(was testing with a wallet where refreshing all tabs takes 15 seconds, and 10*15 seconds is
even more annoying...)
2022-05-14 01:20:22 +02:00
SomberNight
443a76240d interface.get_block_header: assert height >= 0 2022-05-04 17:53:13 +02:00
SomberNight
b7dd51612e asyncio: use loop.create_future() instead of asyncio.Future()
from https://docs.python.org/3.10/library/asyncio-future.html#asyncio.Future :
> the recommended way to create a Future object is to call loop.create_future().
> This way alternative event loop implementations can inject their own optimized implementations of a Future object.
2022-04-26 20:24:04 +02:00
coval3nte
f60bbf96d8 fix 2022-02-28 15:17:56 +01:00
coval3nte
bbb96b6e25 fix get_balance method 2022-02-28 14:16:43 +01:00
SomberNight
7d74fd0201 interface: add comment about monitor_connection 2022-02-23 18:37:21 +01:00
SomberNight
f4cfc6c7c3 interface: set got_disconnected earlier
related: https://github.com/spesmilo/electrum/issues/7677
related: prev commit
2022-02-22 15:22:08 +01:00
SomberNight
2acecc5859 interface: speed up handle_disconnect via shorter flush-buffer-timeout
Note in particular that _RSClient.__aexit__ calls session.close()
so a lot of time can pass between interface.taskgroup raising and
handle_disconnect seeing the exception.

related https://github.com/spesmilo/electrum/issues/7677
2022-02-22 15:16:55 +01:00
SomberNight
d7af868ed8 network: test if interface is alive before iface.taskgroup.spawn
closes https://github.com/spesmilo/electrum/issues/7677

```
 E/n | network | taskgroup died.
 Traceback (most recent call last):
   File "/opt/electrum/electrum/network.py", line 1204, in main
     [await group.spawn(job) for job in self._jobs]
   File "/home/voegtlin/.local/lib/python3.8/site-packages/aiorpcx/curio.py", line 297, in __aexit__
     await self.join()
   File "/opt/electrum/electrum/util.py", line 1255, in join
     task.result()
   File "/opt/electrum/electrum/network.py", line 1277, in _maintain_sessions
     await maintain_main_interface()
   File "/opt/electrum/electrum/network.py", line 1268, in maintain_main_interface
     await self._ensure_there_is_a_main_interface()
   File "/opt/electrum/electrum/network.py", line 1245, in _ensure_there_is_a_main_interface
     await self._switch_to_random_interface()
   File "/opt/electrum/electrum/network.py", line 648, in _switch_to_random_interface
     await self.switch_to_interface(random.choice(servers))
   File "/opt/electrum/electrum/network.py", line 714, in switch_to_interface
     await i.taskgroup.spawn(self._request_server_info(i))
   File "/home/voegtlin/.local/lib/python3.8/site-packages/aiorpcx/curio.py", line 204, in spawn
     self._add_task(task)
   File "/home/voegtlin/.local/lib/python3.8/site-packages/aiorpcx/curio.py", line 150, in _add_task
     raise RuntimeError('task group terminated')
 RuntimeError: task group terminated
```

I believe the "suppress spurious cancellations" block was added as SilentTaskGroup raised
CancelledError instead of RuntimeError for this scenario.
2022-02-21 20:09:26 +01:00
sgmoore
3f20215d03 trivial: minor grammar fixes
closes https://github.com/spesmilo/electrum/pull/7664
closes https://github.com/spesmilo/electrum/pull/7665
closes https://github.com/spesmilo/electrum/pull/7666
closes https://github.com/spesmilo/electrum/pull/7667
2022-02-17 15:36:13 +01:00
SomberNight
c9c094cfab requirements: bump min aiorpcx to 0.22.0
aiorpcx 0.20 changed the behaviour/API of TaskGroups.
When used as a context manager, TaskGroups no longer propagate
exceptions raised by their tasks. Instead, the calling code has
to explicitly check the results of tasks and decide whether to re-raise
any exceptions.
This is a significant change, and so this commit introduces "OldTaskGroup",
which should behave as the TaskGroup class of old aiorpcx. All existing
usages of TaskGroup are replaced with OldTaskGroup.

closes https://github.com/spesmilo/electrum/issues/7446
2022-02-15 18:22:44 +01:00
SomberNight
c131831373 util: rm SilentTaskGroup. this does not seem to be needed anymore
I think this was originally needed due to incorrect management of group lifecycles,
which our current code is doing better.

also note that if we needed this, in newer aiorpcx, the name of
the field was ~changed from `_closed` to `joined`:
239002689a
2022-02-15 18:22:40 +01:00
SomberNight
3f3212e94d some clean-ups now that we require python 3.8
In particular, asyncio.CancelledError no longer inherits Exception (it inherits BaseException directly)
2022-02-15 18:22:36 +01:00
SomberNight
2599010eed interface: scripthash.get_history: fix tx order check
logic bug
2021-11-11 15:28:16 +01:00
SomberNight
7ffb2c3cb0 config: (trivial) add some type hints and rm unused variable 2021-04-15 19:00:46 +02:00
SomberNight
3c019c2f9c daemon/wallet/network: make stop() methods async 2021-03-09 17:52:36 +01:00
SomberNight
34413a9c30 python 3.6 compat: asyncio.Task.set_name was added in 3.8 2021-03-05 20:11:54 +01:00
SomberNight
064670bd75 network: close interfaces more aggressively (abort after 2 seconds)
fixes #7083
2021-03-04 17:47:49 +01:00
SomberNight
ff485cee62 use functools.wraps() for some wrappers
to help debugging
2021-03-04 16:44:13 +01:00
SomberNight
228c4b4597 synchronizer: better handle history-status mismatch
When receiving the history of an address, the client behaved unexpectedly
if either of two checks failed.
The client checked that the txids in the history are unique, and that the
history matches the previously announced status. If either failed, it
would just log a line and do nothing. Importantly, the synchronizer could
even consider itself is_up_to_date, i.e. the GUI could show the wallet is
synced.

This is now changed such that:
- if the txid uniqueness test fails, we simply disconnect
- if the history is not consistent with previously announced status,
  we wait a bit, make sure is_up_to_date is False in the meantime,
  and then potentially disconnect
See rationale for these in the comments.

related: https://github.com/spesmilo/electrum/issues/7058#issuecomment-783613084
2021-02-24 12:32:54 +01:00
SomberNight
26d73cba0e interface.get_history: enforce sorted order of heights
related: #7058
2021-02-23 02:13:16 +01:00
SomberNight
1ee99cf9c4 interface: "block.headers": nicer error if server uses too low 'max'
related 4ff6a9c4f0
2021-02-18 20:40:38 +01:00