1
0
Commit Graph

155 Commits

Author SHA1 Message Date
SomberNight
bd492fbd14 cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.

With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).

As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.

----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
	from electrum.util import UserFacingException
	raise UserFacingException("heyheyhey")

@command('')
async def errorbad(self):
	raise Exception("heyheyhey")
```

----------
(before change)

CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```

CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```

RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```

----------
(after change)

CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
    response['result'] = await f(*params)
  File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
    result = await func(*args, **kwargs)
  File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
    return await func(*args, **kwargs)
  File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
    raise Exception("heyheyhey")
Exception: heyheyhey

internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```

CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
  0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
  File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
    result = fut.result()
  File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
    return self.__get_result()
  File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
    raise self._exception
  File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
    result = await func(*args, **kwargs)
  File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
    return await func(*args, **kwargs)
  File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
    raise Exception("heyheyhey")
Exception: heyheyhey
```

RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n  File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n    response['result'] = await f(*params)\n  File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n    return await func(*args, **kwargs)\n  File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n    raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
Sander van Grieken
7ca9b735d5 daemon: refactor load_wallet to not just return None, but raise specific exceptions.
The following exceptions should be expected:
FileNotFoundError: given wallet path does not exist
StorageReadWriteError: given file is not readable/writable or containing folder is not writable
InvalidPassword: wallet requires a password but no password or an invalid password was given
WalletFileException: any internal wallet data issue. specific subclasses can be caught separately:
-  WalletRequiresSplit: wallet needs splitting (split_data passed in Exception)
-  WalletRequiresUpgrade: wallet needs upgrade, and no upgrade=True was passed to load_wallet
-  WalletUnfinished: wallet file contains an action and needs additional information to finalize. (WalletDB passed in exception)

Removed qml/qewalletdb.py

This patch also fixes load_wallet calls in electrum/scripts and adds a qml workaround for dialogs opening and closing so
fast that the dialog opened==true property change is missed (which we need to manage the dialog/page stack)
2023-10-10 17:42:07 +02:00
ThomasV
68159b3ef6 walletDB: replace 'manual_upgrades' parameter with 'upgrade', with opposite semantics 2023-09-22 15:02:07 +02:00
ThomasV
b5bc5ff9ed Separate WalletDB from storage upgrades.
Make sure that WalletDB.data is always a StoredDict.
Perform db upgrades in a separate class, since they
operate on a dict object.
2023-09-22 15:02:06 +02:00
SomberNight
1dfc1d567b follow-up prev 2023-08-24 17:30:06 +00:00
SomberNight
90f39bce88 run_electrum: have daemon manage Plugins object, and call Plugins.stop
Plugins.stop was never called, so the Plugins thread only stopped
because of the is_running() check in run(), which triggers too late:
the Plugins thread was stopping after the main thread stopped.

E.g. playing around in the qt wizard with wallet creation for a Trezor,
and closing the wizard (only window):
``` 24.85 | E | p/plugin.Plugins |
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/util.py", line 386, in run_jobs
    job.run()
  File "/home/user/wspace/electrum/electrum/plugin.py", line 430, in run
    client.timeout(cutoff)
  File "/home/user/wspace/electrum/electrum/plugin.py", line 363, in wrapper
    return run_in_hwd_thread(partial(func, *args, **kwargs))
  File "/home/user/wspace/electrum/electrum/plugin.py", line 355, in run_in_hwd_thread
    fut = _hwd_comms_executor.submit(func)
  File "/usr/lib/python3.10/concurrent/futures/thread.py", line 167, in submit
    raise RuntimeError('cannot schedule new futures after shutdown')
RuntimeError: cannot schedule new futures after shutdown
```
2023-08-24 17:15:14 +00:00
ThomasV
b96cc82333 Make storage a field of db
This comes from the jsonpatch_new branch.
I rather have in master now, because it touches a lot of filese.
2023-08-18 08:08:31 +02:00
ThomasV
9f5f802cd1 config: save ports instead of net addresses (follow-up 012ce1c1bb) 2023-08-11 08:12:54 +02:00
ThomasV
012ce1c1bb Remove SSL options from config.
This is out of scope for Electrum; HTTP services that require SSL
should be exposed to the world through a reverse proxy.
2023-08-10 17:24:29 +02:00
SomberNight
8b195ee77a cli: "./run_electrum daemon -d" to block until daemon becomes ready
Without this,
`$ python3 -m unittest electrum.tests.regtest.TestUnixSockets.test_unixsockets`
was failing on my machine but succeeding on CI, due to timing differences.
2023-08-03 17:21:05 +00:00
SomberNight
58a9904a34 daemon: rm "daemon_jobs". maybe makes _run API less error-prone
(follow-up prev)
2023-08-03 17:21:01 +00:00
SomberNight
d65aa3369f daemon: split standardize_path from daemon._wallets keying
- standardize_path is made more lenient: it no longer calls os.path.realpath,
  as that was causing issues on Windows with some mounted drives
- daemon._wallets is still keyed on the old strict standardize_path, but
  filesystem operations in WalletStorage use the new lenient standardize_path.
  - daemon._wallets is strict to forbid opening the same logical file twice concurrently
  - fs operations however work better on the non-resolved paths, so they use those

closes https://github.com/spesmilo/electrum/issues/8495
2023-06-30 11:15:16 +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
4219022c2e fix flake8-bugbear B023
B023 Function definition does not bind loop variable 'already_selected_buckets_value_sum'

in keepkey/qt.py, looks like this was an actual bug
(fixed in trezor plugin already: 52a4810752 )
2023-04-24 13:00:07 +00:00
SomberNight
1530668960 qt/qml: delay starting network until after first-start-network-setup
The qt, qml, and kivy GUIs have a first-start network-setup screen
that allows the user customising the network settings before creating a wallet.
Previously the daemon used to create the network and start it, before this screen,
before the GUI even starts. If the user changed network settings, those would
be set on the already running network, potentially including restarting the network.

Now it becomes the responsibility of the GUI to start the network, allowing this
first-start customisation to take place before starting the network at all.
The qt and the qml GUIs are adapted to make use of this. Kivy, and the other
prototype GUIs are not adapted and just start the network right away, as before.
2023-03-30 00:59:02 +00:00
SomberNight
512b63c424 exchange_rate: FxThread does not need network 2023-03-29 16:41:09 +00:00
SomberNight
9df5f55a1f password unification: bugfix, now passes test cases
fixes https://github.com/spesmilo/electrum/issues/8259

note that technically this is an API change for
- wallet.check_password
- wallet.update_password
- storage.check_password
2023-03-20 20:07:53 +00:00
SomberNight
11f06d860e tests: add more tests for daemon.update_password_for_directory 2023-03-20 20:05:31 +00:00
SomberNight
ad0b853cd9 invoices: improve perf by caching lnaddr even earlier
During wallet-open, we load all invoices/payreqs. This involved decoding the lnaddrs twice.
Now we only decode once.

For a wallet with ~1000 payreqs, this noticeably sped up wallet-open:
(before:)
8.83 | D | util.profiler | Daemon._load_wallet 6.4317 sec
(after:)
5.69 | D | util.profiler | Daemon._load_wallet 3.4450 sec

It is very expensive to parse all the lnaddrs...
2023-02-08 23:36:36 +00:00
SomberNight
d3a10c4225 commands: allow cmd to launch new gui window for wallet
e.g. imagine electrum Qt gui is already running, with some wallet (wallet_1) open,
running `$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/wallet_2` should
open a new window, for wallet_2.

related https://github.com/spesmilo/electrum/issues/8188#issuecomment-1419444675
2023-02-06 17:55:56 +00:00
SomberNight
9039ec1dc4 payserver: make daemon_wallet_loaded hook reliable
daemon.load_wallet() often early-returns,
e.g. in Qt case, for storage-encrypted wallets.

closes https://github.com/spesmilo/electrum/issues/8118
2023-01-13 00:58:02 +00:00
SomberNight
7b6274eaff daemon: better error msg if rpchost/rpcport is badly configured
old traceback:
```
$ ./run_electrum --testnet -o setconfig rpchost qweasdfcsdf
$ ./run_electrum --testnet -o setconfig rpcport 7777
$ ./run_electrum --testnet daemon
E | daemon.Daemon | taskgroup died.
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/daemon.py", line 419, in _run
    async with self.taskgroup as group:
  File "/home/user/wspace/electrum/packages/aiorpcx/curio.py", line 297, in __aexit__
    await self.join()
  File "/home/user/wspace/electrum/electrum/util.py", line 1335, in join
    task.result()
  File "/home/user/wspace/electrum/electrum/daemon.py", line 281, in run
    await site.start()  #
  File "/home/user/wspace/electrum/packages/aiohttp/web_runner.py", line 121, in start
    self._server = await loop.create_server(
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1471, in create_server
    infos = await tasks.gather(*fs)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1408, in _create_server_getaddrinfo
    infos = await self._ensure_resolved((host, port), family=family,
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1404, in _ensure_resolved
    return await loop.getaddrinfo(host, port, family=family, type=type,
  File "/usr/lib/python3.10/asyncio/base_events.py", line 860, in getaddrinfo
    return await self.run_in_executor(
  File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3.10/socket.py", line 955, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -3] Temporary failure in name resolution
```
2022-10-29 03:14:12 +00:00
SomberNight
22371730d6 daemon.run_daemon: always stop if daemon.taskgroup dies
If running in daemon mode, and daemon.taskgroup died, we were not stopping.
Instead, we should gracefully stop and exit the process.

To reproduce:
```
$ ./run_electrum --testnet -o setconfig rpcport 1
$ ./run_electrum --testnet daemon -v
```
2022-10-29 02:58:09 +00:00
ThomasV
724edd223c make PayServer a plugin 2022-10-28 12:07:30 +02:00
ThomasV
eefb855671 PayServer: fix create_request 2022-10-27 10:15:30 +02:00
SomberNight
32ce64faa5 merchant www: allow symlinks for vendored libs
debian packager would like to replace vendored libs with system provided ones
(using symlinks). This requires "follow_symlinks=True".

discussion of security implications: https://serverfault.com/q/244592

to minimise attack surface, we only set this option for the "vendor/" directory.

related: https://github.com/spesmilo/electrum/issues/8023
2022-10-22 15:55:38 +00:00
SomberNight
60769e4769 be more robust to "electrum/www" missing 2022-10-19 14:15:44 +00:00
SomberNight
c463f5e23d password unification refactor: move methods from wallet to daemon
Note in particular that check_password_for_directory was not safe to use while the daemon had wallets loaded,
as the same file would have two corresponding Wallet() instances in memory. This was specifically handled in
the kivy GUI, on the caller side, by stopping-before and reloading-after the wallets; but it was dirty to
have the caller handle this.
2022-07-06 19:57:27 +02:00
SomberNight
c09903be68 daemon.PayServer.create_request: mark as broken...
looks like it's been broken for a long time.
- self.root ??
- lnworker.add_request API change
2022-06-22 02:46:22 +02:00
SomberNight
5b000a871f EventListener follow-ups: adapt left-out classes and minor clean-ups 2022-06-22 02:09:26 +02:00
SomberNight
641c3e23a4 daemon: default rpc socktype to "tcp" if rpcport is set
Having `rpcport` set already indicates the user does not want unix sockets,
we just default `rpchost` to "localhost".

related https://github.com/spesmilo/electrum/issues/7811
2022-05-13 16:19:28 +02:00
SomberNight
2c57c78ebe asyncio: stop using get_event_loop(). introduce ~singleton loop.
asyncio.get_event_loop() became deprecated in python3.10. (see https://github.com/python/cpython/issues/83710)
```
.../electrum/electrum/daemon.py:470: DeprecationWarning: There is no current event loop
  self.asyncio_loop = asyncio.get_event_loop()
.../electrum/electrum/network.py:276: DeprecationWarning: There is no current event loop
  self.asyncio_loop = asyncio.get_event_loop()
```
Also, according to that thread, "set_event_loop() [... is] not deprecated by oversight".
So, we stop using get_event_loop() and set_event_loop() in our own code.
Note that libraries we use (such as the stdlib for python <3.10), might call get_event_loop,
which then relies on us having called set_event_loop e.g. for the GUI thread. To work around
this, a custom event loop policy providing a get_event_loop implementation is used.

Previously, we have been using a single asyncio event loop, created with
util.create_and_start_event_loop, and code in many places got a reference to this loop
using asyncio.get_event_loop().
Now, we still use a single asyncio event loop, but it is now stored as a global in
util._asyncio_event_loop (access with util.get_asyncio_loop()).

I believe these changes also fix https://github.com/spesmilo/electrum/issues/5376
2022-04-29 18:49:07 +02:00
SomberNight
419fc6e1c1 gui init: raise GuiImportError instead of sys.exit if dep is missing 2022-04-11 17:40:16 +02:00
Federico
f42ae3f01c fix rpcsock (#7691)
some heuristics re default rpcsock type: if tcp-related other config keys are set, use tcp

closes https://github.com/spesmilo/electrum/issues/7686
2022-03-02 11:42:49 +00: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
ghost43
3e486b029c Merge pull request #7566 from SomberNight/202111_rpcsock_unix_default
daemon: change `rpcsock` default to "unix" where available
2022-02-16 14:50:29 +00: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
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
12b659ff99 daemon: change rpcsock default to "unix" where available
The daemon by default listens for RPC over a socket.
Before this change, this socket was a TCP/IP network socket (over localhost),
but now we change it to be a unix domain socket by default (if available).

This socket is used not only when sending commands over JSON-RPC,
but also when using the CLI, and to a tiny extent even used when running the GUI.
The type of socket used (and hence this change) is invisible to CLI and GUI users.
JSON-RPC users might want to use either the --rpcsock CLI flag
or set the "rpcsock" config key (to "tcp") to switch back to using a TCP/IP socket.

In practice it seems unix domain sockets are available on
all mainstream OSes/platforms, except for Windows.

closes https://github.com/spesmilo/electrum/issues/7553
2021-11-15 17:44:20 +01:00
SomberNight
62e1d8ed78 daemon: (trivial) subservices log when they start listening 2021-11-15 17:43:34 +01:00
SomberNight
88a1c1a618 python 3.10: fix some deprecation warnings and compat with 3.10 2021-11-09 01:02:57 +01:00
SomberNight
ca9b48e2d6 gui: add BaseElectrumGui base class for guis 2021-11-05 20:21:50 +01:00
SomberNight
c331c311db crash reporter: add EarlyExceptionsQueue
`util.send_exception_to_crash_reporter` is now useful and can be transparently
used even before the exception hook is set up.
2021-11-05 20:01:43 +01:00
SomberNight
e0246b30b9 daemon: if taskgroup dies, show error in GUI
If daemon.taskgroup dies
- in GUI mode, show a crash reporter window to the user,
  instead of immediately stopping the whole process.
- in daemon mode, log exception and stop process, as before.
2021-11-05 20:01:38 +01:00
SomberNight
3643b9f056 daemon: rework stopping
The CLI stop() command can now also stop the GUI.
2021-11-05 19:57:39 +01:00
yanmaani
b1005694ec cli: Add support for Unix domain sockets 2021-11-03 12:00:00 +00:00
bitromortac
ff61020dd2 watchtower: watch new channels 2021-09-27 10:31:44 +02:00
MrNaif2018
c35a46a727 Don't cleanup lockfile is listen_jsonrpc is False 2021-07-07 16:57:09 +03:00
SomberNight
66f68d6b1f commands: fix "close_wallet" cmd, which was deadlocking
fixes #7348
2021-06-17 12:35:31 +02:00
SomberNight
3ea27beb4e daemon: change stop() to use events, instead of polling 2021-03-17 19:42:20 +01:00