2017-03-28 15:49:50 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
#
|
|
|
|
|
# Electrum - lightweight Bitcoin client
|
|
|
|
|
#
|
|
|
|
|
# Permission is hereby granted, free of charge, to any person
|
|
|
|
|
# obtaining a copy of this software and associated documentation files
|
|
|
|
|
# (the "Software"), to deal in the Software without restriction,
|
|
|
|
|
# including without limitation the rights to use, copy, modify, merge,
|
|
|
|
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
|
|
|
# and to permit persons to whom the Software is furnished to do so,
|
|
|
|
|
# subject to the following conditions:
|
|
|
|
|
#
|
|
|
|
|
# The above copyright notice and this permission notice shall be
|
|
|
|
|
# included in all copies or substantial portions of the Software.
|
|
|
|
|
#
|
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
|
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
|
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
# SOFTWARE.
|
2018-02-26 19:53:16 +01:00
|
|
|
import sys
|
2020-04-18 05:48:11 +02:00
|
|
|
import html
|
2020-05-01 06:33:38 +02:00
|
|
|
from typing import TYPE_CHECKING, Optional, Set
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
from PyQt5.QtCore import QObject
|
|
|
|
|
import PyQt5.QtCore as QtCore
|
2019-02-11 20:21:24 +01:00
|
|
|
from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit,
|
|
|
|
|
QMessageBox, QHBoxLayout, QVBoxLayout)
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
from electrum.i18n import _
|
2023-03-16 19:07:33 +00:00
|
|
|
from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue, CrashReportResponse
|
2019-04-26 18:52:26 +02:00
|
|
|
from electrum.logging import Logger
|
2019-07-04 19:13:12 +02:00
|
|
|
from electrum import constants
|
2020-05-01 06:33:38 +02:00
|
|
|
from electrum.network import Network
|
2019-07-04 19:13:12 +02:00
|
|
|
|
2022-08-10 19:53:36 +02:00
|
|
|
from .util import MessageBoxMixin, read_QIcon, WaitingDialog, font_height
|
2018-03-18 01:13:02 +01:00
|
|
|
|
2020-05-01 06:33:38 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from electrum.simple_config import SimpleConfig
|
|
|
|
|
from electrum.wallet import Abstract_Wallet
|
|
|
|
|
|
2017-03-28 15:49:50 +02:00
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger):
|
2017-03-28 15:49:50 +02:00
|
|
|
_active_window = None
|
|
|
|
|
|
2020-05-01 06:33:38 +02:00
|
|
|
def __init__(self, config: 'SimpleConfig', exctype, value, tb):
|
2018-06-12 14:17:34 +02:00
|
|
|
BaseCrashReporter.__init__(self, exctype, value, tb)
|
2020-05-01 06:33:38 +02:00
|
|
|
self.network = Network.get_instance()
|
|
|
|
|
self.config = config
|
2018-09-06 16:18:45 +02:00
|
|
|
|
2017-03-28 15:49:50 +02:00
|
|
|
QWidget.__init__(self)
|
2018-04-15 20:45:30 +03:00
|
|
|
self.setWindowTitle('Electrum - ' + _('An Error Occurred'))
|
2017-03-28 15:49:50 +02:00
|
|
|
self.setMinimumSize(600, 300)
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
Logger.__init__(self)
|
|
|
|
|
|
2017-03-28 15:49:50 +02:00
|
|
|
main_box = QVBoxLayout()
|
|
|
|
|
|
2018-06-12 14:17:34 +02:00
|
|
|
heading = QLabel('<h2>' + BaseCrashReporter.CRASH_TITLE + '</h2>')
|
2017-03-28 15:49:50 +02:00
|
|
|
main_box.addWidget(heading)
|
2018-06-12 14:17:34 +02:00
|
|
|
main_box.addWidget(QLabel(BaseCrashReporter.CRASH_MESSAGE))
|
2017-03-28 15:49:50 +02:00
|
|
|
|
2018-06-12 14:17:34 +02:00
|
|
|
main_box.addWidget(QLabel(BaseCrashReporter.REQUEST_HELP_MESSAGE))
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
collapse_info = QPushButton(_("Show report contents"))
|
2018-03-18 01:13:02 +01:00
|
|
|
collapse_info.clicked.connect(
|
|
|
|
|
lambda: self.msg_box(QMessageBox.NoIcon,
|
2018-12-24 18:52:03 +01:00
|
|
|
self, _("Report contents"), self.get_report_string(),
|
|
|
|
|
rich_text=True))
|
2018-06-12 14:17:34 +02:00
|
|
|
|
2017-03-28 15:49:50 +02:00
|
|
|
main_box.addWidget(collapse_info)
|
|
|
|
|
|
2018-06-12 14:17:34 +02:00
|
|
|
main_box.addWidget(QLabel(BaseCrashReporter.DESCRIBE_ERROR_MESSAGE))
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
self.description_textfield = QTextEdit()
|
2022-08-10 19:53:36 +02:00
|
|
|
self.description_textfield.setFixedHeight(4 * font_height())
|
2021-07-05 18:59:02 +02:00
|
|
|
self.description_textfield.setPlaceholderText(self.USER_COMMENT_PLACEHOLDER)
|
2017-03-28 15:49:50 +02:00
|
|
|
main_box.addWidget(self.description_textfield)
|
|
|
|
|
|
2018-06-12 14:17:34 +02:00
|
|
|
main_box.addWidget(QLabel(BaseCrashReporter.ASK_CONFIRM_SEND))
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
buttons = QHBoxLayout()
|
|
|
|
|
|
|
|
|
|
report_button = QPushButton(_('Send Bug Report'))
|
|
|
|
|
report_button.clicked.connect(self.send_report)
|
2019-02-01 19:01:21 +01:00
|
|
|
report_button.setIcon(read_QIcon("tab_send.png"))
|
2017-03-28 15:49:50 +02:00
|
|
|
buttons.addWidget(report_button)
|
|
|
|
|
|
|
|
|
|
never_button = QPushButton(_('Never'))
|
|
|
|
|
never_button.clicked.connect(self.show_never)
|
|
|
|
|
buttons.addWidget(never_button)
|
|
|
|
|
|
|
|
|
|
close_button = QPushButton(_('Not Now'))
|
|
|
|
|
close_button.clicked.connect(self.close)
|
|
|
|
|
buttons.addWidget(close_button)
|
|
|
|
|
|
|
|
|
|
main_box.addLayout(buttons)
|
|
|
|
|
|
|
|
|
|
self.setLayout(main_box)
|
|
|
|
|
self.show()
|
|
|
|
|
|
|
|
|
|
def send_report(self):
|
2023-03-16 19:07:33 +00:00
|
|
|
def on_success(response: CrashReportResponse):
|
|
|
|
|
text = response.text
|
|
|
|
|
if response.url:
|
|
|
|
|
text += f" You can track further progress on <a href='{response.url}'>GitHub</a>."
|
2019-07-02 19:27:36 +02:00
|
|
|
self.show_message(parent=self,
|
|
|
|
|
title=_("Crash report"),
|
2023-03-16 19:07:33 +00:00
|
|
|
msg=text,
|
2019-07-04 19:13:12 +02:00
|
|
|
rich_text=True)
|
2019-07-02 19:27:36 +02:00
|
|
|
self.close()
|
|
|
|
|
def on_failure(exc_info):
|
|
|
|
|
e = exc_info[1]
|
|
|
|
|
self.logger.error('There was a problem with the automatic reporting', exc_info=exc_info)
|
|
|
|
|
self.show_critical(parent=self,
|
2019-07-04 19:13:12 +02:00
|
|
|
msg=(_('There was a problem with the automatic reporting:') + '<br/>' +
|
2020-06-14 03:41:45 +02:00
|
|
|
repr(e)[:120] + '<br/><br/>' +
|
2019-07-04 19:13:12 +02:00
|
|
|
_("Please report this issue manually") +
|
|
|
|
|
f' <a href="{constants.GIT_REPO_ISSUES_URL}">on GitHub</a>.'),
|
|
|
|
|
rich_text=True)
|
2019-07-02 19:27:36 +02:00
|
|
|
|
2020-05-01 06:33:38 +02:00
|
|
|
proxy = self.network.proxy
|
|
|
|
|
task = lambda: BaseCrashReporter.send_report(self, self.network.asyncio_loop, proxy)
|
2019-07-02 19:27:36 +02:00
|
|
|
msg = _('Sending crash report...')
|
|
|
|
|
WaitingDialog(self, msg, task, on_success, on_failure)
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
def on_close(self):
|
|
|
|
|
Exception_Window._active_window = None
|
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
|
|
def show_never(self):
|
2023-05-24 17:41:44 +00:00
|
|
|
self.config.SHOW_CRASH_REPORTER = False
|
2017-03-28 15:49:50 +02:00
|
|
|
self.close()
|
|
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
self.on_close()
|
|
|
|
|
event.accept()
|
|
|
|
|
|
2018-06-12 14:17:34 +02:00
|
|
|
def get_user_description(self):
|
|
|
|
|
return self.description_textfield.toPlainText()
|
2017-03-28 15:49:50 +02:00
|
|
|
|
2018-06-12 14:17:34 +02:00
|
|
|
def get_wallet_type(self):
|
2020-05-01 06:33:38 +02:00
|
|
|
wallet_types = Exception_Hook._INSTANCE.wallet_types_seen
|
|
|
|
|
return ",".join(wallet_types)
|
2017-03-28 15:49:50 +02:00
|
|
|
|
2021-07-12 19:24:58 +02:00
|
|
|
def _get_traceback_str_to_display(self) -> str:
|
2020-04-18 05:48:11 +02:00
|
|
|
# The msg_box that shows the report uses rich_text=True, so
|
|
|
|
|
# if traceback contains special HTML characters, e.g. '<',
|
|
|
|
|
# they need to be escaped to avoid formatting issues.
|
2021-07-12 19:24:58 +02:00
|
|
|
traceback_str = super()._get_traceback_str_to_display()
|
2020-04-18 05:48:11 +02:00
|
|
|
return html.escape(traceback_str)
|
|
|
|
|
|
2017-03-28 15:49:50 +02:00
|
|
|
|
|
|
|
|
def _show_window(*args):
|
|
|
|
|
if not Exception_Window._active_window:
|
|
|
|
|
Exception_Window._active_window = Exception_Window(*args)
|
|
|
|
|
|
|
|
|
|
|
2019-04-27 18:25:28 +02:00
|
|
|
class Exception_Hook(QObject, Logger):
|
2017-03-28 15:49:50 +02:00
|
|
|
_report_exception = QtCore.pyqtSignal(object, object, object, object)
|
|
|
|
|
|
2020-05-01 06:33:38 +02:00
|
|
|
_INSTANCE = None # type: Optional[Exception_Hook] # singleton
|
|
|
|
|
|
|
|
|
|
def __init__(self, *, config: 'SimpleConfig'):
|
|
|
|
|
QObject.__init__(self)
|
2019-04-27 18:25:28 +02:00
|
|
|
Logger.__init__(self)
|
2020-05-01 06:33:38 +02:00
|
|
|
assert self._INSTANCE is None, "Exception_Hook is supposed to be a singleton"
|
|
|
|
|
self.config = config
|
|
|
|
|
self.wallet_types_seen = set() # type: Set[str]
|
|
|
|
|
|
2017-03-28 15:49:50 +02:00
|
|
|
sys.excepthook = self.handler
|
|
|
|
|
self._report_exception.connect(_show_window)
|
2021-11-05 19:55:22 +01:00
|
|
|
EarlyExceptionsQueue.set_hook_as_ready()
|
2017-03-28 15:49:50 +02:00
|
|
|
|
2020-05-01 06:33:38 +02:00
|
|
|
@classmethod
|
qt init: on exc, let crash reporter appear instead of silently dying
related: https://github.com/spesmilo/electrum/issues/7390
```
20210706T091826.513398Z | ERROR | __main__ | daemon.run_gui errored
Traceback (most recent call last):
File "run_electrum", line 407, in handle_cmd
File "electrum\daemon.py", line 584, in run_gui
File "electrum\gui\qt\__init__.py", line 414, in main
File "electrum\gui\qt\__init__.py", line 291, in wrapper
File "electrum\gui\qt\__init__.py", line 316, in start_new_window
File "electrum\gui\qt\__init__.py", line 361, in _start_wizard_to_select_or_create_wallet
File "electrum\wallet_db.py", line 73, in __init__
File "electrum\wallet_db.py", line 106, in load_data
File "electrum\util.py", line 412, in <lambda>
File "electrum\util.py", line 408, in do_profile
File "electrum\wallet_db.py", line 175, in upgrade
File "electrum\wallet_db.py", line 540, in _convert_version_24
ValueError: too many values to unpack (expected 2)
```
2021-07-07 19:19:43 +02:00
|
|
|
def maybe_setup(cls, *, config: 'SimpleConfig', wallet: 'Abstract_Wallet' = None) -> None:
|
2023-05-24 17:41:44 +00:00
|
|
|
if not config.SHOW_CRASH_REPORTER:
|
crash reporter: if disabled via config, make sure EEQueue is flushed
If the user had `config.get("show_crash_reporter")==False`, they would
never get to see excs collected via `send_exception_to_crash_reporter(exc)`.
E.g.:
```
E | gui.qt.ElectrumGui | error loading wallet (or creating window for it)
Traceback (most recent call last):
File "/opt/electrum/electrum/gui/qt/__init__.py", line 433, in main
if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
File "/opt/electrum/electrum/gui/qt/__init__.py", line 307, in wrapper
return func(self, *args, **kwargs)
File "/opt/electrum/electrum/gui/qt/__init__.py", line 332, in start_new_window
wallet = self._start_wizard_to_select_or_create_wallet(path)
File "/opt/electrum/electrum/gui/qt/__init__.py", line 377, in _start_wizard_to_select_or_create_wallet
db = WalletDB(storage.read(), manual_upgrades=False)
File "/opt/electrum/electrum/wallet_db.py", line 73, in __init__
self.load_data(raw)
File "/opt/electrum/electrum/wallet_db.py", line 104, in load_data
self._after_upgrade_tasks()
File "/opt/electrum/electrum/wallet_db.py", line 202, in _after_upgrade_tasks
self._load_transactions()
File "/opt/electrum/electrum/util.py", line 439, in <lambda>
return lambda *args, **kw_args: do_profile(args, kw_args)
File "/opt/electrum/electrum/util.py", line 435, in do_profile
o = func(*args, **kw_args)
File "/opt/electrum/electrum/wallet_db.py", line 1310, in _load_transactions
self.data = StoredDict(self.data, self, [])
File "/opt/electrum/electrum/json_db.py", line 79, in __init__
self.__setitem__(k, v)
File "/opt/electrum/electrum/json_db.py", line 44, in wrapper
return func(self, *args, **kwargs)
File "/opt/electrum/electrum/json_db.py", line 97, in __setitem__
v = self.db._convert_dict(self.path, key, v)
File "/opt/electrum/electrum/wallet_db.py", line 1361, in _convert_dict
v = dict((k, SwapData(**x)) for k, x in v.items())
```
2022-03-15 13:39:33 +01:00
|
|
|
EarlyExceptionsQueue.set_hook_as_ready() # flush already queued exceptions
|
2020-05-01 06:33:38 +02:00
|
|
|
return
|
|
|
|
|
if not cls._INSTANCE:
|
|
|
|
|
cls._INSTANCE = Exception_Hook(config=config)
|
qt init: on exc, let crash reporter appear instead of silently dying
related: https://github.com/spesmilo/electrum/issues/7390
```
20210706T091826.513398Z | ERROR | __main__ | daemon.run_gui errored
Traceback (most recent call last):
File "run_electrum", line 407, in handle_cmd
File "electrum\daemon.py", line 584, in run_gui
File "electrum\gui\qt\__init__.py", line 414, in main
File "electrum\gui\qt\__init__.py", line 291, in wrapper
File "electrum\gui\qt\__init__.py", line 316, in start_new_window
File "electrum\gui\qt\__init__.py", line 361, in _start_wizard_to_select_or_create_wallet
File "electrum\wallet_db.py", line 73, in __init__
File "electrum\wallet_db.py", line 106, in load_data
File "electrum\util.py", line 412, in <lambda>
File "electrum\util.py", line 408, in do_profile
File "electrum\wallet_db.py", line 175, in upgrade
File "electrum\wallet_db.py", line 540, in _convert_version_24
ValueError: too many values to unpack (expected 2)
```
2021-07-07 19:19:43 +02:00
|
|
|
if wallet:
|
|
|
|
|
cls._INSTANCE.wallet_types_seen.add(wallet.wallet_type)
|
2020-05-01 06:33:38 +02:00
|
|
|
|
2019-04-27 18:25:28 +02:00
|
|
|
def handler(self, *exc_info):
|
|
|
|
|
self.logger.error('exception caught by crash reporter', exc_info=exc_info)
|
2020-05-01 06:33:38 +02:00
|
|
|
self._report_exception.emit(self.config, *exc_info)
|