to work around https://github.com/kivy/python-for-android/issues/1866 :
> PyCryptodome >=3.6.0 crashes at runtime (since commit f5aa2c1618). So actually the currently pinned version in the recipe does not work:
> 80e4f059c1/pythonforandroid/recipes/pycryptodome/__init__.py (L5)
>
> The issue at runtime is with ctypes.
>
>
> Say I have a main script that just does the following (95ccce7ae8/lib/Crypto/Util/_raw_api.py (L200)):
> ```
> import ctypes
> ctypes.pythonapi.PyObject_GetBuffer
> ```
>
> This works with cpython on my laptop, but with the p4a-compiled python on Android it fails:
>
> ```
> 06-14 19:06:27.053 15246 15274 I python : Android kivy bootstrap done. __name__ is __main__
> 06-14 19:06:27.053 15246 15274 I python : AND: Ran string
> 06-14 19:06:27.053 15246 15274 I python : Run user program, change dir and execute entrypoint
> 06-14 19:06:27.092 15246 15274 I python : Traceback (most recent call last):
> 06-14 19:06:27.092 15246 15274 I python : File "/home/user/wspace/electrum/.buildozer/android/app/main.py", line 84, in <module>
> 06-14 19:06:27.093 15246 15274 I python : File "/home/user/wspace/electrum/.buildozer/android/platform/build/build/other_builds/python3-libffi-openssl-sqlite3/armeabi-v7a__ndk_target_21/python3/Lib/ctypes/__init__.py", line 369, in __getattr__
> 06-14 19:06:27.093 15246 15274 I python : File "/home/user/wspace/electrum/.buildozer/android/platform/build/build/other_builds/python3-libffi-openssl-sqlite3/armeabi-v7a__ndk_target_21/python3/Lib/ctypes/__init__.py", line 374, in __getitem__
> 06-14 19:06:27.094 15246 15274 I python : AttributeError: undefined symbol: PyObject_GetBuffer
> ```
>
> I have also tried to access some other attributes of `ctypes.pythonapi`, such as `Py_IncRef`, which raises the same exception.
---
Unclear if the issue still affects newer p4a: upstream seems to think it's fixed, but there multiple comments in the thread saying otherwise. Anyway, rebasing p4a has other blockers atm.
Adds a new configvar `WALLET_PARTIAL_WRITES` to enable/disable partial writes for the walletDB.
This is a further restriction on top of the existing restrictions,
e.g. wallet files still need to have file encryption disabled for partial writes.
It defaults to off, so even for unencrypted wallets we disable partial writes for now.
This is used as a stopgap measure until we fix the issues found with the partial writes impl
(see https://github.com/spesmilo/electrum/issues/10000).
The hack needs to be applied before we try importing electrum_ecc, i.e. it needs to be in the main script.
However, it should also be applied if the main script is not invoked directly, but e.g. the user imports electrum directly.
Hence the duplication.
- CURRENT_WALLET is set when a single wallet is loaded in memory, and it
remains set after Electrum stops running.
- If several wallets are loaded at the same time, CURRENT_WALLET is unset,
and RPCs must specify the wallet explicitly (using --wallet for the CLI)
- The fallback to 'default_wallet' essentially only applies when
creating a new wallet file
This config var should have never been saved to the config file, therefore it is safe to remove
instead of setting to '' to overwrite any existing value.
This module tries to restrict the ability of other processes to access the memory of our process.
Traditionally, on Linux, one process can access the memory of another arbitrary process
if both are running as the same user (uid). (Root can ofc access the memory of ~any process)
Programs can opt-out from this by setting `prctl(PR_SET_DUMPABLE, 0);`
also see https://man.archlinux.org/man/PR_SET_DUMPABLE.2const.en
-----
Also, from https://unix.stackexchange.com/a/518452 :
In a terminal window:
```
% echo $$ # show our pid
6744
% read -sp 'secret password: '; echo
secret password:
%
```
Then in another terminal window:
```
% grep heap /proc/6744/maps
01bb7000-01c3e000 rw-p 00000000 00:00 0 [heap]
% dd if=/proc/6744/mem bs=1 skip=$((0x01bb7000)) count=$((0x01c3e000-0x01bb7000)) status=none |
strings | less
...
% dd if=/proc/6744/mem bs=1 skip=$((0x01bb7000)) count=$((0x01c3e000-0x01bb7000)) status=none |
strings | grep obiwan
obiwan_kenobi # "secret password"
```
- Separation between SwapManager and its transport:
Legacy transpport uses http, Nostr uses websockets
- The transport uses a context to open/close connections.
This context is not async, because it needs to be called
from the GUI
- Swapserver fees values are initialized to None instead
of 0, so that any attempt to use them before the swap
manager is initialized will raise an exception.
- Remove swapserver fees disk caching (swap_pairs file)
- Regtests use http transport
- Android uses http transport (until QML is ready)
```
$ ./run_electrum --testnet signmessage -w /home/user/.electrum/testnet/wallets/test_trezor_white_bip84 tb1q5pguna9y2g9y2gsu8r8gmxeye2cefvyly8dg02 heyheyhey -o
0.84 | W | plugins.jade.jadepy.jade | No module named 'electrum.plugins.jade.jadepy.jade_ble'
0.84 | W | plugins.jade.jadepy.jade | BLE scanning/connectivity will not be available
3.73 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/plugins/trezor/clientbase.py", line 151, in get_xpub
node = trezorlib.btc.get_public_node(self.client, address_n).node
File "/home/user/.local/lib/python3.10/site-packages/trezorlib/tools.py", line 274, in wrapped_f
ret = f(*args, **kwargs)
File "/home/user/.local/lib/python3.10/site-packages/trezorlib/btc.py", line 125, in get_public_node
return client.call(
File "/home/user/.local/lib/python3.10/site-packages/trezorlib/tools.py", line 297, in wrapped_f
return f(client, *args, **kwargs)
File "/home/user/.local/lib/python3.10/site-packages/trezorlib/client.py", line 260, in call
resp = self._callback_pin(resp)
File "/home/user/.local/lib/python3.10/site-packages/trezorlib/client.py", line 186, in _callback_pin
pin = self.ui.get_pin(msg.type)
File "/home/user/wspace/electrum/electrum/plugins/trezor/clientbase.py", line 308, in get_pin
pin = self.handler.get_pin(msg.format(self.device), show_strength=show_strength)
AttributeError: 'NoneType' object has no attribute 'get_pin'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 540, 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 227, in run_offline_command
password = get_password_for_hw_device_encrypted_storage(plugins)
File "/home/user/wspace/electrum/./run_electrum", line 212, in get_password_for_hw_device_encrypted_storage
return client.get_password_for_storage_encryption()
File "/home/user/wspace/electrum/electrum/plugin.py", line 523, in wrapper
return run_in_hwd_thread(partial(func, *args, **kwargs))
File "/home/user/wspace/electrum/electrum/plugin.py", line 516, in run_in_hwd_thread
return 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 "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
File "/home/user/wspace/electrum/electrum/plugins/hw_wallet/plugin.py", line 260, in get_password_for_storage_encryption
xpub = self.get_xpub(derivation, "standard")
File "/home/user/wspace/electrum/electrum/plugin.py", line 523, in wrapper
return run_in_hwd_thread(partial(func, *args, **kwargs))
File "/home/user/wspace/electrum/electrum/plugin.py", line 513, in run_in_hwd_thread
return func()
File "/home/user/wspace/electrum/electrum/plugins/trezor/clientbase.py", line 150, in get_xpub
with self.run_flow(creating_wallet=creating):
File "/home/user/wspace/electrum/electrum/plugins/trezor/clientbase.py", line 89, in __exit__
self.end_flow()
File "/home/user/wspace/electrum/electrum/plugins/trezor/clientbase.py", line 82, in end_flow
self.handler.finished()
AttributeError: 'NoneType' object has no attribute 'finished'
```
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"}}}
```
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
```
if qt is not installed, e.g. on a server, was getting:
```
$ ./run_electrum sadasdasddsa
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 532, in <module>
main()
File "/home/user/wspace/electrum/./run_electrum", line 383, in main
lang = get_default_language(gui_name=gui_name)
File "/home/user/wspace/electrum/electrum/gui/default_lang.py", line 23, in get_default_language
from PyQt5.QtCore import QLocale
ModuleNotFoundError: No module named 'PyQt5.QtCore'
```
Without this,
`$ python3 -m unittest electrum.tests.regtest.TestUnixSockets.test_unixsockets`
was failing on my machine but succeeding on CI, due to timing differences.