1# Electrum - lightweight Bitcoin client 2# 3# Permission is hereby granted, free of charge, to any person 4# obtaining a copy of this software and associated documentation files 5# (the "Software"), to deal in the Software without restriction, 6# including without limitation the rights to use, copy, modify, merge, 7# publish, distribute, sublicense, and/or sell copies of the Software, 8# and to permit persons to whom the Software is furnished to do so, 9# subject to the following conditions: 10# 11# The above copyright notice and this permission notice shall be 12# included in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21# SOFTWARE. 22import asyncio 23import json 24import locale 25import traceback 26import sys 27 28from .version import ELECTRUM_VERSION 29from . import constants 30from .i18n import _ 31from .util import make_aiohttp_session 32from .logging import describe_os_version, Logger, get_git_version 33 34 35class BaseCrashReporter(Logger): 36 report_server = "https://crashhub.electrum.org" 37 config_key = "show_crash_reporter" 38 issue_template = """<h2>Traceback</h2> 39<pre> 40{traceback} 41</pre> 42 43<h2>Additional information</h2> 44<ul> 45 <li>Electrum version: {app_version}</li> 46 <li>Python version: {python_version}</li> 47 <li>Operating system: {os}</li> 48 <li>Wallet type: {wallet_type}</li> 49 <li>Locale: {locale}</li> 50</ul> 51 """ 52 CRASH_MESSAGE = _('Something went wrong while executing Electrum.') 53 CRASH_TITLE = _('Sorry!') 54 REQUEST_HELP_MESSAGE = _('To help us diagnose and fix the problem, you can send us a bug report that contains ' 55 'useful debug information:') 56 DESCRIBE_ERROR_MESSAGE = _("Please briefly describe what led to the error (optional):") 57 ASK_CONFIRM_SEND = _("Do you want to send this report?") 58 USER_COMMENT_PLACEHOLDER = _("Do not enter sensitive/private information here. " 59 "The report will be visible on the public issue tracker.") 60 61 def __init__(self, exctype, value, tb): 62 Logger.__init__(self) 63 self.exc_args = (exctype, value, tb) 64 65 def send_report(self, asyncio_loop, proxy, endpoint="/crash", *, timeout=None): 66 if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server: 67 # Gah! Some kind of altcoin wants to send us crash reports. 68 raise Exception(_("Missing report URL.")) 69 report = self.get_traceback_info() 70 report.update(self.get_additional_info()) 71 report = json.dumps(report) 72 coro = self.do_post(proxy, BaseCrashReporter.report_server + endpoint, data=report) 73 response = asyncio.run_coroutine_threadsafe(coro, asyncio_loop).result(timeout) 74 return response 75 76 async def do_post(self, proxy, url, data): 77 async with make_aiohttp_session(proxy) as session: 78 async with session.post(url, data=data, raise_for_status=True) as resp: 79 return await resp.text() 80 81 def get_traceback_info(self): 82 exc_string = str(self.exc_args[1]) 83 stack = traceback.extract_tb(self.exc_args[2]) 84 readable_trace = self.__get_traceback_str_to_send() 85 id = { 86 "file": stack[-1].filename, 87 "name": stack[-1].name, 88 "type": self.exc_args[0].__name__ 89 } 90 return { 91 "exc_string": exc_string, 92 "stack": readable_trace, 93 "id": id 94 } 95 96 def get_additional_info(self): 97 args = { 98 "app_version": get_git_version() or ELECTRUM_VERSION, 99 "python_version": sys.version, 100 "os": describe_os_version(), 101 "wallet_type": "unknown", 102 "locale": locale.getdefaultlocale()[0] or "?", 103 "description": self.get_user_description() 104 } 105 try: 106 args["wallet_type"] = self.get_wallet_type() 107 except: 108 # Maybe the wallet isn't loaded yet 109 pass 110 return args 111 112 def __get_traceback_str_to_send(self) -> str: 113 # make sure that traceback sent to crash reporter contains 114 # e.__context__ and e.__cause__, i.e. if there was a chain of 115 # exceptions, we want the full traceback for the whole chain. 116 return "".join(traceback.format_exception(*self.exc_args)) 117 118 def _get_traceback_str_to_display(self) -> str: 119 # overridden in Qt subclass 120 return self.__get_traceback_str_to_send() 121 122 def get_report_string(self): 123 info = self.get_additional_info() 124 info["traceback"] = self._get_traceback_str_to_display() 125 return self.issue_template.format(**info) 126 127 def get_user_description(self): 128 raise NotImplementedError 129 130 def get_wallet_type(self) -> str: 131 raise NotImplementedError 132 133 134def trigger_crash(): 135 # note: do not change the type of the exception, the message, 136 # or the name of this method. All reports generated through this 137 # method will be grouped together by the crash reporter, and thus 138 # don't spam the issue tracker. 139 140 class TestingException(Exception): 141 pass 142 143 def crash_test(): 144 raise TestingException("triggered crash for testing purposes") 145 146 import threading 147 t = threading.Thread(target=crash_test) 148 t.start() 149